| // 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/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"); |