diff options
Diffstat (limited to 'drivers/power/supply/rt9756.c')
| -rw-r--r-- | drivers/power/supply/rt9756.c | 955 |
1 files changed, 955 insertions, 0 deletions
diff --git a/drivers/power/supply/rt9756.c b/drivers/power/supply/rt9756.c new file mode 100644 index 000000000000..f254527be653 --- /dev/null +++ b/drivers/power/supply/rt9756.c @@ -0,0 +1,955 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright (C) 2025 Richtek Technology Corp. +// +// Authors: ChiYuan Huang <cy_huang@richtek.com> + +#include <linux/atomic.h> +#include <linux/cleanup.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/linear_range.h> +#include <linux/interrupt.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/power_supply.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/sysfs.h> +#include <linux/util_macros.h> + +#define RT9756_REG_INTFLAG1 0x0B +#define RT9756_REG_INTFLAG2 0x0D +#define RT9756_REG_INTFLAG3 0x0F +#define RT9756_REG_ADCCTL 0x11 +#define RT9756_REG_VBUSADC 0x12 +#define RT9756_REG_BC12FLAG 0x45 +#define RT9756_REG_INTFLAG4 0x49 + +/* Flag1 */ +#define RT9756_EVT_BUSOVP BIT(3) +#define RT9756_EVT_BUSOCP BIT(2) +#define RT9756_EVT_BUSUCP BIT(0) +/* Flag2 */ +#define RT9756_EVT_BATOVP BIT(7) +#define RT9756_EVT_BATOCP BIT(6) +#define RT9756_EVT_TDIEOTP BIT(3) +#define RT9756_EVT_VBUSLOW_ERR BIT(2) +#define RT9756_EVT_VAC_INSERT BIT(0) +/* Flag3 */ +#define RT9756_EVT_WDT BIT(5) +#define RT9756_EVT_VAC_UVLO BIT(4) +/* ADCCTL */ +#define RT9756_ADCEN_MASK BIT(7) +#define RT9756_ADCONCE_MASK BIT(6) +/* Bc12_flag */ +#define RT9756_EVT_BC12_DONE BIT(3) +/* Flag4 */ +#define RT9756_EVT_OUTOVP BIT(0) + +#define RICHTEK_DEVID 7 +#define RT9756_REVID 0 +#define RT9756A_REVID 1 +#define RT9757_REVID 2 +#define RT9757A_REVID 3 +#define RT9756_ADC_CONVTIME 1200 +#define RT9756_ADC_MAXWAIT 16000 + +enum rt9756_model { + MODEL_RT9756 = 0, + MODEL_RT9757, + MODEL_RT9770, + MODEL_MAX +}; + +enum rt9756_adc_chan { + ADC_VBUS = 0, + ADC_IBUS, + ADC_VBAT, + ADC_IBAT, + ADC_TDIE, + ADC_MAX_CHANNEL +}; + +enum rt9756_usb_type { + USB_NO_VBUS = 0, + USB_SDP = 2, + USB_NSTD, + USB_DCP, + USB_CDP, + MAX_USB_TYPE +}; + +enum rt9756_fields { + F_VBATOVP = 0, + F_VBATOVP_EN, + F_IBATOCP, + F_IBATOCP_EN, + F_VBUSOVP, + F_VBUSOVP_EN, + F_IBUSOCP, + F_IBUSOCP_EN, + F_SWITCHING, + F_REG_RST, + F_CHG_EN, + F_OP_MODE, + F_WDT_DIS, + F_WDT_TMR, + F_DEV_ID, + F_BC12_EN, + F_USB_STATE, + F_VBUS_STATE, + F_IBAT_RSEN, + F_REVISION, + F_MAX_FIELD +}; + +enum rt9756_ranges { + R_VBATOVP = 0, + R_IBATOCP, + R_VBUSOVP, + R_IBUSOCP, + R_MAX_RANGE +}; + +static const struct reg_field rt9756_chg_fields[F_MAX_FIELD] = { + [F_VBATOVP] = REG_FIELD(0x08, 0, 4), + [F_VBATOVP_EN] = REG_FIELD(0x08, 7, 7), + [F_IBATOCP] = REG_FIELD(0x09, 0, 5), + [F_IBATOCP_EN] = REG_FIELD(0x09, 7, 7), + [F_VBUSOVP] = REG_FIELD(0x06, 0, 5), + [F_VBUSOVP_EN] = REG_FIELD(0x06, 7, 7), + [F_IBUSOCP] = REG_FIELD(0x07, 0, 4), + [F_IBUSOCP_EN] = REG_FIELD(0x07, 5, 5), + [F_SWITCHING] = REG_FIELD(0x5c, 7, 7), + [F_REG_RST] = REG_FIELD(0x00, 7, 7), + [F_CHG_EN] = REG_FIELD(0x00, 6, 6), + [F_OP_MODE] = REG_FIELD(0x00, 5, 5), + [F_WDT_DIS] = REG_FIELD(0x00, 3, 3), + [F_WDT_TMR] = REG_FIELD(0x00, 0, 2), + [F_DEV_ID] = REG_FIELD(0x03, 0, 3), + [F_BC12_EN] = REG_FIELD(0x44, 7, 7), + [F_USB_STATE] = REG_FIELD(0x46, 5, 7), + [F_VBUS_STATE] = REG_FIELD(0x4c, 0, 0), + [F_IBAT_RSEN] = REG_FIELD(0x5e, 0, 1), + [F_REVISION] = REG_FIELD(0x62, 0, 1), +}; + +static const struct reg_field rt9770_chg_fields[F_MAX_FIELD] = { + [F_VBATOVP] = REG_FIELD(0x08, 0, 4), + [F_VBATOVP_EN] = REG_FIELD(0x08, 7, 7), + [F_IBATOCP] = REG_FIELD(0x09, 0, 5), + [F_IBATOCP_EN] = REG_FIELD(0x09, 7, 7), + [F_VBUSOVP] = REG_FIELD(0x06, 0, 5), + [F_VBUSOVP_EN] = REG_FIELD(0x06, 7, 7), + [F_IBUSOCP] = REG_FIELD(0x07, 0, 4), + [F_IBUSOCP_EN] = REG_FIELD(0x07, 5, 5), + [F_SWITCHING] = REG_FIELD(0x5c, 7, 7), + [F_REG_RST] = REG_FIELD(0x00, 7, 7), + [F_CHG_EN] = REG_FIELD(0x00, 6, 6), + [F_OP_MODE] = REG_FIELD(0x00, 5, 5), + [F_WDT_DIS] = REG_FIELD(0x00, 3, 3), + [F_WDT_TMR] = REG_FIELD(0x00, 0, 2), + [F_DEV_ID] = REG_FIELD(0x60, 0, 3), + [F_BC12_EN] = REG_FIELD(0x03, 7, 7), + [F_USB_STATE] = REG_FIELD(0x02, 5, 7), + [F_VBUS_STATE] = REG_FIELD(0x4c, 0, 0), + [F_IBAT_RSEN] = REG_FIELD(0x5e, 0, 1), + [F_REVISION] = REG_FIELD(0x62, 3, 7), +}; + +/* All converted to microvolt or microamp */ +static const struct linear_range rt9756_chg_ranges[R_MAX_RANGE] = { + LINEAR_RANGE_IDX(R_VBATOVP, 4200000, 0, 31, 25000), + LINEAR_RANGE_IDX(R_IBATOCP, 2000000, 0, 63, 100000), + LINEAR_RANGE_IDX(R_VBUSOVP, 3000000, 0, 63, 50000), + LINEAR_RANGE_IDX(R_IBUSOCP, 1000000, 0, 31, 250000), +}; + +struct charger_event { + unsigned int flag1; + unsigned int flag2; + unsigned int flag3; + unsigned int flag4; +}; + +struct rt9756_data { + struct device *dev; + struct regmap *regmap; + struct regmap_field *rm_fields[F_MAX_FIELD]; + struct power_supply *psy; + struct power_supply *bat_psy; + struct mutex adc_lock; + struct power_supply_desc psy_desc; + struct power_supply_desc bat_psy_desc; + struct charger_event chg_evt; + unsigned int rg_resistor; + unsigned int real_resistor; + enum rt9756_model model; + atomic_t usb_type; +}; + +struct rt975x_dev_data { + const struct regmap_config *regmap_config; + const struct reg_field *reg_fields; + const struct reg_sequence *init_regs; + size_t num_init_regs; + int (*check_device_model)(struct rt9756_data *data); +}; + +static int rt9756_get_value_field_range(struct rt9756_data *data, enum rt9756_fields en_field, + enum rt9756_fields field, enum rt9756_ranges rsel, int *val) +{ + const struct linear_range *range = rt9756_chg_ranges + rsel; + unsigned int enable, selector, value; + int ret; + + ret = regmap_field_read(data->rm_fields[en_field], &enable); + if (ret) + return ret; + + if (!enable) { + *val = 0; + return 0; + } + + ret = regmap_field_read(data->rm_fields[field], &selector); + if (ret) + return ret; + + ret = linear_range_get_value(range, selector, &value); + if (ret) + return ret; + + *val = (int)value; + + return 0; +} + +static int rt9756_set_value_field_range(struct rt9756_data *data, enum rt9756_fields en_field, + enum rt9756_fields field, enum rt9756_ranges rsel, int val) +{ + const struct linear_range *range = rt9756_chg_ranges + rsel; + unsigned int selector, value; + int ret; + + if (!val) + return regmap_field_write(data->rm_fields[en_field], 0); + + value = (unsigned int)val; + linear_range_get_selector_within(range, value, &selector); + ret = regmap_field_write(data->rm_fields[field], selector); + if (ret) + return ret; + + return regmap_field_write(data->rm_fields[en_field], 1); +} + +static int rt9756_get_adc(struct rt9756_data *data, enum rt9756_adc_chan chan, + int *val) +{ + struct regmap *regmap = data->regmap; + unsigned int reg_addr = RT9756_REG_VBUSADC + chan * 2; + unsigned int mask = RT9756_ADCEN_MASK | RT9756_ADCONCE_MASK; + unsigned int shift = 0, adc_cntl; + __be16 raws; + int scale, offset = 0, ret; + + guard(mutex)(&data->adc_lock); + + ret = regmap_update_bits(regmap, RT9756_REG_ADCCTL, mask, mask); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(regmap, RT9756_REG_ADCCTL, adc_cntl, + !(adc_cntl & RT9756_ADCEN_MASK), + RT9756_ADC_CONVTIME, RT9756_ADC_MAXWAIT); + if (ret && ret != -ETIMEDOUT) + return ret; + + ret = regmap_raw_read(regmap, reg_addr, &raws, sizeof(raws)); + if (ret) + return ret; + + /* + * TDIE LSB 1'c, others LSB 1000uV or 1000uA. + * Rsense ratio is needed for IBAT channel + */ + if (chan == ADC_TDIE) { + scale = 10; + shift = 8; + offset = -40; + } else if (chan == ADC_IBAT) + scale = 1000 * data->rg_resistor / data->real_resistor; + else + scale = 1000; + + *val = ((be16_to_cpu(raws) >> shift) + offset) * scale; + + return regmap_update_bits(regmap, RT9756_REG_ADCCTL, mask, 0); +} + +static int rt9756_get_switching_state(struct rt9756_data *data, int *status) +{ + unsigned int switching_state; + int ret; + + ret = regmap_field_read(data->rm_fields[F_SWITCHING], &switching_state); + if (ret) + return ret; + + if (switching_state) + *status = POWER_SUPPLY_STATUS_CHARGING; + else + *status = POWER_SUPPLY_STATUS_NOT_CHARGING; + + return 0; +} + +static int rt9756_get_charger_health(struct rt9756_data *data) +{ + struct charger_event *evt = &data->chg_evt; + + if (evt->flag2 & RT9756_EVT_VBUSLOW_ERR) + return POWER_SUPPLY_HEALTH_UNDERVOLTAGE; + + if (evt->flag1 & RT9756_EVT_BUSOVP || evt->flag2 & RT9756_EVT_BATOVP || + evt->flag4 & RT9756_EVT_OUTOVP) + return POWER_SUPPLY_HEALTH_OVERVOLTAGE; + + if (evt->flag1 & RT9756_EVT_BUSOCP || evt->flag2 & RT9756_EVT_BATOCP) + return POWER_SUPPLY_HEALTH_OVERCURRENT; + + if (evt->flag1 & RT9756_EVT_BUSUCP) + return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + + if (evt->flag2 & RT9756_EVT_TDIEOTP) + return POWER_SUPPLY_HEALTH_OVERHEAT; + + if (evt->flag3 & RT9756_EVT_WDT) + return POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE; + + return POWER_SUPPLY_HEALTH_GOOD; +} + +static int rt9756_get_charger_online(struct rt9756_data *data, int *val) +{ + unsigned int online; + int ret; + + ret = regmap_field_read(data->rm_fields[F_VBUS_STATE], &online); + if (ret) + return ret; + + *val = !!online; + return 0; +} + +static int rt9756_get_vbus_ovp(struct rt9756_data *data, int *val) +{ + unsigned int opmode; + int ovpval, ret; + + /* operating mode -> 0 bypass, 1 div2 */ + ret = regmap_field_read(data->rm_fields[F_OP_MODE], &opmode); + if (ret) + return ret; + + ret = rt9756_get_value_field_range(data, F_VBUSOVP_EN, F_VBUSOVP, R_VBUSOVP, &ovpval); + if (ret) + return ret; + + *val = opmode ? ovpval * 2 : ovpval; + return 0; +} + +static int rt9756_set_vbus_ovp(struct rt9756_data *data, int val) +{ + unsigned int opmode; + int ret; + + /* operating mode -> 0 bypass, 1 div2 */ + ret = regmap_field_read(data->rm_fields[F_OP_MODE], &opmode); + if (ret) + return ret; + + return rt9756_set_value_field_range(data, F_VBUSOVP_EN, F_VBUSOVP, R_VBUSOVP, + opmode ? val / 2 : val); +} + +static const char * const rt9756_manufacturer = "Richtek Technology Corp."; +static const char * const rt9756_model[MODEL_MAX] = { "RT9756", "RT9757", "RT9770" }; + +static int rt9756_psy_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rt9756_data *data = power_supply_get_drvdata(psy); + int *pval = &val->intval; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + return rt9756_get_switching_state(data, pval); + case POWER_SUPPLY_PROP_HEALTH: + *pval = rt9756_get_charger_health(data); + return 0; + case POWER_SUPPLY_PROP_ONLINE: + return rt9756_get_charger_online(data, pval); + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + return rt9756_get_vbus_ovp(data, pval); + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return rt9756_get_adc(data, ADC_VBUS, pval); + case POWER_SUPPLY_PROP_CURRENT_MAX: + return rt9756_get_value_field_range(data, F_IBUSOCP_EN, F_IBUSOCP, R_IBUSOCP, pval); + case POWER_SUPPLY_PROP_CURRENT_NOW: + return rt9756_get_adc(data, ADC_IBUS, pval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + return rt9756_get_value_field_range(data, F_VBATOVP_EN, F_VBATOVP, R_VBATOVP, pval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + return rt9756_get_value_field_range(data, F_IBATOCP_EN, F_IBATOCP, R_IBATOCP, pval); + case POWER_SUPPLY_PROP_TEMP: + return rt9756_get_adc(data, ADC_TDIE, pval); + case POWER_SUPPLY_PROP_USB_TYPE: + *pval = atomic_read(&data->usb_type); + return 0; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = rt9756_model[data->model]; + return 0; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = rt9756_manufacturer; + return 0; + default: + return -ENODATA; + } +} + +static int rt9756_psy_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct rt9756_data *data = power_supply_get_drvdata(psy); + int intval = val->intval; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + memset(&data->chg_evt, 0, sizeof(data->chg_evt)); + return regmap_field_write(data->rm_fields[F_CHG_EN], !!intval); + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + return rt9756_set_vbus_ovp(data, intval); + case POWER_SUPPLY_PROP_CURRENT_MAX: + return rt9756_set_value_field_range(data, F_IBUSOCP_EN, F_IBUSOCP, R_IBUSOCP, + intval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + return rt9756_set_value_field_range(data, F_VBATOVP_EN, F_VBATOVP, R_VBATOVP, + intval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + return rt9756_set_value_field_range(data, F_IBATOCP_EN, F_IBATOCP, R_IBATOCP, + intval); + case POWER_SUPPLY_PROP_USB_TYPE: + return regmap_field_write(data->rm_fields[F_BC12_EN], !!intval); + default: + return -EINVAL; + } +} + +static const enum power_supply_property rt9756_psy_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static int rt9756_bat_psy_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rt9756_data *data = power_supply_get_drvdata(psy); + int *pval = &val->intval; + + switch (psp) { + case POWER_SUPPLY_PROP_TECHNOLOGY: + *pval = POWER_SUPPLY_TECHNOLOGY_LION; + return 0; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return rt9756_get_adc(data, ADC_VBAT, pval); + case POWER_SUPPLY_PROP_CURRENT_NOW: + return rt9756_get_adc(data, ADC_IBAT, pval); + default: + return -ENODATA; + } +} + +static const enum power_supply_property rt9756_bat_psy_properties[] = { + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static int rt9756_psy_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + case POWER_SUPPLY_PROP_USB_TYPE: + return 1; + default: + return 0; + } +} + +static const unsigned int rt9756_wdt_millisecond[] = { + 500, 1000, 5000, 30000, 40000, 80000, 128000, 255000 +}; + +static ssize_t watchdog_timer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = to_power_supply(dev); + struct rt9756_data *data = power_supply_get_drvdata(psy); + unsigned int wdt_tmr_now = 0, wdt_sel, wdt_dis; + int ret; + + ret = regmap_field_read(data->rm_fields[F_WDT_DIS], &wdt_dis); + if (ret) + return ret; + + if (!wdt_dis) { + ret = regmap_field_read(data->rm_fields[F_WDT_TMR], &wdt_sel); + if (ret) + return ret; + + wdt_tmr_now = rt9756_wdt_millisecond[wdt_sel]; + } + + return sysfs_emit(buf, "%d\n", wdt_tmr_now); +} + +static ssize_t watchdog_timer_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = to_power_supply(dev); + struct rt9756_data *data = power_supply_get_drvdata(psy); + unsigned int wdt_set, wdt_sel; + int ret; + + ret = kstrtouint(buf, 10, &wdt_set); + if (ret) + return ret; + + ret = regmap_field_write(data->rm_fields[F_WDT_DIS], 1); + if (ret) + return ret; + + wdt_sel = find_closest(wdt_set, rt9756_wdt_millisecond, + ARRAY_SIZE(rt9756_wdt_millisecond)); + + ret = regmap_field_write(data->rm_fields[F_WDT_TMR], wdt_sel); + if (ret) + return ret; + + if (wdt_set) { + ret = regmap_field_write(data->rm_fields[F_WDT_DIS], 0); + if (ret) + return ret; + } + + return count; +} + +static const char * const rt9756_opmode_str[] = { "bypass", "div2" }; + +static ssize_t operation_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = to_power_supply(dev); + struct rt9756_data *data = power_supply_get_drvdata(psy); + unsigned int opmode; + int ret; + + ret = regmap_field_read(data->rm_fields[F_OP_MODE], &opmode); + if (ret) + return ret; + + return sysfs_emit(buf, "%s\n", rt9756_opmode_str[opmode]); +} + +static ssize_t operation_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = to_power_supply(dev); + struct rt9756_data *data = power_supply_get_drvdata(psy); + int index, ret; + + index = sysfs_match_string(rt9756_opmode_str, buf); + if (index < 0) + return index; + + ret = regmap_field_write(data->rm_fields[F_OP_MODE], index); + + return ret ?: count; +} + +static DEVICE_ATTR_RW(watchdog_timer); +static DEVICE_ATTR_RW(operation_mode); + +static struct attribute *rt9756_sysfs_attrs[] = { + &dev_attr_watchdog_timer.attr, + &dev_attr_operation_mode.attr, + NULL +}; +ATTRIBUTE_GROUPS(rt9756_sysfs); + +static int rt9756_register_psy(struct rt9756_data *data) +{ + struct power_supply_desc *desc = &data->psy_desc; + struct power_supply_desc *bat_desc = &data->bat_psy_desc; + struct power_supply_config cfg = {}, bat_cfg = {}; + struct device *dev = data->dev; + char *psy_name, *bat_psy_name, **supplied_to; + + bat_cfg.drv_data = data; + bat_cfg.fwnode = dev_fwnode(dev); + + bat_psy_name = devm_kasprintf(dev, GFP_KERNEL, "rt9756-%s-battery", dev_name(dev)); + if (!bat_psy_name) + return -ENOMEM; + + bat_desc->name = bat_psy_name; + bat_desc->type = POWER_SUPPLY_TYPE_BATTERY; + bat_desc->properties = rt9756_bat_psy_properties; + bat_desc->num_properties = ARRAY_SIZE(rt9756_bat_psy_properties); + bat_desc->get_property = rt9756_bat_psy_get_property; + + data->bat_psy = devm_power_supply_register(dev, bat_desc, &bat_cfg); + if (IS_ERR(data->bat_psy)) + return dev_err_probe(dev, PTR_ERR(data->bat_psy), "Failed to register battery\n"); + + supplied_to = devm_kzalloc(dev, sizeof(*supplied_to), GFP_KERNEL); + if (!supplied_to) + return -ENOMEM; + + /* Link charger psy to battery psy */ + supplied_to[0] = bat_psy_name; + + cfg.drv_data = data; + cfg.fwnode = dev_fwnode(dev); + cfg.attr_grp = rt9756_sysfs_groups; + cfg.supplied_to = supplied_to; + cfg.num_supplicants = 1; + + psy_name = devm_kasprintf(dev, GFP_KERNEL, "rt9756-%s", dev_name(dev)); + if (!psy_name) + return -ENOMEM; + + desc->name = psy_name; + desc->type = POWER_SUPPLY_TYPE_USB; + desc->usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | BIT(POWER_SUPPLY_USB_TYPE_CDP); + desc->properties = rt9756_psy_properties; + desc->num_properties = ARRAY_SIZE(rt9756_psy_properties); + desc->property_is_writeable = rt9756_psy_property_is_writeable; + desc->get_property = rt9756_psy_get_property; + desc->set_property = rt9756_psy_set_property; + + data->psy = devm_power_supply_register(dev, desc, &cfg); + + return PTR_ERR_OR_ZERO(data->psy); +} + +static int rt9756_get_usb_type(struct rt9756_data *data) +{ + unsigned int type; + int report_type, ret; + + ret = regmap_field_read(data->rm_fields[F_USB_STATE], &type); + if (ret) + return ret; + + switch (type) { + case USB_SDP: + case USB_NSTD: + report_type = POWER_SUPPLY_USB_TYPE_SDP; + break; + case USB_DCP: + report_type = POWER_SUPPLY_USB_TYPE_DCP; + break; + case USB_CDP: + report_type = POWER_SUPPLY_USB_TYPE_CDP; + break; + case USB_NO_VBUS: + default: + report_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + break; + } + + atomic_set(&data->usb_type, report_type); + return 0; +} + +static irqreturn_t rt9756_irq_handler(int irq, void *devid) +{ + struct rt9756_data *data = devid; + struct regmap *regmap = data->regmap; + struct charger_event *evt = &data->chg_evt; + unsigned int bc12_flag = 0; + int ret; + + ret = regmap_read(regmap, RT9756_REG_INTFLAG1, &evt->flag1); + if (ret) + return IRQ_NONE; + + ret = regmap_read(regmap, RT9756_REG_INTFLAG2, &evt->flag2); + if (ret) + return IRQ_NONE; + + ret = regmap_read(regmap, RT9756_REG_INTFLAG3, &evt->flag3); + if (ret) + return IRQ_NONE; + + if (data->model != MODEL_RT9770) { + ret = regmap_read(regmap, RT9756_REG_INTFLAG4, &evt->flag4); + if (ret) + return IRQ_NONE; + + ret = regmap_read(regmap, RT9756_REG_BC12FLAG, &bc12_flag); + if (ret) + return IRQ_NONE; + } + + dev_dbg(data->dev, "events: 0x%02x,%02x,%02x,%02x,%02x\n", evt->flag1, evt->flag2, + evt->flag3, evt->flag4, bc12_flag); + + if (evt->flag2 & RT9756_EVT_VAC_INSERT) { + ret = regmap_field_write(data->rm_fields[F_BC12_EN], 1); + if (ret) + return IRQ_NONE; + } + + if (evt->flag3 & RT9756_EVT_VAC_UVLO) + atomic_set(&data->usb_type, POWER_SUPPLY_USB_TYPE_UNKNOWN); + + if (bc12_flag & RT9756_EVT_BC12_DONE) { + ret = rt9756_get_usb_type(data); + if (ret) + return IRQ_NONE; + } + + power_supply_changed(data->psy); + + return IRQ_HANDLED; +} + +static int rt9756_config_batsense_resistor(struct rt9756_data *data) +{ + unsigned int shunt_resistor_uohms = 2000, rsense_sel; + + device_property_read_u32(data->dev, "shunt-resistor-micro-ohms", &shunt_resistor_uohms); + + if (!shunt_resistor_uohms || shunt_resistor_uohms > 5000) + return -EINVAL; + + data->real_resistor = shunt_resistor_uohms; + + /* Always choose the larger or equal one to prevent false ocp alarm */ + if (shunt_resistor_uohms <= 1000) { + rsense_sel = 0; + data->rg_resistor = 1000; + } else if (shunt_resistor_uohms <= 2000) { + rsense_sel = 1; + data->rg_resistor = 2000; + } else { + rsense_sel = 2; + data->rg_resistor = 5000; + } + + return regmap_field_write(data->rm_fields[F_IBAT_RSEN], rsense_sel); +} + +static const struct reg_sequence rt9756_init_regs[] = { + REG_SEQ(0x00, 0x80, 1000), /* REG_RESET */ + REG_SEQ0(0x04, 0x13), /* VACOVP/OVPGATE 12V */ + REG_SEQ0(0x00, 0x28), /* WDT_DIS = 1 */ + REG_SEQ0(0x0c, 0x02), /* MASK FLAG1 */ + REG_SEQ0(0x0e, 0x06), /* MASK FLAG2 */ + REG_SEQ0(0x10, 0xca), /* MASK FLAG3 */ + REG_SEQ0(0x44, 0xa0), /* BC12_EN */ + REG_SEQ0(0x47, 0x07), /* MASK BC12FLAG */ + REG_SEQ0(0x4a, 0xfe), /* MASK FLAG4 */ + REG_SEQ0(0x5c, 0x40), /* MASK CON_SWITCHING */ + REG_SEQ0(0x63, 0x01), /* MASK VDDA_UVLO */ +}; + +static const struct reg_sequence rt9770_init_regs[] = { + REG_SEQ(0x00, 0x80, 1000), /* REG_RESET */ + REG_SEQ0(0x04, 0x13), /* VACOVP/OVPGATE 12V */ + REG_SEQ0(0x00, 0x28), /* WDT_DIS = 1 */ + REG_SEQ0(0x0c, 0x02), /* MASK FLAG1 */ + REG_SEQ0(0x0e, 0x06), /* MASK FLAG2 */ + REG_SEQ0(0x10, 0xca), /* MASK FLAG3 */ + REG_SEQ0(0x5c, 0x40), /* MASK CON_SWITCHING */ + REG_SEQ0(0x63, 0x01), /* MASK VDDA_UVLO */ +}; + +static const struct regmap_config rt9756_regmap_config = { + .name = "rt9756", + .reg_bits = 16, + .val_bits = 8, + .max_register = 0x1ff, +}; + +static const struct regmap_config rt9770_regmap_config = { + .name = "rt9770", + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, +}; + +static int rt9756_check_device_model(struct rt9756_data *data) +{ + struct device *dev = data->dev; + unsigned int revid; + int ret; + + ret = regmap_field_read(data->rm_fields[F_REVISION], &revid); + if (ret) + return dev_err_probe(dev, ret, "Failed to read revid\n"); + + if (revid == RT9757_REVID || revid == RT9757A_REVID) + data->model = MODEL_RT9757; + else if (revid == RT9756_REVID || revid == RT9756A_REVID) + data->model = MODEL_RT9756; + else + return dev_err_probe(dev, -EINVAL, "Unknown revision %d\n", revid); + + return 0; +} + +static int rt9770_check_device_model(struct rt9756_data *data) +{ + data->model = MODEL_RT9770; + return 0; +} + +static int rt9756_probe(struct i2c_client *i2c) +{ + const struct rt975x_dev_data *dev_data; + struct device *dev = &i2c->dev; + struct rt9756_data *data; + struct regmap *regmap; + unsigned int devid; + int ret; + + dev_data = device_get_match_data(dev); + if (!dev_data) + return dev_err_probe(dev, -EINVAL, "No device data found\n"); + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev; + mutex_init(&data->adc_lock); + atomic_set(&data->usb_type, POWER_SUPPLY_USB_TYPE_UNKNOWN); + i2c_set_clientdata(i2c, data); + + regmap = devm_regmap_init_i2c(i2c, dev_data->regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to init regmap\n"); + + data->regmap = regmap; + + ret = devm_regmap_field_bulk_alloc(dev, regmap, data->rm_fields, dev_data->reg_fields, + F_MAX_FIELD); + if (ret) + return dev_err_probe(dev, ret, "Failed to alloc regmap fields\n"); + + /* Richtek Device ID check */ + ret = regmap_field_read(data->rm_fields[F_DEV_ID], &devid); + if (ret) + return dev_err_probe(dev, ret, "Failed to read devid\n"); + + if (devid != RICHTEK_DEVID) + return dev_err_probe(dev, -ENODEV, "Incorrect VID 0x%02x\n", devid); + + /* Get specific model */ + ret = dev_data->check_device_model(data); + if (ret) + return ret; + + ret = regmap_register_patch(regmap, dev_data->init_regs, dev_data->num_init_regs); + if (ret) + return dev_err_probe(dev, ret, "Failed to init registers\n"); + + ret = rt9756_config_batsense_resistor(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to config batsense resistor\n"); + + ret = rt9756_register_psy(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to init power supply\n"); + + return devm_request_threaded_irq(dev, i2c->irq, NULL, rt9756_irq_handler, IRQF_ONESHOT, + dev_name(dev), data); +} + +static void rt9756_shutdown(struct i2c_client *i2c) +{ + struct rt9756_data *data = i2c_get_clientdata(i2c); + + regmap_field_write(data->rm_fields[F_REG_RST], 1); +} + +static const struct rt975x_dev_data rt9756_dev_data = { + .regmap_config = &rt9756_regmap_config, + .reg_fields = rt9756_chg_fields, + .init_regs = rt9756_init_regs, + .num_init_regs = ARRAY_SIZE(rt9756_init_regs), + .check_device_model = rt9756_check_device_model, +}; + +static const struct rt975x_dev_data rt9770_dev_data = { + .regmap_config = &rt9770_regmap_config, + .reg_fields = rt9770_chg_fields, + .init_regs = rt9770_init_regs, + .num_init_regs = ARRAY_SIZE(rt9770_init_regs), + .check_device_model = rt9770_check_device_model, +}; + +static const struct of_device_id rt9756_device_match_table[] = { + { .compatible = "richtek,rt9756", .data = &rt9756_dev_data }, + { .compatible = "richtek,rt9770", .data = &rt9770_dev_data }, + {} +}; +MODULE_DEVICE_TABLE(of, rt9756_device_match_table); + +static struct i2c_driver rt9756_charger_driver = { + .driver = { + .name = "rt9756", + .of_match_table = rt9756_device_match_table, + }, + .probe = rt9756_probe, + .shutdown = rt9756_shutdown, +}; +module_i2c_driver(rt9756_charger_driver); + +MODULE_DESCRIPTION("Richtek RT9756 charger driver"); +MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>"); +MODULE_LICENSE("GPL"); |
