From 3e6cae8e5d40b8bdee1815124b5e36abcc746f56 Mon Sep 17 00:00:00 2001 From: Tim Harvey Date: Fri, 15 May 2020 10:57:06 -0700 Subject: [PATCH 01/14] dt-bindings: mfd: Add Gateworks System Controller bindings This patch adds documentation of device-tree bindings for the Gateworks System Controller (GSC). Signed-off-by: Tim Harvey Reviewed-by: Rob Herring Signed-off-by: Lee Jones --- .../bindings/mfd/gateworks-gsc.yaml | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/gateworks-gsc.yaml diff --git a/Documentation/devicetree/bindings/mfd/gateworks-gsc.yaml b/Documentation/devicetree/bindings/mfd/gateworks-gsc.yaml new file mode 100644 index 000000000000..487a8445722e --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/gateworks-gsc.yaml @@ -0,0 +1,196 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/gateworks-gsc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Gateworks System Controller + +description: | + The Gateworks System Controller (GSC) is a device present across various + Gateworks product families that provides a set of system related features + such as the following (refer to the board hardware user manuals to see what + features are present) + - Watchdog Timer + - GPIO + - Pushbutton controller + - Hardware monitor with ADC's for temperature and voltage rails and + fan controller + +maintainers: + - Tim Harvey + - Robert Jones + +properties: + $nodename: + pattern: "gsc@[0-9a-f]{1,2}" + compatible: + const: gw,gsc + + reg: + description: I2C device address + maxItems: 1 + + interrupts: + maxItems: 1 + + interrupt-controller: true + + "#interrupt-cells": + const: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + adc: + type: object + description: Optional hardware monitoring module + + properties: + compatible: + const: gw,gsc-adc + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + patternProperties: + "^channel@[0-9]+$": + type: object + description: | + Properties for a single ADC which can report cooked values + (i.e. temperature sensor based on thermister), raw values + (i.e. voltage rail with a pre-scaling resistor divider). + + properties: + reg: + description: Register of the ADC + maxItems: 1 + + label: + description: Name of the ADC input + + gw,mode: + description: | + conversion mode: + 0 - temperature, in C*10 + 1 - pre-scaled voltage value + 2 - scaled voltage based on an optional resistor divider + and optional offset + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1, 2] + + gw,voltage-divider-ohms: + description: Values of resistors for divider on raw ADC input + maxItems: 2 + items: + minimum: 1000 + maximum: 1000000 + + gw,voltage-offset-microvolt: + description: | + A positive voltage offset to apply to a raw ADC + (i.e. to compensate for a diode drop). + minimum: 0 + maximum: 1000000 + + required: + - gw,mode + - reg + - label + + required: + - compatible + - "#address-cells" + - "#size-cells" + +patternProperties: + "^fan-controller@[0-9a-f]+$": + type: object + description: Optional fan controller + + properties: + compatible: + const: gw,gsc-fan + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + reg: + description: The fan controller base address + maxItems: 1 + + required: + - compatible + - reg + - "#address-cells" + - "#size-cells" + +required: + - compatible + - reg + - interrupts + - interrupt-controller + - "#interrupt-cells" + - "#address-cells" + - "#size-cells" + +examples: + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + gsc@20 { + compatible = "gw,gsc"; + reg = <0x20>; + interrupt-parent = <&gpio1>; + interrupts = <4 GPIO_ACTIVE_LOW>; + interrupt-controller; + #interrupt-cells = <1>; + #address-cells = <1>; + #size-cells = <0>; + + adc { + compatible = "gw,gsc-adc"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { /* A0: Board Temperature */ + reg = <0x00>; + label = "temp"; + gw,mode = <0>; + }; + + channel@2 { /* A1: Input Voltage (raw ADC) */ + reg = <0x02>; + label = "vdd_vin"; + gw,mode = <1>; + gw,voltage-divider-ohms = <22100 1000>; + gw,voltage-offset-microvolt = <800000>; + }; + + channel@b { /* A2: Battery voltage */ + reg = <0x0b>; + label = "vdd_bat"; + gw,mode = <1>; + }; + }; + + fan-controller@2c { + #address-cells = <1>; + #size-cells = <0>; + compatible = "gw,gsc-fan"; + reg = <0x2c>; + }; + }; + }; From d85234994b2fb2d88fadd3d9e60385b02b244dac Mon Sep 17 00:00:00 2001 From: Tim Harvey Date: Fri, 15 May 2020 10:57:07 -0700 Subject: [PATCH 02/14] mfd: Add Gateworks System Controller core driver The Gateworks System Controller (GSC) is an I2C slave controller implemented with an MSP430 micro-controller whose firmware embeds the following features: - I/O expander (16 GPIO's) using PCA955x protocol - Real Time Clock using DS1672 protocol - User EEPROM using AT24 protocol - HWMON using custom protocol - Interrupt controller with tamper detect, user pushbotton - Watchdog controller capable of full board power-cycle - Power Control capable of full board power-cycle see http://trac.gateworks.com/wiki/gsc for more details Signed-off-by: Tim Harvey Signed-off-by: Lee Jones --- MAINTAINERS | 8 ++ drivers/mfd/Kconfig | 15 ++ drivers/mfd/Makefile | 1 + drivers/mfd/gateworks-gsc.c | 277 ++++++++++++++++++++++++++++++++++++ include/linux/mfd/gsc.h | 76 ++++++++++ 5 files changed, 377 insertions(+) create mode 100644 drivers/mfd/gateworks-gsc.c create mode 100644 include/linux/mfd/gsc.h diff --git a/MAINTAINERS b/MAINTAINERS index e64e5db31497..a5d5e5d963f1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7028,6 +7028,14 @@ F: kernel/futex.c F: tools/perf/bench/futex* F: tools/testing/selftests/futex/ +GATEWORKS SYSTEM CONTROLLER (GSC) DRIVER +M: Tim Harvey +M: Robert Jones +S: Maintained +F: Documentation/devicetree/bindings/mfd/gateworks-gsc.yaml +F: drivers/mfd/gateworks-gsc.c +F: include/linux/mfd/gsc.h + GASKET DRIVER FRAMEWORK M: Rob Springer M: Todd Poynor diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 0a59249198d3..05448e78e5b8 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -407,6 +407,21 @@ config MFD_EXYNOS_LPASS Select this option to enable support for Samsung Exynos Low Power Audio Subsystem. +config MFD_GATEWORKS_GSC + tristate "Gateworks System Controller" + depends on (I2C && OF) + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + Enable support for the Gateworks System Controller (GSC) found + on Gateworks Single Board Computers supporting system functions + such as push-button monitor, multiple ADC's for voltage and + temperature monitoring, fan controller and watchdog monitor. + This driver provides common support for accessing the device. + Additional drivers must be enabled in order to use the + functionality of the device. + config MFD_MC13XXX tristate depends on (SPI_MASTER || I2C) diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index f935d10cbf0f..ed433aed7010 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o obj-$(CONFIG_MFD_CROS_EC_DEV) += cros_ec_dev.o obj-$(CONFIG_MFD_EXYNOS_LPASS) += exynos-lpass.o +obj-$(CONFIG_MFD_GATEWORKS_GSC) += gateworks-gsc.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o diff --git a/drivers/mfd/gateworks-gsc.c b/drivers/mfd/gateworks-gsc.c new file mode 100644 index 000000000000..576da62fbb0c --- /dev/null +++ b/drivers/mfd/gateworks-gsc.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The Gateworks System Controller (GSC) is a multi-function + * device designed for use in Gateworks Single Board Computers. + * The control interface is I2C, with an interrupt. The device supports + * system functions such as push-button monitoring, multiple ADC's for + * voltage and temperature monitoring, fan controller and watchdog monitor. + * + * Copyright (C) 2020 Gateworks Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * The GSC suffers from an errata where occasionally during + * ADC cycles the chip can NAK I2C transactions. To ensure we have reliable + * register access we place retries around register access. + */ +#define I2C_RETRIES 3 + +int gsc_write(void *context, unsigned int reg, unsigned int val) +{ + struct i2c_client *client = context; + int retry, ret; + + for (retry = 0; retry < I2C_RETRIES; retry++) { + ret = i2c_smbus_write_byte_data(client, reg, val); + /* + * -EAGAIN returned when the i2c host controller is busy + * -EIO returned when i2c device is busy + */ + if (ret != -EAGAIN && ret != -EIO) + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(gsc_write); + +int gsc_read(void *context, unsigned int reg, unsigned int *val) +{ + struct i2c_client *client = context; + int retry, ret; + + for (retry = 0; retry < I2C_RETRIES; retry++) { + ret = i2c_smbus_read_byte_data(client, reg); + /* + * -EAGAIN returned when the i2c host controller is busy + * -EIO returned when i2c device is busy + */ + if (ret != -EAGAIN && ret != -EIO) + break; + } + *val = ret & 0xff; + + return 0; +} +EXPORT_SYMBOL_GPL(gsc_read); + +/* + * gsc_powerdown - API to use GSC to power down board for a specific time + * + * secs - number of seconds to remain powered off + */ +static int gsc_powerdown(struct gsc_dev *gsc, unsigned long secs) +{ + int ret; + unsigned char regs[4]; + + dev_info(&gsc->i2c->dev, "GSC powerdown for %ld seconds\n", + secs); + + put_unaligned_le32(secs, regs); + ret = regmap_bulk_write(gsc->regmap, GSC_TIME_ADD, regs, 4); + if (ret) + return ret; + + ret = regmap_update_bits(gsc->regmap, GSC_CTRL_1, + BIT(GSC_CTRL_1_SLEEP_ADD), + BIT(GSC_CTRL_1_SLEEP_ADD)); + if (ret) + return ret; + + ret = regmap_update_bits(gsc->regmap, GSC_CTRL_1, + BIT(GSC_CTRL_1_SLEEP_ACTIVATE) | + BIT(GSC_CTRL_1_SLEEP_ENABLE), + BIT(GSC_CTRL_1_SLEEP_ACTIVATE) | + BIT(GSC_CTRL_1_SLEEP_ENABLE)); + + + return ret; +} + +static ssize_t gsc_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct gsc_dev *gsc = dev_get_drvdata(dev); + const char *name = attr->attr.name; + int rz = 0; + + if (strcasecmp(name, "fw_version") == 0) + rz = sprintf(buf, "%d\n", gsc->fwver); + else if (strcasecmp(name, "fw_crc") == 0) + rz = sprintf(buf, "0x%04x\n", gsc->fwcrc); + else + dev_err(dev, "invalid command: '%s'\n", name); + + return rz; +} + +static ssize_t gsc_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gsc_dev *gsc = dev_get_drvdata(dev); + const char *name = attr->attr.name; + long value; + + if (strcasecmp(name, "powerdown") == 0) { + if (kstrtol(buf, 0, &value) == 0) + gsc_powerdown(gsc, value); + } else { + dev_err(dev, "invalid command: '%s\n", name); + } + + return count; +} + +static struct device_attribute attr_fwver = + __ATTR(fw_version, 0440, gsc_show, NULL); +static struct device_attribute attr_fwcrc = + __ATTR(fw_crc, 0440, gsc_show, NULL); +static struct device_attribute attr_pwrdown = + __ATTR(powerdown, 0220, NULL, gsc_store); + +static struct attribute *gsc_attrs[] = { + &attr_fwver.attr, + &attr_fwcrc.attr, + &attr_pwrdown.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = gsc_attrs, +}; + +static const struct of_device_id gsc_of_match[] = { + { .compatible = "gw,gsc", }, + { } +}; +MODULE_DEVICE_TABLE(of, gsc_of_match); + +static struct regmap_bus gsc_regmap_bus = { + .reg_read = gsc_read, + .reg_write = gsc_write, +}; + +static const struct regmap_config gsc_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_NONE, + .max_register = GSC_WP, +}; + +static const struct regmap_irq gsc_irqs[] = { + REGMAP_IRQ_REG(GSC_IRQ_PB, 0, BIT(GSC_IRQ_PB)), + REGMAP_IRQ_REG(GSC_IRQ_KEY_ERASED, 0, BIT(GSC_IRQ_KEY_ERASED)), + REGMAP_IRQ_REG(GSC_IRQ_EEPROM_WP, 0, BIT(GSC_IRQ_EEPROM_WP)), + REGMAP_IRQ_REG(GSC_IRQ_RESV, 0, BIT(GSC_IRQ_RESV)), + REGMAP_IRQ_REG(GSC_IRQ_GPIO, 0, BIT(GSC_IRQ_GPIO)), + REGMAP_IRQ_REG(GSC_IRQ_TAMPER, 0, BIT(GSC_IRQ_TAMPER)), + REGMAP_IRQ_REG(GSC_IRQ_WDT_TIMEOUT, 0, BIT(GSC_IRQ_WDT_TIMEOUT)), + REGMAP_IRQ_REG(GSC_IRQ_SWITCH_HOLD, 0, BIT(GSC_IRQ_SWITCH_HOLD)), +}; + +static const struct regmap_irq_chip gsc_irq_chip = { + .name = "gateworks-gsc", + .irqs = gsc_irqs, + .num_irqs = ARRAY_SIZE(gsc_irqs), + .num_regs = 1, + .status_base = GSC_IRQ_STATUS, + .mask_base = GSC_IRQ_ENABLE, + .mask_invert = true, + .ack_base = GSC_IRQ_STATUS, + .ack_invert = true, +}; + +static int gsc_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct gsc_dev *gsc; + struct regmap_irq_chip_data *irq_data; + int ret; + unsigned int reg; + + gsc = devm_kzalloc(dev, sizeof(*gsc), GFP_KERNEL); + if (!gsc) + return -ENOMEM; + + gsc->dev = &client->dev; + gsc->i2c = client; + i2c_set_clientdata(client, gsc); + + gsc->regmap = devm_regmap_init(dev, &gsc_regmap_bus, client, + &gsc_regmap_config); + if (IS_ERR(gsc->regmap)) + return PTR_ERR(gsc->regmap); + + if (regmap_read(gsc->regmap, GSC_FW_VER, ®)) + return -EIO; + gsc->fwver = reg; + + regmap_read(gsc->regmap, GSC_FW_CRC, ®); + gsc->fwcrc = reg; + regmap_read(gsc->regmap, GSC_FW_CRC + 1, ®); + gsc->fwcrc |= reg << 8; + + gsc->i2c_hwmon = devm_i2c_new_dummy_device(dev, client->adapter, + GSC_HWMON); + if (IS_ERR(gsc->i2c_hwmon)) { + dev_err(dev, "Failed to allocate I2C device for HWMON\n"); + return PTR_ERR(gsc->i2c_hwmon); + } + + ret = devm_regmap_add_irq_chip(dev, gsc->regmap, client->irq, + IRQF_ONESHOT | IRQF_SHARED | + IRQF_TRIGGER_FALLING, 0, + &gsc_irq_chip, &irq_data); + if (ret) + return ret; + + dev_info(dev, "Gateworks System Controller v%d: fw 0x%04x\n", + gsc->fwver, gsc->fwcrc); + + ret = sysfs_create_group(&dev->kobj, &attr_group); + if (ret) + dev_err(dev, "failed to create sysfs attrs\n"); + + ret = devm_of_platform_populate(dev); + if (ret) { + sysfs_remove_group(&dev->kobj, &attr_group); + return ret; + } + + return 0; +} + +static int gsc_remove(struct i2c_client *client) +{ + sysfs_remove_group(&client->dev.kobj, &attr_group); + + return 0; +} + +static struct i2c_driver gsc_driver = { + .driver = { + .name = "gateworks-gsc", + .of_match_table = gsc_of_match, + }, + .probe_new = gsc_probe, + .remove = gsc_remove, +}; +module_i2c_driver(gsc_driver); + +MODULE_AUTHOR("Tim Harvey "); +MODULE_DESCRIPTION("I2C Core interface for GSC"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mfd/gsc.h b/include/linux/mfd/gsc.h new file mode 100644 index 000000000000..6bd639c285b4 --- /dev/null +++ b/include/linux/mfd/gsc.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2020 Gateworks Corporation + */ +#ifndef __LINUX_MFD_GSC_H_ +#define __LINUX_MFD_GSC_H_ + +#include + +/* Device Addresses */ +#define GSC_MISC 0x20 +#define GSC_UPDATE 0x21 +#define GSC_GPIO 0x23 +#define GSC_HWMON 0x29 +#define GSC_EEPROM0 0x50 +#define GSC_EEPROM1 0x51 +#define GSC_EEPROM2 0x52 +#define GSC_EEPROM3 0x53 +#define GSC_RTC 0x68 + +/* Register offsets */ +enum { + GSC_CTRL_0 = 0x00, + GSC_CTRL_1 = 0x01, + GSC_TIME = 0x02, + GSC_TIME_ADD = 0x06, + GSC_IRQ_STATUS = 0x0A, + GSC_IRQ_ENABLE = 0x0B, + GSC_FW_CRC = 0x0C, + GSC_FW_VER = 0x0E, + GSC_WP = 0x0F, +}; + +/* Bit definitions */ +#define GSC_CTRL_0_PB_HARD_RESET 0 +#define GSC_CTRL_0_PB_CLEAR_SECURE_KEY 1 +#define GSC_CTRL_0_PB_SOFT_POWER_DOWN 2 +#define GSC_CTRL_0_PB_BOOT_ALTERNATE 3 +#define GSC_CTRL_0_PERFORM_CRC 4 +#define GSC_CTRL_0_TAMPER_DETECT 5 +#define GSC_CTRL_0_SWITCH_HOLD 6 + +#define GSC_CTRL_1_SLEEP_ENABLE 0 +#define GSC_CTRL_1_SLEEP_ACTIVATE 1 +#define GSC_CTRL_1_SLEEP_ADD 2 +#define GSC_CTRL_1_SLEEP_NOWAKEPB 3 +#define GSC_CTRL_1_WDT_TIME 4 +#define GSC_CTRL_1_WDT_ENABLE 5 +#define GSC_CTRL_1_SWITCH_BOOT_ENABLE 6 +#define GSC_CTRL_1_SWITCH_BOOT_CLEAR 7 + +#define GSC_IRQ_PB 0 +#define GSC_IRQ_KEY_ERASED 1 +#define GSC_IRQ_EEPROM_WP 2 +#define GSC_IRQ_RESV 3 +#define GSC_IRQ_GPIO 4 +#define GSC_IRQ_TAMPER 5 +#define GSC_IRQ_WDT_TIMEOUT 6 +#define GSC_IRQ_SWITCH_HOLD 7 + +int gsc_read(void *context, unsigned int reg, unsigned int *val); +int gsc_write(void *context, unsigned int reg, unsigned int val); + +struct gsc_dev { + struct device *dev; + + struct i2c_client *i2c; /* 0x20: interrupt controller, WDT */ + struct i2c_client *i2c_hwmon; /* 0x29: hwmon, fan controller */ + + struct regmap *regmap; + + unsigned int fwver; + unsigned short fwcrc; +}; + +#endif /* __LINUX_MFD_GSC_H_ */ From 3bce5377ef66a8700dcf7a9cb89b7aeb99326cb7 Mon Sep 17 00:00:00 2001 From: Tim Harvey Date: Fri, 15 May 2020 10:57:08 -0700 Subject: [PATCH 03/14] hwmon: Add Gateworks System Controller support The Gateworks System Controller has a hwmon sub-component that exposes up to 16 ADC's, some of which are temperature sensors, others which are voltage inputs. The ADC configuration (register mapping and name) is configured via device-tree and varies board to board. Signed-off-by: Tim Harvey Reviewed-by: Guenter Roeck Signed-off-by: Lee Jones --- Documentation/hwmon/gsc-hwmon.rst | 53 ++++ Documentation/hwmon/index.rst | 1 + MAINTAINERS | 3 + drivers/hwmon/Kconfig | 9 + drivers/hwmon/Makefile | 1 + drivers/hwmon/gsc-hwmon.c | 390 ++++++++++++++++++++++++ include/linux/platform_data/gsc_hwmon.h | 44 +++ 7 files changed, 501 insertions(+) create mode 100644 Documentation/hwmon/gsc-hwmon.rst create mode 100644 drivers/hwmon/gsc-hwmon.c create mode 100644 include/linux/platform_data/gsc_hwmon.h diff --git a/Documentation/hwmon/gsc-hwmon.rst b/Documentation/hwmon/gsc-hwmon.rst new file mode 100644 index 000000000000..ffac392a7129 --- /dev/null +++ b/Documentation/hwmon/gsc-hwmon.rst @@ -0,0 +1,53 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver gsc-hwmon +======================= + +Supported chips: Gateworks GSC +Datasheet: http://trac.gateworks.com/wiki/gsc +Author: Tim Harvey + +Description: +------------ + +This driver supports hardware monitoring for the temperature sensor, +various ADC's connected to the GSC, and optional FAN controller available +on some boards. + + +Voltage Monitoring +------------------ + +The voltage inputs are scaled either internally or by the driver depending +on the GSC version and firmware. The values returned by the driver do not need +further scaling. The voltage input labels provide the voltage rail name: + +inX_input Measured voltage (mV). +inX_label Name of voltage rail. + + +Temperature Monitoring +---------------------- + +Temperatures are measured with 12-bit or 10-bit resolution and are scaled +either internally or by the driver depending on the GSC version and firmware. +The values returned by the driver reflect millidegree Celcius: + +tempX_input Measured temperature. +tempX_label Name of temperature input. + + +PWM Output Control +------------------ + +The GSC features 1 PWM output that operates in automatic mode where the +PWM value will be scalled depending on 6 temperature boundaries. +The tempeature boundaries are read-write and in millidegree Celcius and the +read-only PWM values range from 0 (off) to 255 (full speed). +Fan speed will be set to minimum (off) when the temperature sensor reads +less than pwm1_auto_point1_temp and maximum when the temperature sensor +equals or exceeds pwm1_auto_point6_temp. + +pwm1_auto_point[1-6]_pwm PWM value. +pwm1_auto_point[1-6]_temp Temperature boundary. + diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 8ef62fd39787..1c02aa697a02 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -60,6 +60,7 @@ Hardware Monitoring Kernel Drivers ftsteutates g760a g762 + gsc-hwmon gl518sm hih6130 ibmaem diff --git a/MAINTAINERS b/MAINTAINERS index a5d5e5d963f1..d9cee5698bcc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7035,6 +7035,9 @@ S: Maintained F: Documentation/devicetree/bindings/mfd/gateworks-gsc.yaml F: drivers/mfd/gateworks-gsc.c F: include/linux/mfd/gsc.h +F: Documentation/hwmon/gsc-hwmon.rst +F: drivers/hwmon/gsc-hwmon.c +F: include/linux/platform_data/gsc_hwmon.h GASKET DRIVER FRAMEWORK M: Rob Springer diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 05a30832c6ba..d008b445baba 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -523,6 +523,15 @@ config SENSORS_F75375S This driver can also be built as a module. If so, the module will be called f75375s. +config SENSORS_GSC + tristate "Gateworks System Controller ADC" + depends on MFD_GATEWORKS_GSC + help + Support for the Gateworks System Controller A/D converters. + + To compile this driver as a module, choose M here: + the module will be called gsc-hwmon. + config SENSORS_MC13783_ADC tristate "Freescale MC13783/MC13892 ADC" depends on MFD_MC13XXX diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index b0b9c8e57176..a6564d00d94c 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -74,6 +74,7 @@ obj-$(CONFIG_SENSORS_G760A) += g760a.o obj-$(CONFIG_SENSORS_G762) += g762.o obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o +obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o diff --git a/drivers/hwmon/gsc-hwmon.c b/drivers/hwmon/gsc-hwmon.c new file mode 100644 index 000000000000..2137bc65829d --- /dev/null +++ b/drivers/hwmon/gsc-hwmon.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Gateworks System Controller Hardware Monitor module + * + * Copyright (C) 2020 Gateworks Corporation + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define GSC_HWMON_MAX_TEMP_CH 16 +#define GSC_HWMON_MAX_IN_CH 16 + +#define GSC_HWMON_RESOLUTION 12 +#define GSC_HWMON_VREF 2500 + +struct gsc_hwmon_data { + struct gsc_dev *gsc; + struct gsc_hwmon_platform_data *pdata; + struct regmap *regmap; + const struct gsc_hwmon_channel *temp_ch[GSC_HWMON_MAX_TEMP_CH]; + const struct gsc_hwmon_channel *in_ch[GSC_HWMON_MAX_IN_CH]; + u32 temp_config[GSC_HWMON_MAX_TEMP_CH + 1]; + u32 in_config[GSC_HWMON_MAX_IN_CH + 1]; + struct hwmon_channel_info temp_info; + struct hwmon_channel_info in_info; + const struct hwmon_channel_info *info[3]; + struct hwmon_chip_info chip; +}; + +static struct regmap_bus gsc_hwmon_regmap_bus = { + .reg_read = gsc_read, + .reg_write = gsc_write, +}; + +static const struct regmap_config gsc_hwmon_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_NONE, +}; + +static ssize_t pwm_auto_point_temp_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + u8 reg = hwmon->pdata->fan_base + (2 * attr->index); + u8 regs[2]; + int ret; + + ret = regmap_bulk_read(hwmon->regmap, reg, regs, 2); + if (ret) + return ret; + + ret = regs[0] | regs[1] << 8; + return sprintf(buf, "%d\n", ret * 10); +} + +static ssize_t pwm_auto_point_temp_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + u8 reg = hwmon->pdata->fan_base + (2 * attr->index); + u8 regs[2]; + long temp; + int err; + + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + + temp = clamp_val(temp, 0, 10000); + temp = DIV_ROUND_CLOSEST(temp, 10); + + regs[0] = temp & 0xff; + regs[1] = (temp >> 8) & 0xff; + err = regmap_bulk_write(hwmon->regmap, reg, regs, 2); + if (err) + return err; + + return count; +} + +static ssize_t pwm_auto_point_pwm_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%d\n", 255 * (50 + (attr->index * 10)) / 100); +} + +static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point1_pwm, pwm_auto_point_pwm, 0); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, pwm_auto_point_temp, 0); + +static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point2_pwm, pwm_auto_point_pwm, 1); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, pwm_auto_point_temp, 1); + +static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point3_pwm, pwm_auto_point_pwm, 2); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_auto_point_temp, 2); + +static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point4_pwm, pwm_auto_point_pwm, 3); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_auto_point_temp, 3); + +static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point5_pwm, pwm_auto_point_pwm, 4); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_auto_point_temp, 4); + +static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point6_pwm, pwm_auto_point_pwm, 5); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point6_temp, pwm_auto_point_temp, 5); + +static struct attribute *gsc_hwmon_attributes[] = { + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr, + NULL +}; + +static const struct attribute_group gsc_hwmon_group = { + .attrs = gsc_hwmon_attributes, +}; +__ATTRIBUTE_GROUPS(gsc_hwmon); + +static int +gsc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev); + const struct gsc_hwmon_channel *ch; + int sz, ret; + long tmp; + u8 buf[3]; + + switch (type) { + case hwmon_in: + ch = hwmon->in_ch[channel]; + break; + case hwmon_temp: + ch = hwmon->temp_ch[channel]; + break; + default: + return -EOPNOTSUPP; + } + + sz = (ch->mode == mode_voltage) ? 3 : 2; + ret = regmap_bulk_read(hwmon->regmap, ch->reg, buf, sz); + if (ret) + return ret; + + tmp = 0; + while (sz-- > 0) + tmp |= (buf[sz] << (8 * sz)); + + switch (ch->mode) { + case mode_temperature: + if (tmp > 0x8000) + tmp -= 0xffff; + break; + case mode_voltage_raw: + tmp = clamp_val(tmp, 0, BIT(GSC_HWMON_RESOLUTION)); + /* scale based on ref voltage and ADC resolution */ + tmp *= GSC_HWMON_VREF; + tmp >>= GSC_HWMON_RESOLUTION; + /* scale based on optional voltage divider */ + if (ch->vdiv[0] && ch->vdiv[1]) { + tmp *= (ch->vdiv[0] + ch->vdiv[1]); + tmp /= ch->vdiv[1]; + } + /* adjust by uV offset */ + tmp += ch->mvoffset; + break; + case mode_voltage: + /* no adjustment needed */ + break; + } + + *val = tmp; + + return 0; +} + +static int +gsc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **buf) +{ + struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev); + + switch (type) { + case hwmon_in: + *buf = hwmon->in_ch[channel]->name; + break; + case hwmon_temp: + *buf = hwmon->temp_ch[channel]->name; + break; + default: + return -ENOTSUPP; + } + + return 0; +} + +static umode_t +gsc_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr, + int ch) +{ + return 0444; +} + +static const struct hwmon_ops gsc_hwmon_ops = { + .is_visible = gsc_hwmon_is_visible, + .read = gsc_hwmon_read, + .read_string = gsc_hwmon_read_string, +}; + +static struct gsc_hwmon_platform_data * +gsc_hwmon_get_devtree_pdata(struct device *dev) +{ + struct gsc_hwmon_platform_data *pdata; + struct gsc_hwmon_channel *ch; + struct fwnode_handle *child; + struct device_node *fan; + int nchannels; + + nchannels = device_get_child_node_count(dev); + if (nchannels == 0) + return ERR_PTR(-ENODEV); + + pdata = devm_kzalloc(dev, + sizeof(*pdata) + nchannels * sizeof(*ch), + GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + ch = (struct gsc_hwmon_channel *)(pdata + 1); + pdata->channels = ch; + pdata->nchannels = nchannels; + + /* fan controller base address */ + fan = of_find_compatible_node(dev->parent->of_node, NULL, "gw,gsc-fan"); + if (fan && of_property_read_u32(fan, "reg", &pdata->fan_base)) { + dev_err(dev, "fan node without base\n"); + return ERR_PTR(-EINVAL); + } + + /* allocate structures for channels and count instances of each type */ + device_for_each_child_node(dev, child) { + if (fwnode_property_read_string(child, "label", &ch->name)) { + dev_err(dev, "channel without label\n"); + fwnode_handle_put(child); + return ERR_PTR(-EINVAL); + } + if (fwnode_property_read_u32(child, "reg", &ch->reg)) { + dev_err(dev, "channel without reg\n"); + fwnode_handle_put(child); + return ERR_PTR(-EINVAL); + } + if (fwnode_property_read_u32(child, "gw,mode", &ch->mode)) { + dev_err(dev, "channel without mode\n"); + fwnode_handle_put(child); + return ERR_PTR(-EINVAL); + } + if (ch->mode > mode_max) { + dev_err(dev, "invalid channel mode\n"); + fwnode_handle_put(child); + return ERR_PTR(-EINVAL); + } + + if (!fwnode_property_read_u32(child, + "gw,voltage-offset-microvolt", + &ch->mvoffset)) + ch->mvoffset /= 1000; + fwnode_property_read_u32_array(child, + "gw,voltage-divider-ohms", + ch->vdiv, ARRAY_SIZE(ch->vdiv)); + ch++; + } + + return pdata; +} + +static int gsc_hwmon_probe(struct platform_device *pdev) +{ + struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct device *hwmon_dev; + struct gsc_hwmon_platform_data *pdata = dev_get_platdata(dev); + struct gsc_hwmon_data *hwmon; + const struct attribute_group **groups; + int i, i_in, i_temp; + + if (!pdata) { + pdata = gsc_hwmon_get_devtree_pdata(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } + + hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + hwmon->gsc = gsc; + hwmon->pdata = pdata; + + hwmon->regmap = devm_regmap_init(dev, &gsc_hwmon_regmap_bus, + gsc->i2c_hwmon, + &gsc_hwmon_regmap_config); + if (IS_ERR(hwmon->regmap)) + return PTR_ERR(hwmon->regmap); + + for (i = 0, i_in = 0, i_temp = 0; i < hwmon->pdata->nchannels; i++) { + const struct gsc_hwmon_channel *ch = &pdata->channels[i]; + + switch (ch->mode) { + case mode_temperature: + if (i_temp == GSC_HWMON_MAX_TEMP_CH) { + dev_err(gsc->dev, "too many temp channels\n"); + return -EINVAL; + } + hwmon->temp_ch[i_temp] = ch; + hwmon->temp_config[i_temp] = HWMON_T_INPUT | + HWMON_T_LABEL; + i_temp++; + break; + case mode_voltage: + case mode_voltage_raw: + if (i_in == GSC_HWMON_MAX_IN_CH) { + dev_err(gsc->dev, "too many input channels\n"); + return -EINVAL; + } + hwmon->in_ch[i_in] = ch; + hwmon->in_config[i_in] = + HWMON_I_INPUT | HWMON_I_LABEL; + i_in++; + break; + default: + dev_err(gsc->dev, "invalid mode: %d\n", ch->mode); + return -EINVAL; + } + } + + /* setup config structures */ + hwmon->chip.ops = &gsc_hwmon_ops; + hwmon->chip.info = hwmon->info; + hwmon->info[0] = &hwmon->temp_info; + hwmon->info[1] = &hwmon->in_info; + hwmon->temp_info.type = hwmon_temp; + hwmon->temp_info.config = hwmon->temp_config; + hwmon->in_info.type = hwmon_in; + hwmon->in_info.config = hwmon->in_config; + + groups = pdata->fan_base ? gsc_hwmon_groups : NULL; + hwmon_dev = devm_hwmon_device_register_with_info(dev, + KBUILD_MODNAME, hwmon, + &hwmon->chip, groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct of_device_id gsc_hwmon_of_match[] = { + { .compatible = "gw,gsc-adc", }, + {} +}; + +static struct platform_driver gsc_hwmon_driver = { + .driver = { + .name = "gsc-hwmon", + .of_match_table = gsc_hwmon_of_match, + }, + .probe = gsc_hwmon_probe, +}; + +module_platform_driver(gsc_hwmon_driver); + +MODULE_AUTHOR("Tim Harvey "); +MODULE_DESCRIPTION("GSC hardware monitor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/platform_data/gsc_hwmon.h b/include/linux/platform_data/gsc_hwmon.h new file mode 100644 index 000000000000..ec1611aff863 --- /dev/null +++ b/include/linux/platform_data/gsc_hwmon.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _GSC_HWMON_H +#define _GSC_HWMON_H + +enum gsc_hwmon_mode { + mode_temperature, + mode_voltage, + mode_voltage_raw, + mode_max, +}; + +/** + * struct gsc_hwmon_channel - configuration parameters + * @reg: I2C register offset + * @mode: channel mode + * @name: channel name + * @mvoffset: voltage offset + * @vdiv: voltage divider array (2 resistor values in milli-ohms) + */ +struct gsc_hwmon_channel { + unsigned int reg; + unsigned int mode; + const char *name; + unsigned int mvoffset; + unsigned int vdiv[2]; +}; + +/** + * struct gsc_hwmon_platform_data - platform data for gsc_hwmon driver + * @channels: pointer to array of gsc_hwmon_channel structures + * describing channels + * @nchannels: number of elements in @channels array + * @vreference: voltage reference (mV) + * @resolution: ADC bit resolution + * @fan_base: register base for FAN controller + */ +struct gsc_hwmon_platform_data { + const struct gsc_hwmon_channel *channels; + int nchannels; + unsigned int resolution; + unsigned int vreference; + unsigned int fan_base; +}; +#endif From 4e2e7cfec13afa41fcbed1d9b93d83432aa0154f Mon Sep 17 00:00:00 2001 From: Hsin-Hsiung Wang Date: Tue, 21 Apr 2020 11:00:07 +0800 Subject: [PATCH 04/14] mfd: mt6397: Modify suspend/resume behavior Some pmics don't need backup interrupt settings, so we change to use pm notifier for the pmics which are necessary to store settings. Signed-off-by: Hsin-Hsiung Wang Signed-off-by: Lee Jones --- drivers/mfd/mt6397-core.c | 30 ---------------------------- drivers/mfd/mt6397-irq.c | 35 ++++++++++++++++++++++++++++++++- include/linux/mfd/mt6397/core.h | 2 ++ 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/drivers/mfd/mt6397-core.c b/drivers/mfd/mt6397-core.c index 0437c858d115..d2e70d834c40 100644 --- a/drivers/mfd/mt6397-core.c +++ b/drivers/mfd/mt6397-core.c @@ -100,35 +100,6 @@ static const struct mfd_cell mt6397_devs[] = { } }; -#ifdef CONFIG_PM_SLEEP -static int mt6397_irq_suspend(struct device *dev) -{ - struct mt6397_chip *chip = dev_get_drvdata(dev); - - regmap_write(chip->regmap, chip->int_con[0], chip->wake_mask[0]); - regmap_write(chip->regmap, chip->int_con[1], chip->wake_mask[1]); - - enable_irq_wake(chip->irq); - - return 0; -} - -static int mt6397_irq_resume(struct device *dev) -{ - struct mt6397_chip *chip = dev_get_drvdata(dev); - - regmap_write(chip->regmap, chip->int_con[0], chip->irq_masks_cur[0]); - regmap_write(chip->regmap, chip->int_con[1], chip->irq_masks_cur[1]); - - disable_irq_wake(chip->irq); - - return 0; -} -#endif - -static SIMPLE_DEV_PM_OPS(mt6397_pm_ops, mt6397_irq_suspend, - mt6397_irq_resume); - struct chip_data { u32 cid_addr; u32 cid_shift; @@ -238,7 +209,6 @@ static struct platform_driver mt6397_driver = { .driver = { .name = "mt6397", .of_match_table = of_match_ptr(mt6397_of_match), - .pm = &mt6397_pm_ops, }, .id_table = mt6397_id, }; diff --git a/drivers/mfd/mt6397-irq.c b/drivers/mfd/mt6397-irq.c index b2d3ce1f3115..2924919da991 100644 --- a/drivers/mfd/mt6397-irq.c +++ b/drivers/mfd/mt6397-irq.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -81,7 +82,7 @@ static struct irq_chip mt6397_irq_chip = { static void mt6397_irq_handle_reg(struct mt6397_chip *mt6397, int reg, int irqbase) { - unsigned int status; + unsigned int status = 0; int i, irq, ret; ret = regmap_read(mt6397->regmap, reg, &status); @@ -128,6 +129,36 @@ static const struct irq_domain_ops mt6397_irq_domain_ops = { .map = mt6397_irq_domain_map, }; +static int mt6397_irq_pm_notifier(struct notifier_block *notifier, + unsigned long pm_event, void *unused) +{ + struct mt6397_chip *chip = + container_of(notifier, struct mt6397_chip, pm_nb); + + switch (pm_event) { + case PM_SUSPEND_PREPARE: + regmap_write(chip->regmap, + chip->int_con[0], chip->wake_mask[0]); + regmap_write(chip->regmap, + chip->int_con[1], chip->wake_mask[1]); + enable_irq_wake(chip->irq); + break; + + case PM_POST_SUSPEND: + regmap_write(chip->regmap, + chip->int_con[0], chip->irq_masks_cur[0]); + regmap_write(chip->regmap, + chip->int_con[1], chip->irq_masks_cur[1]); + disable_irq_wake(chip->irq); + break; + + default: + break; + } + + return NOTIFY_DONE; +} + int mt6397_irq_init(struct mt6397_chip *chip) { int ret; @@ -159,6 +190,7 @@ int mt6397_irq_init(struct mt6397_chip *chip) regmap_write(chip->regmap, chip->int_con[0], 0x0); regmap_write(chip->regmap, chip->int_con[1], 0x0); + chip->pm_nb.notifier_call = mt6397_irq_pm_notifier; chip->irq_domain = irq_domain_add_linear(chip->dev->of_node, MT6397_IRQ_NR, &mt6397_irq_domain_ops, @@ -177,5 +209,6 @@ int mt6397_irq_init(struct mt6397_chip *chip) return ret; } + register_pm_notifier(&chip->pm_nb); return 0; } diff --git a/include/linux/mfd/mt6397/core.h b/include/linux/mfd/mt6397/core.h index fc88d315bdde..b81d33325ade 100644 --- a/include/linux/mfd/mt6397/core.h +++ b/include/linux/mfd/mt6397/core.h @@ -8,6 +8,7 @@ #define __MFD_MT6397_CORE_H__ #include +#include enum chip_id { MT6323_CHIP_ID = 0x23, @@ -54,6 +55,7 @@ enum mt6397_irq_numbers { struct mt6397_chip { struct device *dev; struct regmap *regmap; + struct notifier_block pm_nb; int irq; struct irq_domain *irq_domain; struct mutex irqlock; From d0c7347dc78f059fa2579a32f444b378589ad7f0 Mon Sep 17 00:00:00 2001 From: Hsin-Hsiung Wang Date: Tue, 21 Apr 2020 11:00:08 +0800 Subject: [PATCH 05/14] mfd: mt6397: Trim probe function to support different chips more cleanly Add new struct members for mfd-cells and irq initial function, so we can call devm_mfd_add_devices() only once. Signed-off-by: Hsin-Hsiung Wang Signed-off-by: Lee Jones --- drivers/mfd/mt6397-core.c | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/drivers/mfd/mt6397-core.c b/drivers/mfd/mt6397-core.c index d2e70d834c40..a313a720bb8b 100644 --- a/drivers/mfd/mt6397-core.c +++ b/drivers/mfd/mt6397-core.c @@ -103,22 +103,31 @@ static const struct mfd_cell mt6397_devs[] = { struct chip_data { u32 cid_addr; u32 cid_shift; + const struct mfd_cell *cells; + int cell_size; + int (*irq_init)(struct mt6397_chip *chip); }; static const struct chip_data mt6323_core = { .cid_addr = MT6323_CID, .cid_shift = 0, + .cells = mt6323_devs, + .cell_size = ARRAY_SIZE(mt6323_devs), + .irq_init = mt6397_irq_init, }; static const struct chip_data mt6397_core = { .cid_addr = MT6397_CID, .cid_shift = 0, + .cells = mt6397_devs, + .cell_size = ARRAY_SIZE(mt6397_devs), + .irq_init = mt6397_irq_init, }; static int mt6397_probe(struct platform_device *pdev) { int ret; - unsigned int id; + unsigned int id = 0; struct mt6397_chip *pmic; const struct chip_data *pmic_core; @@ -154,29 +163,13 @@ static int mt6397_probe(struct platform_device *pdev) if (pmic->irq <= 0) return pmic->irq; - ret = mt6397_irq_init(pmic); + ret = pmic_core->irq_init(pmic); if (ret) return ret; - switch (pmic->chip_id) { - case MT6323_CHIP_ID: - ret = devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, - mt6323_devs, ARRAY_SIZE(mt6323_devs), - NULL, 0, pmic->irq_domain); - break; - - case MT6391_CHIP_ID: - case MT6397_CHIP_ID: - ret = devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, - mt6397_devs, ARRAY_SIZE(mt6397_devs), - NULL, 0, pmic->irq_domain); - break; - - default: - dev_err(&pdev->dev, "unsupported chip: %d\n", pmic->chip_id); - return -ENODEV; - } - + ret = devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, + pmic_core->cells, pmic_core->cell_size, + NULL, 0, pmic->irq_domain); if (ret) { irq_domain_remove(pmic->irq_domain); dev_err(&pdev->dev, "failed to add child devices: %d\n", ret); From 6c3d5c97d260177bfe24b533e80df8500115be2f Mon Sep 17 00:00:00 2001 From: Hsin-Hsiung Wang Date: Tue, 21 Apr 2020 11:00:09 +0800 Subject: [PATCH 06/14] dt-bindings: mfd: Add compatible for the MediaTek MT6358 PMIC This adds compatible for the MediaTek MT6358 PMIC. Signed-off-by: Hsin-Hsiung Wang Reviewed-by: Rob Herring Signed-off-by: Lee Jones --- Documentation/devicetree/bindings/mfd/mt6397.txt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/mfd/mt6397.txt b/Documentation/devicetree/bindings/mfd/mt6397.txt index a9b105ac00a8..0df4382afc20 100644 --- a/Documentation/devicetree/bindings/mfd/mt6397.txt +++ b/Documentation/devicetree/bindings/mfd/mt6397.txt @@ -18,24 +18,30 @@ See the following for pwarp node definitions: This document describes the binding for MFD device and its sub module. Required properties: -compatible: "mediatek,mt6397" or "mediatek,mt6323" +compatible: + "mediatek,mt6323" for PMIC MT6323 + "mediatek,mt6358" for PMIC MT6358 + "mediatek,mt6397" for PMIC MT6397 Optional subnodes: - rtc Required properties: Should be one of follows - compatible: "mediatek,mt6323-rtc" + - compatible: "mediatek,mt6358-rtc" - compatible: "mediatek,mt6397-rtc" For details, see ../rtc/rtc-mt6397.txt - regulators Required properties: - - compatible: "mediatek,mt6397-regulator" - see ../regulator/mt6397-regulator.txt - compatible: "mediatek,mt6323-regulator" see ../regulator/mt6323-regulator.txt + - compatible: "mediatek,mt6358-regulator" + see ../regulator/mt6358-regulator.txt + - compatible: "mediatek,mt6397-regulator" + see ../regulator/mt6397-regulator.txt - codec Required properties: - - compatible: "mediatek,mt6397-codec" + - compatible: "mediatek,mt6397-codec" or "mediatek,mt6358-sound" - clk Required properties: - compatible: "mediatek,mt6397-clk" From 2b91c28f2abd9471aac4aa6cd8de7896ef469153 Mon Sep 17 00:00:00 2001 From: Hsin-Hsiung Wang Date: Tue, 21 Apr 2020 11:00:10 +0800 Subject: [PATCH 07/14] mfd: Add support for the MediaTek MT6358 PMIC This adds support for the MediaTek MT6358 PMIC. This is a multifunction device with the following sub modules: - Regulator - RTC - Codec - Interrupt It is interfaced to the host controller using SPI interface by a proprietary hardware called PMIC wrapper or pwrap. MT6358 MFD is a child device of the pwrap. Signed-off-by: Hsin-Hsiung Wang Signed-off-by: Lee Jones --- drivers/mfd/Makefile | 2 +- drivers/mfd/mt6358-irq.c | 235 ++++++++++++++++++++++ drivers/mfd/mt6397-core.c | 36 ++++ include/linux/mfd/mt6358/core.h | 158 +++++++++++++++ include/linux/mfd/mt6358/registers.h | 282 +++++++++++++++++++++++++++ include/linux/mfd/mt6397/core.h | 3 + 6 files changed, 715 insertions(+), 1 deletion(-) create mode 100644 drivers/mfd/mt6358-irq.c create mode 100644 include/linux/mfd/mt6358/core.h create mode 100644 include/linux/mfd/mt6358/registers.h diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index f935d10cbf0f..947032b63c64 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -239,7 +239,7 @@ obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o obj-$(CONFIG_INTEL_SOC_PMIC_BXTWC) += intel_soc_pmic_bxtwc.o obj-$(CONFIG_INTEL_SOC_PMIC_CHTWC) += intel_soc_pmic_chtwc.o obj-$(CONFIG_INTEL_SOC_PMIC_CHTDC_TI) += intel_soc_pmic_chtdc_ti.o -mt6397-objs := mt6397-core.o mt6397-irq.o +mt6397-objs := mt6397-core.o mt6397-irq.o mt6358-irq.o obj-$(CONFIG_MFD_MT6397) += mt6397.o obj-$(CONFIG_INTEL_SOC_PMIC_MRFLD) += intel_soc_pmic_mrfld.o diff --git a/drivers/mfd/mt6358-irq.c b/drivers/mfd/mt6358-irq.c new file mode 100644 index 000000000000..db734f2831ff --- /dev/null +++ b/drivers/mfd/mt6358-irq.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2020 MediaTek Inc. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct irq_top_t mt6358_ints[] = { + MT6358_TOP_GEN(BUCK), + MT6358_TOP_GEN(LDO), + MT6358_TOP_GEN(PSC), + MT6358_TOP_GEN(SCK), + MT6358_TOP_GEN(BM), + MT6358_TOP_GEN(HK), + MT6358_TOP_GEN(AUD), + MT6358_TOP_GEN(MISC), +}; + +static void pmic_irq_enable(struct irq_data *data) +{ + unsigned int hwirq = irqd_to_hwirq(data); + struct mt6397_chip *chip = irq_data_get_irq_chip_data(data); + struct pmic_irq_data *irqd = chip->irq_data; + + irqd->enable_hwirq[hwirq] = true; +} + +static void pmic_irq_disable(struct irq_data *data) +{ + unsigned int hwirq = irqd_to_hwirq(data); + struct mt6397_chip *chip = irq_data_get_irq_chip_data(data); + struct pmic_irq_data *irqd = chip->irq_data; + + irqd->enable_hwirq[hwirq] = false; +} + +static void pmic_irq_lock(struct irq_data *data) +{ + struct mt6397_chip *chip = irq_data_get_irq_chip_data(data); + + mutex_lock(&chip->irqlock); +} + +static void pmic_irq_sync_unlock(struct irq_data *data) +{ + unsigned int i, top_gp, gp_offset, en_reg, int_regs, shift; + struct mt6397_chip *chip = irq_data_get_irq_chip_data(data); + struct pmic_irq_data *irqd = chip->irq_data; + + for (i = 0; i < irqd->num_pmic_irqs; i++) { + if (irqd->enable_hwirq[i] == irqd->cache_hwirq[i]) + continue; + + /* Find out the IRQ group */ + top_gp = 0; + while ((top_gp + 1) < irqd->num_top && + i >= mt6358_ints[top_gp + 1].hwirq_base) + top_gp++; + + /* Find the IRQ registers */ + gp_offset = i - mt6358_ints[top_gp].hwirq_base; + int_regs = gp_offset / MT6358_REG_WIDTH; + shift = gp_offset % MT6358_REG_WIDTH; + en_reg = mt6358_ints[top_gp].en_reg + + (mt6358_ints[top_gp].en_reg_shift * int_regs); + + regmap_update_bits(chip->regmap, en_reg, BIT(shift), + irqd->enable_hwirq[i] << shift); + + irqd->cache_hwirq[i] = irqd->enable_hwirq[i]; + } + mutex_unlock(&chip->irqlock); +} + +static struct irq_chip mt6358_irq_chip = { + .name = "mt6358-irq", + .flags = IRQCHIP_SKIP_SET_WAKE, + .irq_enable = pmic_irq_enable, + .irq_disable = pmic_irq_disable, + .irq_bus_lock = pmic_irq_lock, + .irq_bus_sync_unlock = pmic_irq_sync_unlock, +}; + +static void mt6358_irq_sp_handler(struct mt6397_chip *chip, + unsigned int top_gp) +{ + unsigned int irq_status, sta_reg, status; + unsigned int hwirq, virq; + int i, j, ret; + + for (i = 0; i < mt6358_ints[top_gp].num_int_regs; i++) { + sta_reg = mt6358_ints[top_gp].sta_reg + + mt6358_ints[top_gp].sta_reg_shift * i; + + ret = regmap_read(chip->regmap, sta_reg, &irq_status); + if (ret) { + dev_err(chip->dev, + "Failed to read IRQ status, ret=%d\n", ret); + return; + } + + if (!irq_status) + continue; + + status = irq_status; + do { + j = __ffs(status); + + hwirq = mt6358_ints[top_gp].hwirq_base + + MT6358_REG_WIDTH * i + j; + + virq = irq_find_mapping(chip->irq_domain, hwirq); + if (virq) + handle_nested_irq(virq); + + status &= ~BIT(j); + } while (status); + + regmap_write(chip->regmap, sta_reg, irq_status); + } +} + +static irqreturn_t mt6358_irq_handler(int irq, void *data) +{ + struct mt6397_chip *chip = data; + struct pmic_irq_data *mt6358_irq_data = chip->irq_data; + unsigned int bit, i, top_irq_status = 0; + int ret; + + ret = regmap_read(chip->regmap, + mt6358_irq_data->top_int_status_reg, + &top_irq_status); + if (ret) { + dev_err(chip->dev, + "Failed to read status from the device, ret=%d\n", ret); + return IRQ_NONE; + } + + for (i = 0; i < mt6358_irq_data->num_top; i++) { + bit = BIT(mt6358_ints[i].top_offset); + if (top_irq_status & bit) { + mt6358_irq_sp_handler(chip, i); + top_irq_status &= ~bit; + if (!top_irq_status) + break; + } + } + + return IRQ_HANDLED; +} + +static int pmic_irq_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hw) +{ + struct mt6397_chip *mt6397 = d->host_data; + + irq_set_chip_data(irq, mt6397); + irq_set_chip_and_handler(irq, &mt6358_irq_chip, handle_level_irq); + irq_set_nested_thread(irq, 1); + irq_set_noprobe(irq); + + return 0; +} + +static const struct irq_domain_ops mt6358_irq_domain_ops = { + .map = pmic_irq_domain_map, + .xlate = irq_domain_xlate_twocell, +}; + +int mt6358_irq_init(struct mt6397_chip *chip) +{ + int i, j, ret; + struct pmic_irq_data *irqd; + + irqd = devm_kzalloc(chip->dev, sizeof(*irqd), GFP_KERNEL); + if (!irqd) + return -ENOMEM; + + chip->irq_data = irqd; + + mutex_init(&chip->irqlock); + irqd->top_int_status_reg = MT6358_TOP_INT_STATUS0; + irqd->num_pmic_irqs = MT6358_IRQ_NR; + irqd->num_top = ARRAY_SIZE(mt6358_ints); + + irqd->enable_hwirq = devm_kcalloc(chip->dev, + irqd->num_pmic_irqs, + sizeof(*irqd->enable_hwirq), + GFP_KERNEL); + if (!irqd->enable_hwirq) + return -ENOMEM; + + irqd->cache_hwirq = devm_kcalloc(chip->dev, + irqd->num_pmic_irqs, + sizeof(*irqd->cache_hwirq), + GFP_KERNEL); + if (!irqd->cache_hwirq) + return -ENOMEM; + + /* Disable all interrupts for initializing */ + for (i = 0; i < irqd->num_top; i++) { + for (j = 0; j < mt6358_ints[i].num_int_regs; j++) + regmap_write(chip->regmap, + mt6358_ints[i].en_reg + + mt6358_ints[i].en_reg_shift * j, 0); + } + + chip->irq_domain = irq_domain_add_linear(chip->dev->of_node, + irqd->num_pmic_irqs, + &mt6358_irq_domain_ops, chip); + if (!chip->irq_domain) { + dev_err(chip->dev, "Could not create IRQ domain\n"); + return -ENODEV; + } + + ret = devm_request_threaded_irq(chip->dev, chip->irq, NULL, + mt6358_irq_handler, IRQF_ONESHOT, + mt6358_irq_chip.name, chip); + if (ret) { + dev_err(chip->dev, "Failed to register IRQ=%d, ret=%d\n", + chip->irq, ret); + return ret; + } + + enable_irq_wake(chip->irq); + return ret; +} diff --git a/drivers/mfd/mt6397-core.c b/drivers/mfd/mt6397-core.c index a313a720bb8b..f6cd8a660602 100644 --- a/drivers/mfd/mt6397-core.c +++ b/drivers/mfd/mt6397-core.c @@ -12,13 +12,18 @@ #include #include #include +#include #include #include +#include #include #define MT6323_RTC_BASE 0x8000 #define MT6323_RTC_SIZE 0x40 +#define MT6358_RTC_BASE 0x0588 +#define MT6358_RTC_SIZE 0x3c + #define MT6397_RTC_BASE 0xe000 #define MT6397_RTC_SIZE 0x3e @@ -30,6 +35,11 @@ static const struct resource mt6323_rtc_resources[] = { DEFINE_RES_IRQ(MT6323_IRQ_STATUS_RTC), }; +static const struct resource mt6358_rtc_resources[] = { + DEFINE_RES_MEM(MT6358_RTC_BASE, MT6358_RTC_SIZE), + DEFINE_RES_IRQ(MT6358_IRQ_RTC), +}; + static const struct resource mt6397_rtc_resources[] = { DEFINE_RES_MEM(MT6397_RTC_BASE, MT6397_RTC_SIZE), DEFINE_RES_IRQ(MT6397_IRQ_RTC), @@ -74,6 +84,21 @@ static const struct mfd_cell mt6323_devs[] = { }, }; +static const struct mfd_cell mt6358_devs[] = { + { + .name = "mt6358-regulator", + .of_compatible = "mediatek,mt6358-regulator" + }, { + .name = "mt6358-rtc", + .num_resources = ARRAY_SIZE(mt6358_rtc_resources), + .resources = mt6358_rtc_resources, + .of_compatible = "mediatek,mt6358-rtc", + }, { + .name = "mt6358-sound", + .of_compatible = "mediatek,mt6358-sound" + }, +}; + static const struct mfd_cell mt6397_devs[] = { { .name = "mt6397-rtc", @@ -116,6 +141,14 @@ static const struct chip_data mt6323_core = { .irq_init = mt6397_irq_init, }; +static const struct chip_data mt6358_core = { + .cid_addr = MT6358_SWCID, + .cid_shift = 8, + .cells = mt6358_devs, + .cell_size = ARRAY_SIZE(mt6358_devs), + .irq_init = mt6358_irq_init, +}; + static const struct chip_data mt6397_core = { .cid_addr = MT6397_CID, .cid_shift = 0, @@ -182,6 +215,9 @@ static const struct of_device_id mt6397_of_match[] = { { .compatible = "mediatek,mt6323", .data = &mt6323_core, + }, { + .compatible = "mediatek,mt6358", + .data = &mt6358_core, }, { .compatible = "mediatek,mt6397", .data = &mt6397_core, diff --git a/include/linux/mfd/mt6358/core.h b/include/linux/mfd/mt6358/core.h new file mode 100644 index 000000000000..c5a11b7458d4 --- /dev/null +++ b/include/linux/mfd/mt6358/core.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2020 MediaTek Inc. + */ + +#ifndef __MFD_MT6358_CORE_H__ +#define __MFD_MT6358_CORE_H__ + +#define MT6358_REG_WIDTH 16 + +struct irq_top_t { + int hwirq_base; + unsigned int num_int_regs; + unsigned int num_int_bits; + unsigned int en_reg; + unsigned int en_reg_shift; + unsigned int sta_reg; + unsigned int sta_reg_shift; + unsigned int top_offset; +}; + +struct pmic_irq_data { + unsigned int num_top; + unsigned int num_pmic_irqs; + unsigned short top_int_status_reg; + bool *enable_hwirq; + bool *cache_hwirq; +}; + +enum mt6358_irq_top_status_shift { + MT6358_BUCK_TOP = 0, + MT6358_LDO_TOP, + MT6358_PSC_TOP, + MT6358_SCK_TOP, + MT6358_BM_TOP, + MT6358_HK_TOP, + MT6358_AUD_TOP, + MT6358_MISC_TOP, +}; + +enum mt6358_irq_numbers { + MT6358_IRQ_VPROC11_OC = 0, + MT6358_IRQ_VPROC12_OC, + MT6358_IRQ_VCORE_OC, + MT6358_IRQ_VGPU_OC, + MT6358_IRQ_VMODEM_OC, + MT6358_IRQ_VDRAM1_OC, + MT6358_IRQ_VS1_OC, + MT6358_IRQ_VS2_OC, + MT6358_IRQ_VPA_OC, + MT6358_IRQ_VCORE_PREOC, + MT6358_IRQ_VFE28_OC = 16, + MT6358_IRQ_VXO22_OC, + MT6358_IRQ_VRF18_OC, + MT6358_IRQ_VRF12_OC, + MT6358_IRQ_VEFUSE_OC, + MT6358_IRQ_VCN33_OC, + MT6358_IRQ_VCN28_OC, + MT6358_IRQ_VCN18_OC, + MT6358_IRQ_VCAMA1_OC, + MT6358_IRQ_VCAMA2_OC, + MT6358_IRQ_VCAMD_OC, + MT6358_IRQ_VCAMIO_OC, + MT6358_IRQ_VLDO28_OC, + MT6358_IRQ_VA12_OC, + MT6358_IRQ_VAUX18_OC, + MT6358_IRQ_VAUD28_OC, + MT6358_IRQ_VIO28_OC, + MT6358_IRQ_VIO18_OC, + MT6358_IRQ_VSRAM_PROC11_OC, + MT6358_IRQ_VSRAM_PROC12_OC, + MT6358_IRQ_VSRAM_OTHERS_OC, + MT6358_IRQ_VSRAM_GPU_OC, + MT6358_IRQ_VDRAM2_OC, + MT6358_IRQ_VMC_OC, + MT6358_IRQ_VMCH_OC, + MT6358_IRQ_VEMC_OC, + MT6358_IRQ_VSIM1_OC, + MT6358_IRQ_VSIM2_OC, + MT6358_IRQ_VIBR_OC, + MT6358_IRQ_VUSB_OC, + MT6358_IRQ_VBIF28_OC, + MT6358_IRQ_PWRKEY = 48, + MT6358_IRQ_HOMEKEY, + MT6358_IRQ_PWRKEY_R, + MT6358_IRQ_HOMEKEY_R, + MT6358_IRQ_NI_LBAT_INT, + MT6358_IRQ_CHRDET, + MT6358_IRQ_CHRDET_EDGE, + MT6358_IRQ_VCDT_HV_DET, + MT6358_IRQ_RTC = 64, + MT6358_IRQ_FG_BAT0_H = 80, + MT6358_IRQ_FG_BAT0_L, + MT6358_IRQ_FG_CUR_H, + MT6358_IRQ_FG_CUR_L, + MT6358_IRQ_FG_ZCV, + MT6358_IRQ_FG_BAT1_H, + MT6358_IRQ_FG_BAT1_L, + MT6358_IRQ_FG_N_CHARGE_L, + MT6358_IRQ_FG_IAVG_H, + MT6358_IRQ_FG_IAVG_L, + MT6358_IRQ_FG_TIME_H, + MT6358_IRQ_FG_DISCHARGE, + MT6358_IRQ_FG_CHARGE, + MT6358_IRQ_BATON_LV = 96, + MT6358_IRQ_BATON_HT, + MT6358_IRQ_BATON_BAT_IN, + MT6358_IRQ_BATON_BAT_OUT, + MT6358_IRQ_BIF, + MT6358_IRQ_BAT_H = 112, + MT6358_IRQ_BAT_L, + MT6358_IRQ_BAT2_H, + MT6358_IRQ_BAT2_L, + MT6358_IRQ_BAT_TEMP_H, + MT6358_IRQ_BAT_TEMP_L, + MT6358_IRQ_AUXADC_IMP, + MT6358_IRQ_NAG_C_DLTV, + MT6358_IRQ_AUDIO = 128, + MT6358_IRQ_ACCDET = 133, + MT6358_IRQ_ACCDET_EINT0, + MT6358_IRQ_ACCDET_EINT1, + MT6358_IRQ_SPI_CMD_ALERT = 144, + MT6358_IRQ_NR, +}; + +#define MT6358_IRQ_BUCK_BASE MT6358_IRQ_VPROC11_OC +#define MT6358_IRQ_LDO_BASE MT6358_IRQ_VFE28_OC +#define MT6358_IRQ_PSC_BASE MT6358_IRQ_PWRKEY +#define MT6358_IRQ_SCK_BASE MT6358_IRQ_RTC +#define MT6358_IRQ_BM_BASE MT6358_IRQ_FG_BAT0_H +#define MT6358_IRQ_HK_BASE MT6358_IRQ_BAT_H +#define MT6358_IRQ_AUD_BASE MT6358_IRQ_AUDIO +#define MT6358_IRQ_MISC_BASE MT6358_IRQ_SPI_CMD_ALERT + +#define MT6358_IRQ_BUCK_BITS (MT6358_IRQ_VCORE_PREOC - MT6358_IRQ_BUCK_BASE + 1) +#define MT6358_IRQ_LDO_BITS (MT6358_IRQ_VBIF28_OC - MT6358_IRQ_LDO_BASE + 1) +#define MT6358_IRQ_PSC_BITS (MT6358_IRQ_VCDT_HV_DET - MT6358_IRQ_PSC_BASE + 1) +#define MT6358_IRQ_SCK_BITS (MT6358_IRQ_RTC - MT6358_IRQ_SCK_BASE + 1) +#define MT6358_IRQ_BM_BITS (MT6358_IRQ_BIF - MT6358_IRQ_BM_BASE + 1) +#define MT6358_IRQ_HK_BITS (MT6358_IRQ_NAG_C_DLTV - MT6358_IRQ_HK_BASE + 1) +#define MT6358_IRQ_AUD_BITS (MT6358_IRQ_ACCDET_EINT1 - MT6358_IRQ_AUD_BASE + 1) +#define MT6358_IRQ_MISC_BITS \ + (MT6358_IRQ_SPI_CMD_ALERT - MT6358_IRQ_MISC_BASE + 1) + +#define MT6358_TOP_GEN(sp) \ +{ \ + .hwirq_base = MT6358_IRQ_##sp##_BASE, \ + .num_int_regs = \ + ((MT6358_IRQ_##sp##_BITS - 1) / MT6358_REG_WIDTH) + 1, \ + .num_int_bits = MT6358_IRQ_##sp##_BITS, \ + .en_reg = MT6358_##sp##_TOP_INT_CON0, \ + .en_reg_shift = 0x6, \ + .sta_reg = MT6358_##sp##_TOP_INT_STATUS0, \ + .sta_reg_shift = 0x2, \ + .top_offset = MT6358_##sp##_TOP, \ +} + +#endif /* __MFD_MT6358_CORE_H__ */ diff --git a/include/linux/mfd/mt6358/registers.h b/include/linux/mfd/mt6358/registers.h new file mode 100644 index 000000000000..2ad0b312aa28 --- /dev/null +++ b/include/linux/mfd/mt6358/registers.h @@ -0,0 +1,282 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2020 MediaTek Inc. + */ + +#ifndef __MFD_MT6358_REGISTERS_H__ +#define __MFD_MT6358_REGISTERS_H__ + +/* PMIC Registers */ +#define MT6358_SWCID 0xa +#define MT6358_MISC_TOP_INT_CON0 0x188 +#define MT6358_MISC_TOP_INT_STATUS0 0x194 +#define MT6358_TOP_INT_STATUS0 0x19e +#define MT6358_SCK_TOP_INT_CON0 0x52e +#define MT6358_SCK_TOP_INT_STATUS0 0x53a +#define MT6358_EOSC_CALI_CON0 0x540 +#define MT6358_EOSC_CALI_CON1 0x542 +#define MT6358_RTC_MIX_CON0 0x544 +#define MT6358_RTC_MIX_CON1 0x546 +#define MT6358_RTC_MIX_CON2 0x548 +#define MT6358_RTC_DSN_ID 0x580 +#define MT6358_RTC_DSN_REV0 0x582 +#define MT6358_RTC_DBI 0x584 +#define MT6358_RTC_DXI 0x586 +#define MT6358_RTC_BBPU 0x588 +#define MT6358_RTC_IRQ_STA 0x58a +#define MT6358_RTC_IRQ_EN 0x58c +#define MT6358_RTC_CII_EN 0x58e +#define MT6358_RTC_AL_MASK 0x590 +#define MT6358_RTC_TC_SEC 0x592 +#define MT6358_RTC_TC_MIN 0x594 +#define MT6358_RTC_TC_HOU 0x596 +#define MT6358_RTC_TC_DOM 0x598 +#define MT6358_RTC_TC_DOW 0x59a +#define MT6358_RTC_TC_MTH 0x59c +#define MT6358_RTC_TC_YEA 0x59e +#define MT6358_RTC_AL_SEC 0x5a0 +#define MT6358_RTC_AL_MIN 0x5a2 +#define MT6358_RTC_AL_HOU 0x5a4 +#define MT6358_RTC_AL_DOM 0x5a6 +#define MT6358_RTC_AL_DOW 0x5a8 +#define MT6358_RTC_AL_MTH 0x5aa +#define MT6358_RTC_AL_YEA 0x5ac +#define MT6358_RTC_OSC32CON 0x5ae +#define MT6358_RTC_POWERKEY1 0x5b0 +#define MT6358_RTC_POWERKEY2 0x5b2 +#define MT6358_RTC_PDN1 0x5b4 +#define MT6358_RTC_PDN2 0x5b6 +#define MT6358_RTC_SPAR0 0x5b8 +#define MT6358_RTC_SPAR1 0x5ba +#define MT6358_RTC_PROT 0x5bc +#define MT6358_RTC_DIFF 0x5be +#define MT6358_RTC_CALI 0x5c0 +#define MT6358_RTC_WRTGR 0x5c2 +#define MT6358_RTC_CON 0x5c4 +#define MT6358_RTC_SEC_CTRL 0x5c6 +#define MT6358_RTC_INT_CNT 0x5c8 +#define MT6358_RTC_SEC_DAT0 0x5ca +#define MT6358_RTC_SEC_DAT1 0x5cc +#define MT6358_RTC_SEC_DAT2 0x5ce +#define MT6358_RTC_SEC_DSN_ID 0x600 +#define MT6358_RTC_SEC_DSN_REV0 0x602 +#define MT6358_RTC_SEC_DBI 0x604 +#define MT6358_RTC_SEC_DXI 0x606 +#define MT6358_RTC_TC_SEC_SEC 0x608 +#define MT6358_RTC_TC_MIN_SEC 0x60a +#define MT6358_RTC_TC_HOU_SEC 0x60c +#define MT6358_RTC_TC_DOM_SEC 0x60e +#define MT6358_RTC_TC_DOW_SEC 0x610 +#define MT6358_RTC_TC_MTH_SEC 0x612 +#define MT6358_RTC_TC_YEA_SEC 0x614 +#define MT6358_RTC_SEC_CK_PDN 0x616 +#define MT6358_RTC_SEC_WRTGR 0x618 +#define MT6358_PSC_TOP_INT_CON0 0x910 +#define MT6358_PSC_TOP_INT_STATUS0 0x91c +#define MT6358_BM_TOP_INT_CON0 0xc32 +#define MT6358_BM_TOP_INT_CON1 0xc38 +#define MT6358_BM_TOP_INT_STATUS0 0xc4a +#define MT6358_BM_TOP_INT_STATUS1 0xc4c +#define MT6358_HK_TOP_INT_CON0 0xf92 +#define MT6358_HK_TOP_INT_STATUS0 0xf9e +#define MT6358_BUCK_TOP_INT_CON0 0x1318 +#define MT6358_BUCK_TOP_INT_STATUS0 0x1324 +#define MT6358_BUCK_VPROC11_CON0 0x1388 +#define MT6358_BUCK_VPROC11_DBG0 0x139e +#define MT6358_BUCK_VPROC11_DBG1 0x13a0 +#define MT6358_BUCK_VPROC11_ELR0 0x13a6 +#define MT6358_BUCK_VPROC12_CON0 0x1408 +#define MT6358_BUCK_VPROC12_DBG0 0x141e +#define MT6358_BUCK_VPROC12_DBG1 0x1420 +#define MT6358_BUCK_VPROC12_ELR0 0x1426 +#define MT6358_BUCK_VCORE_CON0 0x1488 +#define MT6358_BUCK_VCORE_DBG0 0x149e +#define MT6358_BUCK_VCORE_DBG1 0x14a0 +#define MT6358_BUCK_VCORE_ELR0 0x14aa +#define MT6358_BUCK_VGPU_CON0 0x1508 +#define MT6358_BUCK_VGPU_DBG0 0x151e +#define MT6358_BUCK_VGPU_DBG1 0x1520 +#define MT6358_BUCK_VGPU_ELR0 0x1526 +#define MT6358_BUCK_VMODEM_CON0 0x1588 +#define MT6358_BUCK_VMODEM_DBG0 0x159e +#define MT6358_BUCK_VMODEM_DBG1 0x15a0 +#define MT6358_BUCK_VMODEM_ELR0 0x15a6 +#define MT6358_BUCK_VDRAM1_CON0 0x1608 +#define MT6358_BUCK_VDRAM1_DBG0 0x161e +#define MT6358_BUCK_VDRAM1_DBG1 0x1620 +#define MT6358_BUCK_VDRAM1_ELR0 0x1626 +#define MT6358_BUCK_VS1_CON0 0x1688 +#define MT6358_BUCK_VS1_DBG0 0x169e +#define MT6358_BUCK_VS1_DBG1 0x16a0 +#define MT6358_BUCK_VS1_ELR0 0x16ae +#define MT6358_BUCK_VS2_CON0 0x1708 +#define MT6358_BUCK_VS2_DBG0 0x171e +#define MT6358_BUCK_VS2_DBG1 0x1720 +#define MT6358_BUCK_VS2_ELR0 0x172e +#define MT6358_BUCK_VPA_CON0 0x1788 +#define MT6358_BUCK_VPA_CON1 0x178a +#define MT6358_BUCK_VPA_ELR0 MT6358_BUCK_VPA_CON1 +#define MT6358_BUCK_VPA_DBG0 0x1792 +#define MT6358_BUCK_VPA_DBG1 0x1794 +#define MT6358_VPROC_ANA_CON0 0x180c +#define MT6358_VCORE_VGPU_ANA_CON0 0x1828 +#define MT6358_VMODEM_ANA_CON0 0x1888 +#define MT6358_VDRAM1_ANA_CON0 0x1896 +#define MT6358_VS1_ANA_CON0 0x18a2 +#define MT6358_VS2_ANA_CON0 0x18ae +#define MT6358_VPA_ANA_CON0 0x18ba +#define MT6358_LDO_TOP_INT_CON0 0x1a50 +#define MT6358_LDO_TOP_INT_CON1 0x1a56 +#define MT6358_LDO_TOP_INT_STATUS0 0x1a68 +#define MT6358_LDO_TOP_INT_STATUS1 0x1a6a +#define MT6358_LDO_VXO22_CON0 0x1a88 +#define MT6358_LDO_VXO22_CON1 0x1a96 +#define MT6358_LDO_VA12_CON0 0x1a9c +#define MT6358_LDO_VA12_CON1 0x1aaa +#define MT6358_LDO_VAUX18_CON0 0x1ab0 +#define MT6358_LDO_VAUX18_CON1 0x1abe +#define MT6358_LDO_VAUD28_CON0 0x1ac4 +#define MT6358_LDO_VAUD28_CON1 0x1ad2 +#define MT6358_LDO_VIO28_CON0 0x1ad8 +#define MT6358_LDO_VIO28_CON1 0x1ae6 +#define MT6358_LDO_VIO18_CON0 0x1aec +#define MT6358_LDO_VIO18_CON1 0x1afa +#define MT6358_LDO_VDRAM2_CON0 0x1b08 +#define MT6358_LDO_VDRAM2_CON1 0x1b16 +#define MT6358_LDO_VEMC_CON0 0x1b1c +#define MT6358_LDO_VEMC_CON1 0x1b2a +#define MT6358_LDO_VUSB_CON0_0 0x1b30 +#define MT6358_LDO_VUSB_CON1 0x1b40 +#define MT6358_LDO_VSRAM_PROC11_CON0 0x1b46 +#define MT6358_LDO_VSRAM_PROC11_DBG0 0x1b60 +#define MT6358_LDO_VSRAM_PROC11_DBG1 0x1b62 +#define MT6358_LDO_VSRAM_PROC11_TRACKING_CON0 0x1b64 +#define MT6358_LDO_VSRAM_PROC11_TRACKING_CON1 0x1b66 +#define MT6358_LDO_VSRAM_PROC11_TRACKING_CON2 0x1b68 +#define MT6358_LDO_VSRAM_PROC11_TRACKING_CON3 0x1b6a +#define MT6358_LDO_VSRAM_PROC12_TRACKING_CON0 0x1b6c +#define MT6358_LDO_VSRAM_PROC12_TRACKING_CON1 0x1b6e +#define MT6358_LDO_VSRAM_PROC12_TRACKING_CON2 0x1b70 +#define MT6358_LDO_VSRAM_PROC12_TRACKING_CON3 0x1b72 +#define MT6358_LDO_VSRAM_WAKEUP_CON0 0x1b74 +#define MT6358_LDO_GON1_ELR_NUM 0x1b76 +#define MT6358_LDO_VDRAM2_ELR0 0x1b78 +#define MT6358_LDO_VSRAM_PROC12_CON0 0x1b88 +#define MT6358_LDO_VSRAM_PROC12_DBG0 0x1ba2 +#define MT6358_LDO_VSRAM_PROC12_DBG1 0x1ba4 +#define MT6358_LDO_VSRAM_OTHERS_CON0 0x1ba6 +#define MT6358_LDO_VSRAM_OTHERS_DBG0 0x1bc0 +#define MT6358_LDO_VSRAM_OTHERS_DBG1 0x1bc2 +#define MT6358_LDO_VSRAM_GPU_CON0 0x1bc8 +#define MT6358_LDO_VSRAM_GPU_DBG0 0x1be2 +#define MT6358_LDO_VSRAM_GPU_DBG1 0x1be4 +#define MT6358_LDO_VSRAM_CON0 0x1bee +#define MT6358_LDO_VSRAM_CON1 0x1bf0 +#define MT6358_LDO_VSRAM_CON2 0x1bf2 +#define MT6358_LDO_VSRAM_CON3 0x1bf4 +#define MT6358_LDO_VFE28_CON0 0x1c08 +#define MT6358_LDO_VFE28_CON1 0x1c16 +#define MT6358_LDO_VFE28_CON2 0x1c18 +#define MT6358_LDO_VFE28_CON3 0x1c1a +#define MT6358_LDO_VRF18_CON0 0x1c1c +#define MT6358_LDO_VRF18_CON1 0x1c2a +#define MT6358_LDO_VRF18_CON2 0x1c2c +#define MT6358_LDO_VRF18_CON3 0x1c2e +#define MT6358_LDO_VRF12_CON0 0x1c30 +#define MT6358_LDO_VRF12_CON1 0x1c3e +#define MT6358_LDO_VRF12_CON2 0x1c40 +#define MT6358_LDO_VRF12_CON3 0x1c42 +#define MT6358_LDO_VEFUSE_CON0 0x1c44 +#define MT6358_LDO_VEFUSE_CON1 0x1c52 +#define MT6358_LDO_VEFUSE_CON2 0x1c54 +#define MT6358_LDO_VEFUSE_CON3 0x1c56 +#define MT6358_LDO_VCN18_CON0 0x1c58 +#define MT6358_LDO_VCN18_CON1 0x1c66 +#define MT6358_LDO_VCN18_CON2 0x1c68 +#define MT6358_LDO_VCN18_CON3 0x1c6a +#define MT6358_LDO_VCAMA1_CON0 0x1c6c +#define MT6358_LDO_VCAMA1_CON1 0x1c7a +#define MT6358_LDO_VCAMA1_CON2 0x1c7c +#define MT6358_LDO_VCAMA1_CON3 0x1c7e +#define MT6358_LDO_VCAMA2_CON0 0x1c88 +#define MT6358_LDO_VCAMA2_CON1 0x1c96 +#define MT6358_LDO_VCAMA2_CON2 0x1c98 +#define MT6358_LDO_VCAMA2_CON3 0x1c9a +#define MT6358_LDO_VCAMD_CON0 0x1c9c +#define MT6358_LDO_VCAMD_CON1 0x1caa +#define MT6358_LDO_VCAMD_CON2 0x1cac +#define MT6358_LDO_VCAMD_CON3 0x1cae +#define MT6358_LDO_VCAMIO_CON0 0x1cb0 +#define MT6358_LDO_VCAMIO_CON1 0x1cbe +#define MT6358_LDO_VCAMIO_CON2 0x1cc0 +#define MT6358_LDO_VCAMIO_CON3 0x1cc2 +#define MT6358_LDO_VMC_CON0 0x1cc4 +#define MT6358_LDO_VMC_CON1 0x1cd2 +#define MT6358_LDO_VMC_CON2 0x1cd4 +#define MT6358_LDO_VMC_CON3 0x1cd6 +#define MT6358_LDO_VMCH_CON0 0x1cd8 +#define MT6358_LDO_VMCH_CON1 0x1ce6 +#define MT6358_LDO_VMCH_CON2 0x1ce8 +#define MT6358_LDO_VMCH_CON3 0x1cea +#define MT6358_LDO_VIBR_CON0 0x1d08 +#define MT6358_LDO_VIBR_CON1 0x1d16 +#define MT6358_LDO_VIBR_CON2 0x1d18 +#define MT6358_LDO_VIBR_CON3 0x1d1a +#define MT6358_LDO_VCN33_CON0_0 0x1d1c +#define MT6358_LDO_VCN33_CON0_1 0x1d2a +#define MT6358_LDO_VCN33_CON1 0x1d2c +#define MT6358_LDO_VCN33_BT_CON1 MT6358_LDO_VCN33_CON1 +#define MT6358_LDO_VCN33_WIFI_CON1 MT6358_LDO_VCN33_CON1 +#define MT6358_LDO_VCN33_CON2 0x1d2e +#define MT6358_LDO_VCN33_CON3 0x1d30 +#define MT6358_LDO_VLDO28_CON0_0 0x1d32 +#define MT6358_LDO_VLDO28_CON0_1 0x1d40 +#define MT6358_LDO_VLDO28_CON1 0x1d42 +#define MT6358_LDO_VLDO28_CON2 0x1d44 +#define MT6358_LDO_VLDO28_CON3 0x1d46 +#define MT6358_LDO_VSIM1_CON0 0x1d48 +#define MT6358_LDO_VSIM1_CON1 0x1d56 +#define MT6358_LDO_VSIM1_CON2 0x1d58 +#define MT6358_LDO_VSIM1_CON3 0x1d5a +#define MT6358_LDO_VSIM2_CON0 0x1d5c +#define MT6358_LDO_VSIM2_CON1 0x1d6a +#define MT6358_LDO_VSIM2_CON2 0x1d6c +#define MT6358_LDO_VSIM2_CON3 0x1d6e +#define MT6358_LDO_VCN28_CON0 0x1d88 +#define MT6358_LDO_VCN28_CON1 0x1d96 +#define MT6358_LDO_VCN28_CON2 0x1d98 +#define MT6358_LDO_VCN28_CON3 0x1d9a +#define MT6358_VRTC28_CON0 0x1d9c +#define MT6358_LDO_VBIF28_CON0 0x1d9e +#define MT6358_LDO_VBIF28_CON1 0x1dac +#define MT6358_LDO_VBIF28_CON2 0x1dae +#define MT6358_LDO_VBIF28_CON3 0x1db0 +#define MT6358_VCAMA1_ANA_CON0 0x1e08 +#define MT6358_VCAMA2_ANA_CON0 0x1e0c +#define MT6358_VCN33_ANA_CON0 0x1e28 +#define MT6358_VSIM1_ANA_CON0 0x1e2c +#define MT6358_VSIM2_ANA_CON0 0x1e30 +#define MT6358_VUSB_ANA_CON0 0x1e34 +#define MT6358_VEMC_ANA_CON0 0x1e38 +#define MT6358_VLDO28_ANA_CON0 0x1e3c +#define MT6358_VIO28_ANA_CON0 0x1e40 +#define MT6358_VIBR_ANA_CON0 0x1e44 +#define MT6358_VMCH_ANA_CON0 0x1e48 +#define MT6358_VMC_ANA_CON0 0x1e4c +#define MT6358_VRF18_ANA_CON0 0x1e88 +#define MT6358_VCN18_ANA_CON0 0x1e8c +#define MT6358_VCAMIO_ANA_CON0 0x1e90 +#define MT6358_VIO18_ANA_CON0 0x1e94 +#define MT6358_VEFUSE_ANA_CON0 0x1e98 +#define MT6358_VRF12_ANA_CON0 0x1e9c +#define MT6358_VSRAM_PROC11_ANA_CON0 0x1ea0 +#define MT6358_VSRAM_PROC12_ANA_CON0 0x1ea4 +#define MT6358_VSRAM_OTHERS_ANA_CON0 0x1ea6 +#define MT6358_VSRAM_GPU_ANA_CON0 0x1ea8 +#define MT6358_VDRAM2_ANA_CON0 0x1eaa +#define MT6358_VCAMD_ANA_CON0 0x1eae +#define MT6358_VA12_ANA_CON0 0x1eb2 +#define MT6358_AUD_TOP_INT_CON0 0x2228 +#define MT6358_AUD_TOP_INT_STATUS0 0x2234 + +#endif /* __MFD_MT6358_REGISTERS_H__ */ diff --git a/include/linux/mfd/mt6397/core.h b/include/linux/mfd/mt6397/core.h index b81d33325ade..949268581b36 100644 --- a/include/linux/mfd/mt6397/core.h +++ b/include/linux/mfd/mt6397/core.h @@ -12,6 +12,7 @@ enum chip_id { MT6323_CHIP_ID = 0x23, + MT6358_CHIP_ID = 0x58, MT6391_CHIP_ID = 0x91, MT6397_CHIP_ID = 0x97, }; @@ -65,8 +66,10 @@ struct mt6397_chip { u16 int_con[2]; u16 int_status[2]; u16 chip_id; + void *irq_data; }; +int mt6358_irq_init(struct mt6397_chip *chip); int mt6397_irq_init(struct mt6397_chip *chip); #endif /* __MFD_MT6397_CORE_H__ */ From 29ee40091e27615530c0ba7773a2879d8266381e Mon Sep 17 00:00:00 2001 From: Ran Bi Date: Tue, 21 Apr 2020 11:00:11 +0800 Subject: [PATCH 08/14] rtc: mt6397: Add support for the MediaTek MT6358 RTC This add support for the MediaTek MT6358 RTC. Driver using compatible data to store different RTC_WRTGR address offset. This replace RTC_WRTGR to RTC_WRTGR_MT6323 in mt6323-poweroff driver which only needed by armv7 CPU without ATF. Signed-off-by: Ran Bi Signed-off-by: Hsin-Hsiung Wang Reviewed-by: Nicolas Boichat Acked-by: Alexandre Belloni Acked-by: Sebastian Reichel Reviewed-by: Yingjoe Chen Signed-off-by: Lee Jones --- drivers/power/reset/mt6323-poweroff.c | 2 +- drivers/rtc/rtc-mt6397.c | 18 +++++++++++++++--- include/linux/mfd/mt6397/rtc.h | 9 ++++++++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/drivers/power/reset/mt6323-poweroff.c b/drivers/power/reset/mt6323-poweroff.c index 1caf43d9e46d..0532803e6cbc 100644 --- a/drivers/power/reset/mt6323-poweroff.c +++ b/drivers/power/reset/mt6323-poweroff.c @@ -30,7 +30,7 @@ static void mt6323_do_pwroff(void) int ret; regmap_write(pwrc->regmap, pwrc->base + RTC_BBPU, RTC_BBPU_KEY); - regmap_write(pwrc->regmap, pwrc->base + RTC_WRTGR, 1); + regmap_write(pwrc->regmap, pwrc->base + RTC_WRTGR_MT6323, 1); ret = regmap_read_poll_timeout(pwrc->regmap, pwrc->base + RTC_BBPU, val, diff --git a/drivers/rtc/rtc-mt6397.c b/drivers/rtc/rtc-mt6397.c index cda238dfe69b..f8b1353777ba 100644 --- a/drivers/rtc/rtc-mt6397.c +++ b/drivers/rtc/rtc-mt6397.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -20,7 +21,7 @@ static int mtk_rtc_write_trigger(struct mt6397_rtc *rtc) int ret; u32 data; - ret = regmap_write(rtc->regmap, rtc->addr_base + RTC_WRTGR, 1); + ret = regmap_write(rtc->regmap, rtc->addr_base + rtc->data->wrtgr, 1); if (ret < 0) return ret; @@ -269,6 +270,8 @@ static int mtk_rtc_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); rtc->addr_base = res->start; + rtc->data = of_device_get_match_data(&pdev->dev); + rtc->irq = platform_get_irq(pdev, 0); if (rtc->irq < 0) return rtc->irq; @@ -325,9 +328,18 @@ static int mt6397_rtc_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(mt6397_pm_ops, mt6397_rtc_suspend, mt6397_rtc_resume); +static const struct mtk_rtc_data mt6358_rtc_data = { + .wrtgr = RTC_WRTGR_MT6358, +}; + +static const struct mtk_rtc_data mt6397_rtc_data = { + .wrtgr = RTC_WRTGR_MT6397, +}; + static const struct of_device_id mt6397_rtc_of_match[] = { - { .compatible = "mediatek,mt6323-rtc", }, - { .compatible = "mediatek,mt6397-rtc", }, + { .compatible = "mediatek,mt6323-rtc", .data = &mt6397_rtc_data }, + { .compatible = "mediatek,mt6358-rtc", .data = &mt6358_rtc_data }, + { .compatible = "mediatek,mt6397-rtc", .data = &mt6397_rtc_data }, { } }; MODULE_DEVICE_TABLE(of, mt6397_rtc_of_match); diff --git a/include/linux/mfd/mt6397/rtc.h b/include/linux/mfd/mt6397/rtc.h index 7dfb63b81373..66989a16221a 100644 --- a/include/linux/mfd/mt6397/rtc.h +++ b/include/linux/mfd/mt6397/rtc.h @@ -18,7 +18,9 @@ #define RTC_BBPU_CBUSY BIT(6) #define RTC_BBPU_KEY (0x43 << 8) -#define RTC_WRTGR 0x003c +#define RTC_WRTGR_MT6358 0x003a +#define RTC_WRTGR_MT6397 0x003c +#define RTC_WRTGR_MT6323 RTC_WRTGR_MT6397 #define RTC_IRQ_STA 0x0002 #define RTC_IRQ_STA_AL BIT(0) @@ -65,6 +67,10 @@ #define MTK_RTC_POLL_DELAY_US 10 #define MTK_RTC_POLL_TIMEOUT (jiffies_to_usecs(HZ)) +struct mtk_rtc_data { + u32 wrtgr; +}; + struct mt6397_rtc { struct device *dev; struct rtc_device *rtc_dev; @@ -74,6 +80,7 @@ struct mt6397_rtc { struct regmap *regmap; int irq; u32 addr_base; + const struct mtk_rtc_data *data; }; #endif /* _LINUX_MFD_MT6397_RTC_H_ */ From 25c8c452ff45e9376c0b664d549fe2f57ee9d602 Mon Sep 17 00:00:00 2001 From: Saravanan Sekar Date: Tue, 26 May 2020 11:06:41 +0200 Subject: [PATCH 09/14] dt-bindings: mfd: Add document bindings for mp2629 Add device tree binding information for mp2629 mfd driver. Signed-off-by: Saravanan Sekar Reviewed-by: Andy Shevchenko Reviewed-by: Rob Herring Signed-off-by: Lee Jones --- .../devicetree/bindings/mfd/mps,mp2629.yaml | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/mps,mp2629.yaml diff --git a/Documentation/devicetree/bindings/mfd/mps,mp2629.yaml b/Documentation/devicetree/bindings/mfd/mps,mp2629.yaml new file mode 100644 index 000000000000..f91acc42d652 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/mps,mp2629.yaml @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/mps,mp2629.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MP2629 Battery Charger PMIC from Monolithic Power System. + +maintainers: + - Saravanan Sekar + +description: | + MP2629 is a PMIC providing battery charging and power supply for smartphones, + wireless camera and portable devices. Chip is controlled over I2C. + + The battery charge management device handles battery charger controller and + ADC IIO device for battery, system voltage + +properties: + compatible: + const: mps,mp2629 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + interrupt-controller: true + + "#interrupt-cells": + const: 2 + description: + The first cell is the IRQ number, the second cell is the trigger type. + +required: + - compatible + - reg + - interrupts + - interrupt-controller + - "#interrupt-cells" + +additionalProperties: false + +examples: + - | + #include + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + pmic@4b { + compatible = "mps,mp2629"; + reg = <0x4b>; + + interrupt-controller; + interrupt-parent = <&gpio2>; + #interrupt-cells = <2>; + interrupts = <3 IRQ_TYPE_LEVEL_HIGH>; + }; + }; From 06081646450e46efcc1ca58c3b227bd60083cd3e Mon Sep 17 00:00:00 2001 From: Saravanan Sekar Date: Tue, 26 May 2020 11:06:42 +0200 Subject: [PATCH 10/14] mfd: mp2629: Add support for mps battery charger mp2629 is a highly-integrated switching-mode battery charge management device for single-cell Li-ion or Li-polymer battery. Add MFD core enables chip access for ADC driver for battery readings, and a power supply battery-charger driver Signed-off-by: Saravanan Sekar Reviewed-by: Andy Shevchenko Reviewed-by: Jonathan Cameron Signed-off-by: Lee Jones --- drivers/mfd/Kconfig | 9 +++++ drivers/mfd/Makefile | 2 + drivers/mfd/mp2629.c | 79 ++++++++++++++++++++++++++++++++++++++ include/linux/mfd/mp2629.h | 17 ++++++++ 4 files changed, 107 insertions(+) create mode 100644 drivers/mfd/mp2629.c create mode 100644 include/linux/mfd/mp2629.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 0a59249198d3..3526a03855be 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -434,6 +434,15 @@ config MFD_MC13XXX_I2C help Select this if your MC13xxx is connected via an I2C bus. +config MFD_MP2629 + tristate "Monolithic Power Systems MP2629 ADC and Battery charger" + depends on I2C + select REGMAP_I2C + help + Select this option to enable support for Monolithic Power Systems + battery charger. This provides ADC, thermal and battery charger power + management functions. + config MFD_MXS_LRADC tristate "Freescale i.MX23/i.MX28 LRADC" depends on ARCH_MXS || COMPILE_TEST diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index f935d10cbf0f..d6c210f96d02 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -170,6 +170,8 @@ obj-$(CONFIG_MFD_MAX8925) += max8925.o obj-$(CONFIG_MFD_MAX8997) += max8997.o max8997-irq.o obj-$(CONFIG_MFD_MAX8998) += max8998.o max8998-irq.o +obj-$(CONFIG_MFD_MP2629) += mp2629.o + pcf50633-objs := pcf50633-core.o pcf50633-irq.o obj-$(CONFIG_MFD_PCF50633) += pcf50633.o obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o diff --git a/drivers/mfd/mp2629.c b/drivers/mfd/mp2629.c new file mode 100644 index 000000000000..16840ec5fd1c --- /dev/null +++ b/drivers/mfd/mp2629.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MP2629 parent driver for ADC and battery charger + * + * Copyright 2020 Monolithic Power Systems, Inc + * + * Author: Saravanan Sekar + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct mfd_cell mp2629_cell[] = { + { + .name = "mp2629_adc", + .of_compatible = "mps,mp2629_adc", + }, + { + .name = "mp2629_charger", + .of_compatible = "mps,mp2629_charger", + } +}; + +static const struct regmap_config mp2629_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x17, +}; + +static int mp2629_probe(struct i2c_client *client) +{ + struct mp2629_data *ddata; + int ret; + + ddata = devm_kzalloc(&client->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + ddata->dev = &client->dev; + i2c_set_clientdata(client, ddata); + + ddata->regmap = devm_regmap_init_i2c(client, &mp2629_regmap_config); + if (IS_ERR(ddata->regmap)) { + dev_err(ddata->dev, "Failed to allocate regmap\n"); + return PTR_ERR(ddata->regmap); + } + + ret = devm_mfd_add_devices(ddata->dev, PLATFORM_DEVID_AUTO, mp2629_cell, + ARRAY_SIZE(mp2629_cell), NULL, 0, NULL); + if (ret) + dev_err(ddata->dev, "Failed to register sub-devices %d\n", ret); + + return ret; +} + +static const struct of_device_id mp2629_of_match[] = { + { .compatible = "mps,mp2629"}, + { } +}; +MODULE_DEVICE_TABLE(of, mp2629_of_match); + +static struct i2c_driver mp2629_driver = { + .driver = { + .name = "mp2629", + .of_match_table = mp2629_of_match, + }, + .probe_new = mp2629_probe, +}; +module_i2c_driver(mp2629_driver); + +MODULE_AUTHOR("Saravanan Sekar "); +MODULE_DESCRIPTION("MP2629 Battery charger parent driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/mp2629.h b/include/linux/mfd/mp2629.h new file mode 100644 index 000000000000..baaeeaf82949 --- /dev/null +++ b/include/linux/mfd/mp2629.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2020 Monolithic Power Systems, Inc + */ + +#ifndef __MP2629_H__ +#define __MP2629_H__ + +#include +#include + +struct mp2629_data { + struct device *dev; + struct regmap *regmap; +}; + +#endif From 7abd9fb6468225f5c7f83149ce279cc1a912a68a Mon Sep 17 00:00:00 2001 From: Saravanan Sekar Date: Tue, 26 May 2020 11:06:43 +0200 Subject: [PATCH 11/14] iio: adc: mp2629: Add support for mp2629 ADC driver Add support for 8-bit resolution ADC readings for input power supply and battery charging measurement. Provides voltage, current readings to mp2629 power supply driver. Signed-off-by: Saravanan Sekar Reviewed-by: Andy Shevchenko Reviewed-by: Jonathan Cameron Signed-off-by: Lee Jones --- drivers/iio/adc/Kconfig | 10 ++ drivers/iio/adc/Makefile | 1 + drivers/iio/adc/mp2629_adc.c | 208 +++++++++++++++++++++++++++++++++++ include/linux/mfd/mp2629.h | 9 ++ 4 files changed, 228 insertions(+) create mode 100644 drivers/iio/adc/mp2629_adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 12bb8b7ca1ff..a864ede98114 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -692,6 +692,16 @@ config MESON_SARADC To compile this driver as a module, choose M here: the module will be called meson_saradc. +config MP2629_ADC + tristate "Monolithic MP2629 ADC driver" + depends on MFD_MP2629 + help + Say yes to have support for battery charger IC MP2629 ADC device + accessed over I2C. + + This driver provides ADC conversion of system, input power supply + and battery voltage & current information. + config NAU7802 tristate "Nuvoton NAU7802 ADC driver" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 637807861112..78a1963a14f3 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_MCP3911) += mcp3911.o obj-$(CONFIG_MEDIATEK_MT6577_AUXADC) += mt6577_auxadc.o obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o obj-$(CONFIG_MESON_SARADC) += meson_saradc.o +obj-$(CONFIG_MP2629_ADC) += mp2629_adc.o obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o obj-$(CONFIG_NAU7802) += nau7802.o obj-$(CONFIG_NPCM_ADC) += npcm_adc.o diff --git a/drivers/iio/adc/mp2629_adc.c b/drivers/iio/adc/mp2629_adc.c new file mode 100644 index 000000000000..331a9a728217 --- /dev/null +++ b/drivers/iio/adc/mp2629_adc.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MP2629 Driver for ADC + * + * Copyright 2020 Monolithic Power Systems, Inc + * + * Author: Saravanan Sekar + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MP2629_REG_ADC_CTRL 0x03 +#define MP2629_REG_BATT_VOLT 0x0e +#define MP2629_REG_SYSTEM_VOLT 0x0f +#define MP2629_REG_INPUT_VOLT 0x11 +#define MP2629_REG_BATT_CURRENT 0x12 +#define MP2629_REG_INPUT_CURRENT 0x13 + +#define MP2629_ADC_START BIT(7) +#define MP2629_ADC_CONTINUOUS BIT(6) + +#define MP2629_MAP(_mp, _mpc) IIO_MAP(#_mp, "mp2629_charger", "mp2629-"_mpc) + +#define MP2629_ADC_CHAN(_ch, _type) { \ + .type = _type, \ + .indexed = 1, \ + .datasheet_name = #_ch, \ + .channel = MP2629_ ## _ch, \ + .address = MP2629_REG_ ## _ch, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +struct mp2629_adc { + struct regmap *regmap; + struct device *dev; +}; + +static struct iio_chan_spec mp2629_channels[] = { + MP2629_ADC_CHAN(BATT_VOLT, IIO_VOLTAGE), + MP2629_ADC_CHAN(SYSTEM_VOLT, IIO_VOLTAGE), + MP2629_ADC_CHAN(INPUT_VOLT, IIO_VOLTAGE), + MP2629_ADC_CHAN(BATT_CURRENT, IIO_CURRENT), + MP2629_ADC_CHAN(INPUT_CURRENT, IIO_CURRENT) +}; + +static struct iio_map mp2629_adc_maps[] = { + MP2629_MAP(BATT_VOLT, "batt-volt"), + MP2629_MAP(SYSTEM_VOLT, "system-volt"), + MP2629_MAP(INPUT_VOLT, "input-volt"), + MP2629_MAP(BATT_CURRENT, "batt-current"), + MP2629_MAP(INPUT_CURRENT, "input-current") +}; + +static int mp2629_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mp2629_adc *info = iio_priv(indio_dev); + unsigned int rval; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(info->regmap, chan->address, &rval); + if (ret) + return ret; + + if (chan->address == MP2629_INPUT_VOLT) + rval &= GENMASK(6, 0); + *val = rval; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->channel) { + case MP2629_BATT_VOLT: + case MP2629_SYSTEM_VOLT: + *val = 20; + return IIO_VAL_INT; + + case MP2629_INPUT_VOLT: + *val = 60; + return IIO_VAL_INT; + + case MP2629_BATT_CURRENT: + *val = 175; + *val2 = 10; + return IIO_VAL_FRACTIONAL; + + case MP2629_INPUT_CURRENT: + *val = 133; + *val2 = 10; + return IIO_VAL_FRACTIONAL; + + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static const struct iio_info mp2629_adc_info = { + .read_raw = &mp2629_read_raw, +}; + +static int mp2629_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mp2629_data *ddata = dev_get_drvdata(dev->parent); + struct mp2629_adc *info; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + info->regmap = ddata->regmap; + info->dev = dev; + platform_set_drvdata(pdev, indio_dev); + + ret = regmap_update_bits(info->regmap, MP2629_REG_ADC_CTRL, + MP2629_ADC_START | MP2629_ADC_CONTINUOUS, + MP2629_ADC_START | MP2629_ADC_CONTINUOUS); + if (ret) { + dev_err(dev, "adc enable fail: %d\n", ret); + return ret; + } + + ret = iio_map_array_register(indio_dev, mp2629_adc_maps); + if (ret) { + dev_err(dev, "IIO maps register fail: %d\n", ret); + goto fail_disable; + } + + indio_dev->name = "mp2629-adc"; + indio_dev->dev.parent = dev; + indio_dev->channels = mp2629_channels; + indio_dev->num_channels = ARRAY_SIZE(mp2629_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mp2629_adc_info; + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "IIO device register fail: %d\n", ret); + goto fail_map_unregister; + } + + return 0; + +fail_map_unregister: + iio_map_array_unregister(indio_dev); + +fail_disable: + regmap_update_bits(info->regmap, MP2629_REG_ADC_CTRL, + MP2629_ADC_CONTINUOUS, 0); + regmap_update_bits(info->regmap, MP2629_REG_ADC_CTRL, + MP2629_ADC_START, 0); + + return ret; +} + +static int mp2629_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct mp2629_adc *info = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + iio_map_array_unregister(indio_dev); + + regmap_update_bits(info->regmap, MP2629_REG_ADC_CTRL, + MP2629_ADC_CONTINUOUS, 0); + regmap_update_bits(info->regmap, MP2629_REG_ADC_CTRL, + MP2629_ADC_START, 0); + + return 0; +} + +static const struct of_device_id mp2629_adc_of_match[] = { + { .compatible = "mps,mp2629_adc"}, + {} +}; +MODULE_DEVICE_TABLE(of, mp2629_adc_of_match); + +static struct platform_driver mp2629_adc_driver = { + .driver = { + .name = "mp2629_adc", + .of_match_table = mp2629_adc_of_match, + }, + .probe = mp2629_adc_probe, + .remove = mp2629_adc_remove, +}; +module_platform_driver(mp2629_adc_driver); + +MODULE_AUTHOR("Saravanan Sekar "); +MODULE_DESCRIPTION("MP2629 ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/mp2629.h b/include/linux/mfd/mp2629.h index baaeeaf82949..89b706900b57 100644 --- a/include/linux/mfd/mp2629.h +++ b/include/linux/mfd/mp2629.h @@ -14,4 +14,13 @@ struct mp2629_data { struct regmap *regmap; }; +enum mp2629_adc_chan { + MP2629_BATT_VOLT, + MP2629_SYSTEM_VOLT, + MP2629_INPUT_VOLT, + MP2629_BATT_CURRENT, + MP2629_INPUT_CURRENT, + MP2629_ADC_CHAN_END +}; + #endif From 3bc6d790c39dfc4539c36525e6bcb617abbae467 Mon Sep 17 00:00:00 2001 From: Saravanan Sekar Date: Tue, 26 May 2020 11:06:44 +0200 Subject: [PATCH 12/14] power: supply: Add support for mps mp2629 battery charger The mp2629 provides switching-mode battery charge management for single-cell Li-ion or Li-polymer battery. Driver supports the access/control input source and battery charging parameters. Signed-off-by: Saravanan Sekar Reviewed-by: Andy Shevchenko Reviewed-by: Sebastian Reichel Signed-off-by: Lee Jones --- drivers/power/supply/Kconfig | 10 + drivers/power/supply/Makefile | 1 + drivers/power/supply/mp2629_charger.c | 669 ++++++++++++++++++++++++++ 3 files changed, 680 insertions(+) create mode 100644 drivers/power/supply/mp2629_charger.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index f3424fdce341..a46643f5e2fb 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -541,6 +541,16 @@ config CHARGER_MAX8998 Say Y to enable support for the battery charger control sysfs and platform data of MAX8998/LP3974 PMICs. +config CHARGER_MP2629 + tristate "Monolithic power system MP2629 Battery charger" + depends on MFD_MP2629 + depends on MP2629_ADC + depends on IIO + help + Select this option to enable support for Monolithic power system + Battery charger. This driver provides Battery charger power management + functions on the systems. + config CHARGER_QCOM_SMBB tristate "Qualcomm Switch-Mode Battery Charger and Boost" depends on MFD_SPMI_PMIC || COMPILE_TEST diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 6c7da920ea83..41cb64f09e49 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o +obj-$(CONFIG_CHARGER_MP2629) += mp2629_charger.o obj-$(CONFIG_CHARGER_QCOM_SMBB) += qcom_smbb.o obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o diff --git a/drivers/power/supply/mp2629_charger.c b/drivers/power/supply/mp2629_charger.c new file mode 100644 index 000000000000..bdf924b73e47 --- /dev/null +++ b/drivers/power/supply/mp2629_charger.c @@ -0,0 +1,669 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MP2629 battery charger driver + * + * Copyright 2020 Monolithic Power Systems, Inc + * + * Author: Saravanan Sekar + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MP2629_REG_INPUT_ILIM 0x00 +#define MP2629_REG_INPUT_VLIM 0x01 +#define MP2629_REG_CHARGE_CTRL 0x04 +#define MP2629_REG_CHARGE_ILIM 0x05 +#define MP2629_REG_PRECHARGE 0x06 +#define MP2629_REG_TERM_CURRENT 0x06 +#define MP2629_REG_CHARGE_VLIM 0x07 +#define MP2629_REG_TIMER_CTRL 0x08 +#define MP2629_REG_IMPEDANCE_COMP 0x09 +#define MP2629_REG_INTERRUPT 0x0b +#define MP2629_REG_STATUS 0x0c +#define MP2629_REG_FAULT 0x0d + +#define MP2629_MASK_INPUT_TYPE GENMASK(7, 5) +#define MP2629_MASK_CHARGE_TYPE GENMASK(4, 3) +#define MP2629_MASK_CHARGE_CTRL GENMASK(5, 4) +#define MP2629_MASK_WDOG_CTRL GENMASK(5, 4) +#define MP2629_MASK_IMPEDANCE GENMASK(7, 4) + +#define MP2629_INPUTSOURCE_CHANGE GENMASK(7, 5) +#define MP2629_CHARGING_CHANGE GENMASK(4, 3) +#define MP2629_FAULT_BATTERY BIT(3) +#define MP2629_FAULT_THERMAL BIT(4) +#define MP2629_FAULT_INPUT BIT(5) +#define MP2629_FAULT_OTG BIT(6) + +#define MP2629_MAX_BATT_CAPACITY 100 + +#define MP2629_PROPS(_idx, _min, _max, _step) \ + [_idx] = { \ + .min = _min, \ + .max = _max, \ + .step = _step, \ +} + +enum mp2629_source_type { + MP2629_SOURCE_TYPE_NO_INPUT, + MP2629_SOURCE_TYPE_NON_STD, + MP2629_SOURCE_TYPE_SDP, + MP2629_SOURCE_TYPE_CDP, + MP2629_SOURCE_TYPE_DCP, + MP2629_SOURCE_TYPE_OTG = 7, +}; + +enum mp2629_field { + INPUT_ILIM, + INPUT_VLIM, + CHARGE_ILIM, + CHARGE_VLIM, + PRECHARGE, + TERM_CURRENT, + MP2629_MAX_FIELD +}; + +struct mp2629_charger { + struct device *dev; + int status; + int fault; + + struct regmap *regmap; + struct regmap_field *regmap_fields[MP2629_MAX_FIELD]; + struct mutex lock; + struct power_supply *usb; + struct power_supply *battery; + struct iio_channel *iiochan[MP2629_ADC_CHAN_END]; +}; + +struct mp2629_prop { + int reg; + int mask; + int min; + int max; + int step; + int shift; +}; + +static enum power_supply_usb_type mp2629_usb_types[] = { + POWER_SUPPLY_USB_TYPE_SDP, + POWER_SUPPLY_USB_TYPE_DCP, + POWER_SUPPLY_USB_TYPE_CDP, + POWER_SUPPLY_USB_TYPE_PD_DRP, + POWER_SUPPLY_USB_TYPE_UNKNOWN +}; + +static enum power_supply_property mp2629_charger_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, +}; + +static enum power_supply_property mp2629_charger_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_PRECHARGE_CURRENT, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, +}; + +static struct mp2629_prop props[] = { + MP2629_PROPS(INPUT_ILIM, 100000, 3250000, 50000), + MP2629_PROPS(INPUT_VLIM, 3800000, 5300000, 100000), + MP2629_PROPS(CHARGE_ILIM, 320000, 4520000, 40000), + MP2629_PROPS(CHARGE_VLIM, 3400000, 4670000, 10000), + MP2629_PROPS(PRECHARGE, 120000, 720000, 40000), + MP2629_PROPS(TERM_CURRENT, 80000, 680000, 40000), +}; + +static const struct reg_field mp2629_reg_fields[] = { + [INPUT_ILIM] = REG_FIELD(MP2629_REG_INPUT_ILIM, 0, 5), + [INPUT_VLIM] = REG_FIELD(MP2629_REG_INPUT_VLIM, 0, 3), + [CHARGE_ILIM] = REG_FIELD(MP2629_REG_CHARGE_ILIM, 0, 6), + [CHARGE_VLIM] = REG_FIELD(MP2629_REG_CHARGE_VLIM, 1, 7), + [PRECHARGE] = REG_FIELD(MP2629_REG_PRECHARGE, 4, 7), + [TERM_CURRENT] = REG_FIELD(MP2629_REG_TERM_CURRENT, 0, 3), +}; + +static char *adc_chan_name[] = { + "mp2629-batt-volt", + "mp2629-system-volt", + "mp2629-input-volt", + "mp2629-batt-current", + "mp2629-input-current", +}; + +static int mp2629_read_adc(struct mp2629_charger *charger, + enum mp2629_adc_chan ch, + union power_supply_propval *val) +{ + int ret; + int chval; + + ret = iio_read_channel_processed(charger->iiochan[ch], &chval); + if (ret) + return ret; + + val->intval = chval * 1000; + + return 0; +} + +static int mp2629_get_prop(struct mp2629_charger *charger, + enum mp2629_field fld, + union power_supply_propval *val) +{ + int ret; + unsigned int rval; + + ret = regmap_field_read(charger->regmap_fields[fld], &rval); + if (ret) + return ret; + + val->intval = rval * props[fld].step + props[fld].min; + + return 0; +} + +static int mp2629_set_prop(struct mp2629_charger *charger, + enum mp2629_field fld, + const union power_supply_propval *val) +{ + unsigned int rval; + + if (val->intval < props[fld].min || val->intval > props[fld].max) + return -EINVAL; + + rval = (val->intval - props[fld].min) / props[fld].step; + return regmap_field_write(charger->regmap_fields[fld], rval); +} + +static int mp2629_get_battery_capacity(struct mp2629_charger *charger, + union power_supply_propval *val) +{ + union power_supply_propval vnow, vlim; + int ret; + + ret = mp2629_read_adc(charger, MP2629_BATT_VOLT, &vnow); + if (ret) + return ret; + + ret = mp2629_get_prop(charger, CHARGE_VLIM, &vlim); + if (ret) + return ret; + + val->intval = (vnow.intval * 100) / vlim.intval; + val->intval = min(val->intval, MP2629_MAX_BATT_CAPACITY); + + return 0; +} + +static int mp2629_charger_battery_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mp2629_charger *charger = dev_get_drvdata(psy->dev.parent); + unsigned int rval; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = mp2629_read_adc(charger, MP2629_BATT_VOLT, val); + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = mp2629_read_adc(charger, MP2629_BATT_CURRENT, val); + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = 4520000; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = 4670000; + break; + + case POWER_SUPPLY_PROP_CAPACITY: + ret = mp2629_get_battery_capacity(charger, val); + break; + + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + ret = mp2629_get_prop(charger, TERM_CURRENT, val); + break; + + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + ret = mp2629_get_prop(charger, PRECHARGE, val); + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = mp2629_get_prop(charger, CHARGE_VLIM, val); + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = mp2629_get_prop(charger, CHARGE_ILIM, val); + break; + + case POWER_SUPPLY_PROP_HEALTH: + if (!charger->fault) + val->intval = POWER_SUPPLY_HEALTH_GOOD; + if (MP2629_FAULT_BATTERY & charger->fault) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else if (MP2629_FAULT_THERMAL & charger->fault) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (MP2629_FAULT_INPUT & charger->fault) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + + case POWER_SUPPLY_PROP_STATUS: + ret = regmap_read(charger->regmap, MP2629_REG_STATUS, &rval); + if (ret) + break; + + rval = (rval & MP2629_MASK_CHARGE_TYPE) >> 3; + switch (rval) { + case 0x00: + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case 0x01: + case 0x10: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case 0x11: + val->intval = POWER_SUPPLY_STATUS_FULL; + } + break; + + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = regmap_read(charger->regmap, MP2629_REG_STATUS, &rval); + if (ret) + break; + + rval = (rval & MP2629_MASK_CHARGE_TYPE) >> 3; + switch (rval) { + case 0x00: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + case 0x01: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case 0x10: + val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD; + break; + default: + val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + break; + + default: + return -EINVAL; + } + + return ret; +} + +static int mp2629_charger_battery_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct mp2629_charger *charger = dev_get_drvdata(psy->dev.parent); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + return mp2629_set_prop(charger, TERM_CURRENT, val); + + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + return mp2629_set_prop(charger, PRECHARGE, val); + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + return mp2629_set_prop(charger, CHARGE_VLIM, val); + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + return mp2629_set_prop(charger, CHARGE_ILIM, val); + + default: + return -EINVAL; + } +} + +static int mp2629_charger_usb_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mp2629_charger *charger = dev_get_drvdata(psy->dev.parent); + unsigned int rval; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = regmap_read(charger->regmap, MP2629_REG_STATUS, &rval); + if (ret) + break; + + val->intval = !!(rval & MP2629_MASK_INPUT_TYPE); + break; + + case POWER_SUPPLY_PROP_USB_TYPE: + ret = regmap_read(charger->regmap, MP2629_REG_STATUS, &rval); + if (ret) + break; + + rval = (rval & MP2629_MASK_INPUT_TYPE) >> 5; + switch (rval) { + case MP2629_SOURCE_TYPE_SDP: + val->intval = POWER_SUPPLY_USB_TYPE_SDP; + break; + case MP2629_SOURCE_TYPE_CDP: + val->intval = POWER_SUPPLY_USB_TYPE_CDP; + break; + case MP2629_SOURCE_TYPE_DCP: + val->intval = POWER_SUPPLY_USB_TYPE_DCP; + break; + case MP2629_SOURCE_TYPE_OTG: + val->intval = POWER_SUPPLY_USB_TYPE_PD_DRP; + break; + default: + val->intval = POWER_SUPPLY_USB_TYPE_UNKNOWN; + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = mp2629_read_adc(charger, MP2629_INPUT_VOLT, val); + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = mp2629_read_adc(charger, MP2629_INPUT_CURRENT, val); + break; + + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + ret = mp2629_get_prop(charger, INPUT_VLIM, val); + break; + + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = mp2629_get_prop(charger, INPUT_ILIM, val); + break; + + default: + return -EINVAL; + } + + return ret; +} + +static int mp2629_charger_usb_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct mp2629_charger *charger = dev_get_drvdata(psy->dev.parent); + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + return mp2629_set_prop(charger, INPUT_VLIM, val); + + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return mp2629_set_prop(charger, INPUT_ILIM, val); + + default: + return -EINVAL; + } +} + +static int mp2629_charger_battery_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + return (psp == POWER_SUPPLY_PROP_PRECHARGE_CURRENT) || + (psp == POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT) || + (psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT) || + (psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE); +} + +static int mp2629_charger_usb_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + return (psp == POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT) || + (psp == POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT); +} + +static irqreturn_t mp2629_irq_handler(int irq, void *dev_id) +{ + struct mp2629_charger *charger = dev_id; + unsigned int rval; + int ret; + + mutex_lock(&charger->lock); + + ret = regmap_read(charger->regmap, MP2629_REG_FAULT, &rval); + if (ret) + goto unlock; + + if (rval) { + charger->fault = rval; + if (MP2629_FAULT_BATTERY & rval) + dev_err(charger->dev, "Battery fault OVP\n"); + else if (MP2629_FAULT_THERMAL & rval) + dev_err(charger->dev, "Thermal shutdown fault\n"); + else if (MP2629_FAULT_INPUT & rval) + dev_err(charger->dev, "no input or input OVP\n"); + else if (MP2629_FAULT_OTG & rval) + dev_err(charger->dev, "VIN overloaded\n"); + + goto unlock; + } + + ret = regmap_read(charger->regmap, MP2629_REG_STATUS, &rval); + if (ret) + goto unlock; + + if (rval & MP2629_INPUTSOURCE_CHANGE) + power_supply_changed(charger->usb); + else if (rval & MP2629_CHARGING_CHANGE) + power_supply_changed(charger->battery); + +unlock: + mutex_unlock(&charger->lock); + + return IRQ_HANDLED; +} + +static const struct power_supply_desc mp2629_usb_desc = { + .name = "mp2629_usb", + .type = POWER_SUPPLY_TYPE_USB, + .usb_types = mp2629_usb_types, + .num_usb_types = ARRAY_SIZE(mp2629_usb_types), + .properties = mp2629_charger_usb_props, + .num_properties = ARRAY_SIZE(mp2629_charger_usb_props), + .get_property = mp2629_charger_usb_get_prop, + .set_property = mp2629_charger_usb_set_prop, + .property_is_writeable = mp2629_charger_usb_prop_writeable, +}; + +static const struct power_supply_desc mp2629_battery_desc = { + .name = "mp2629_battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = mp2629_charger_bat_props, + .num_properties = ARRAY_SIZE(mp2629_charger_bat_props), + .get_property = mp2629_charger_battery_get_prop, + .set_property = mp2629_charger_battery_set_prop, + .property_is_writeable = mp2629_charger_battery_prop_writeable, +}; + +static ssize_t batt_impedance_compensation_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct mp2629_charger *charger = dev_get_drvdata(dev->parent); + unsigned int rval; + int ret; + + ret = regmap_read(charger->regmap, MP2629_REG_IMPEDANCE_COMP, &rval); + if (ret) + return ret; + + rval = (rval >> 4) * 10; + return sprintf(buf, "%d mohm\n", rval); +} + +static ssize_t batt_impedance_compensation_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct mp2629_charger *charger = dev_get_drvdata(dev->parent); + unsigned int val; + int ret; + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + + if (val > 140) + return -ERANGE; + + /* multiples of 10 mohm so round off */ + val = val / 10; + ret = regmap_update_bits(charger->regmap, MP2629_REG_IMPEDANCE_COMP, + MP2629_MASK_IMPEDANCE, val << 4); + if (ret) + return ret; + + return count; +} + +static DEVICE_ATTR_RW(batt_impedance_compensation); + +static struct attribute *mp2629_charger_sysfs_attrs[] = { + &dev_attr_batt_impedance_compensation.attr, + NULL +}; +ATTRIBUTE_GROUPS(mp2629_charger_sysfs); + +static void mp2629_charger_disable(void *data) +{ + struct mp2629_charger *charger = data; + + regmap_update_bits(charger->regmap, MP2629_REG_CHARGE_CTRL, + MP2629_MASK_CHARGE_CTRL, 0); +} + +static int mp2629_charger_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mp2629_data *ddata = dev_get_drvdata(dev->parent); + struct mp2629_charger *charger; + struct power_supply_config psy_cfg = {}; + int ret, i, irq; + + charger = devm_kzalloc(dev, sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + charger->regmap = ddata->regmap; + charger->dev = dev; + platform_set_drvdata(pdev, charger); + + irq = platform_get_irq_optional(to_platform_device(dev->parent), 0); + if (irq < 0) { + dev_err(dev, "get irq fail: %d\n", irq); + return irq; + } + + for (i = 0; i < MP2629_MAX_FIELD; i++) { + charger->regmap_fields[i] = devm_regmap_field_alloc(dev, + charger->regmap, mp2629_reg_fields[i]); + if (IS_ERR(charger->regmap_fields[i])) { + dev_err(dev, "regmap field alloc fail %d\n", i); + return PTR_ERR(charger->regmap_fields[i]); + } + } + + for (i = 0; i < MP2629_ADC_CHAN_END; i++) { + charger->iiochan[i] = devm_iio_channel_get(dev, + adc_chan_name[i]); + if (IS_ERR(charger->iiochan[i])) { + dev_err(dev, "iio chan get %s err\n", adc_chan_name[i]); + return PTR_ERR(charger->iiochan[i]); + } + } + + ret = devm_add_action_or_reset(dev, mp2629_charger_disable, charger); + if (ret) + return ret; + + charger->usb = devm_power_supply_register(dev, &mp2629_usb_desc, NULL); + if (IS_ERR(charger->usb)) { + dev_err(dev, "power supply register usb failed\n"); + return PTR_ERR(charger->usb); + } + + psy_cfg.drv_data = charger; + psy_cfg.attr_grp = mp2629_charger_sysfs_groups; + charger->battery = devm_power_supply_register(dev, + &mp2629_battery_desc, &psy_cfg); + if (IS_ERR(charger->battery)) { + dev_err(dev, "power supply register battery failed\n"); + return PTR_ERR(charger->battery); + } + + ret = regmap_update_bits(charger->regmap, MP2629_REG_CHARGE_CTRL, + MP2629_MASK_CHARGE_CTRL, BIT(4)); + if (ret) { + dev_err(dev, "enable charge fail: %d\n", ret); + return ret; + } + + regmap_update_bits(charger->regmap, MP2629_REG_TIMER_CTRL, + MP2629_MASK_WDOG_CTRL, 0); + + mutex_init(&charger->lock); + + ret = devm_request_threaded_irq(dev, irq, NULL, mp2629_irq_handler, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "mp2629-charger", charger); + if (ret) { + dev_err(dev, "failed to request gpio IRQ\n"); + return ret; + } + + regmap_update_bits(charger->regmap, MP2629_REG_INTERRUPT, + GENMASK(6, 5), BIT(6) | BIT(5)); + + return 0; +} + +static const struct of_device_id mp2629_charger_of_match[] = { + { .compatible = "mps,mp2629_charger"}, + {} +}; +MODULE_DEVICE_TABLE(of, mp2629_charger_of_match); + +static struct platform_driver mp2629_charger_driver = { + .driver = { + .name = "mp2629_charger", + .of_match_table = mp2629_charger_of_match, + }, + .probe = mp2629_charger_probe, +}; +module_platform_driver(mp2629_charger_driver); + +MODULE_AUTHOR("Saravanan Sekar "); +MODULE_DESCRIPTION("MP2629 Charger driver"); +MODULE_LICENSE("GPL"); From fe27f04e0cdc841e3a0b2387b27bf57ef99c5740 Mon Sep 17 00:00:00 2001 From: Saravanan Sekar Date: Tue, 26 May 2020 11:06:45 +0200 Subject: [PATCH 13/14] power: supply: mp2629: Add impedance compensation config Allows the user to compensate the intrinsic resistance of the battery to accelerate the charging cycle. Signed-off-by: Saravanan Sekar Reviewed-by: Andy Shevchenko Reviewed-by: Sebastian Reichel Signed-off-by: Lee Jones --- Documentation/ABI/testing/sysfs-class-power-mp2629 | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-power-mp2629 diff --git a/Documentation/ABI/testing/sysfs-class-power-mp2629 b/Documentation/ABI/testing/sysfs-class-power-mp2629 new file mode 100644 index 000000000000..327a07e22805 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-power-mp2629 @@ -0,0 +1,8 @@ +What: /sys/class/power_supply/mp2629_battery/batt_impedance_compen +Date: April 2020 +KernelVersion: 5.7 +Description: + Represents a battery impedance compensation to accelerate charging. + + Access: Read, Write + Valid values: Represented in milli-ohms. Valid range is [0, 140]. From 904ac71f4b0c1c26ec47ff597cb3d3c7d36e618d Mon Sep 17 00:00:00 2001 From: Saravanan Sekar Date: Tue, 26 May 2020 11:06:46 +0200 Subject: [PATCH 14/14] MAINTAINERS: Add entry for mp2629 Battery Charger driver Add MAINTAINERS entry for Monolithic Power Systems mp2629 Charger driver. Signed-off-by: Saravanan Sekar Reviewed-by: Andy Shevchenko Reviewed-by: Sebastian Reichel Signed-off-by: Lee Jones --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index e64e5db31497..ecf734eee786 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11377,10 +11377,15 @@ F: kernel/module.c MONOLITHIC POWER SYSTEM PMIC DRIVER M: Saravanan Sekar S: Maintained +F: Documentation/devicetree/bindings/mfd/mps,mp2629.yaml F: Documentation/devicetree/bindings/regulator/mps,mp*.yaml +F: drivers/iio/adc/mp2629_adc.c +F: drivers/mfd/mp2629.c +F: drivers/power/supply/mp2629_charger.c F: drivers/regulator/mp5416.c F: drivers/regulator/mpq7920.c F: drivers/regulator/mpq7920.h +F: include/linux/mfd/mp2629.h MOTION EYE VAIO PICTUREBOOK CAMERA DRIVER S: Orphan