| /* |
| * Copyright 2023 Grinn |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT maxim_max20335_charger |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/charger.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/sys/linear_range.h> |
| |
| #include "zephyr/logging/log.h" |
| LOG_MODULE_REGISTER(max20335_charger); |
| |
| #define MAX20335_REG_STATUS_A 0x02 |
| #define MAX20335_REG_ILIMCNTL 0x09 |
| #define MAX20335_REG_CHG_CNTL_A 0x0A |
| #define MAX20335_CHGCNTLA_BAT_REG_CFG_MASK GENMASK(4, 1) |
| #define MAX20335_ILIMCNTL_MASK GENMASK(1, 0) |
| #define MAX20335_STATUS_A_CHG_STAT_MASK GENMASK(2, 0) |
| #define MAX20335_CHRG_EN_MASK BIT(0) |
| #define MAX20335_CHRG_EN BIT(0) |
| #define MAX20335_REG_CVC_VREG_MIN_UV 4050000U |
| #define MAX20335_REG_CVC_VREG_STEP_UV 50000U |
| #define MAX20335_REG_CVC_VREG_MIN_IDX 0x0U |
| #define MAX20335_REG_CVC_VREG_MAX_IDX 0x0CU |
| |
| struct charger_max20335_config { |
| struct i2c_dt_spec bus; |
| uint32_t max_ichg_ua; |
| uint32_t max_vreg_uv; |
| }; |
| |
| enum { |
| MAX20335_CHARGER_OFF, |
| MAX20335_CHARGING_SUSPENDED_DUE_TO_TEMPERATURE, |
| MAX20335_PRE_CHARGE_IN_PROGRESS, |
| MAX20335_FAST_CHARGE_IN_PROGRESS_1, |
| MAX20335_FAST_CHARGE_IN_PROGRESS_2, |
| MAX20335_MAINTAIN_CHARGE_IN_PROGRESS, |
| MAX20335_MAIN_CHARGER_TIMER_DONE, |
| MAX20335_CHARGER_FAULT_CONDITION, |
| }; |
| |
| static const struct linear_range charger_uv_range = |
| LINEAR_RANGE_INIT(MAX20335_REG_CVC_VREG_MIN_UV, |
| MAX20335_REG_CVC_VREG_STEP_UV, |
| MAX20335_REG_CVC_VREG_MIN_IDX, |
| MAX20335_REG_CVC_VREG_MAX_IDX); |
| |
| static int max20335_get_status(const struct device *dev, enum charger_status *status) |
| { |
| const struct charger_max20335_config *const config = dev->config; |
| uint8_t val; |
| int ret; |
| |
| ret = i2c_reg_read_byte_dt(&config->bus, MAX20335_REG_STATUS_A, &val); |
| if (ret) { |
| return ret; |
| } |
| |
| val = FIELD_GET(MAX20335_STATUS_A_CHG_STAT_MASK, val); |
| |
| switch (val) { |
| case MAX20335_CHARGER_OFF: |
| __fallthrough; |
| case MAX20335_CHARGING_SUSPENDED_DUE_TO_TEMPERATURE: |
| __fallthrough; |
| case MAX20335_CHARGER_FAULT_CONDITION: |
| *status = CHARGER_STATUS_NOT_CHARGING; |
| break; |
| case MAX20335_PRE_CHARGE_IN_PROGRESS: |
| __fallthrough; |
| case MAX20335_FAST_CHARGE_IN_PROGRESS_1: |
| __fallthrough; |
| case MAX20335_FAST_CHARGE_IN_PROGRESS_2: |
| __fallthrough; |
| case MAX20335_MAINTAIN_CHARGE_IN_PROGRESS: |
| *status = CHARGER_STATUS_CHARGING; |
| break; |
| case MAX20335_MAIN_CHARGER_TIMER_DONE: |
| *status = CHARGER_STATUS_FULL; |
| break; |
| default: |
| *status = CHARGER_STATUS_UNKNOWN; |
| break; |
| }; |
| |
| return 0; |
| } |
| |
| static int max20335_set_constant_charge_voltage(const struct device *dev, |
| uint32_t voltage_uv) |
| { |
| const struct charger_max20335_config *const config = dev->config; |
| uint16_t idx; |
| uint8_t val; |
| int ret; |
| |
| if (voltage_uv > config->max_vreg_uv) { |
| LOG_WRN("Exceeded max constant charge voltage!"); |
| return -EINVAL; |
| } |
| |
| ret = linear_range_get_index(&charger_uv_range, voltage_uv, &idx); |
| if (ret == -EINVAL) { |
| return ret; |
| } |
| |
| val = FIELD_PREP(MAX20335_CHGCNTLA_BAT_REG_CFG_MASK, idx); |
| |
| return i2c_reg_update_byte_dt(&config->bus, |
| MAX20335_REG_CHG_CNTL_A, |
| MAX20335_CHGCNTLA_BAT_REG_CFG_MASK, |
| val); |
| } |
| |
| static int max20335_set_constant_charge_current(const struct device *dev, |
| uint32_t current_ua) |
| { |
| const struct charger_max20335_config *const config = dev->config; |
| uint8_t val; |
| |
| if (current_ua > config->max_ichg_ua) { |
| LOG_WRN("Exceeded max constant charge current!"); |
| return -EINVAL; |
| } |
| |
| switch (current_ua) { |
| case 0: |
| val = 0x00; |
| break; |
| case 100000: |
| val = 0x01; |
| break; |
| case 500000: |
| val = 0x02; |
| break; |
| case 1000000: |
| val = 0x03; |
| break; |
| default: |
| return -ENOTSUP; |
| }; |
| |
| val = FIELD_PREP(MAX20335_ILIMCNTL_MASK, val); |
| |
| return i2c_reg_update_byte_dt(&config->bus, |
| MAX20335_REG_ILIMCNTL, |
| MAX20335_ILIMCNTL_MASK, |
| val); |
| } |
| |
| static int max20335_get_constant_charge_current(const struct device *dev, |
| uint32_t *current_ua) |
| { |
| const struct charger_max20335_config *const config = dev->config; |
| uint8_t val; |
| int ret; |
| |
| ret = i2c_reg_read_byte_dt(&config->bus, MAX20335_REG_ILIMCNTL, &val); |
| if (ret) { |
| return ret; |
| } |
| |
| switch (val) { |
| case 0x00: |
| *current_ua = 0; |
| break; |
| case 0x01: |
| *current_ua = 100000; |
| break; |
| case 0x02: |
| *current_ua = 500000; |
| break; |
| case 0x03: |
| *current_ua = 1000000; |
| break; |
| default: |
| return -ENOTSUP; |
| }; |
| |
| return 0; |
| } |
| |
| static int max20335_get_constant_charge_voltage(const struct device *dev, |
| uint32_t *current_uv) |
| { |
| const struct charger_max20335_config *const config = dev->config; |
| uint8_t val; |
| int ret; |
| |
| ret = i2c_reg_read_byte_dt(&config->bus, MAX20335_REG_CHG_CNTL_A, &val); |
| if (ret) { |
| return ret; |
| } |
| |
| val = FIELD_GET(MAX20335_CHGCNTLA_BAT_REG_CFG_MASK, val); |
| |
| return linear_range_get_value(&charger_uv_range, val, current_uv); |
| } |
| |
| static int max20335_set_enabled(const struct device *dev, bool enable) |
| { |
| const struct charger_max20335_config *const config = dev->config; |
| |
| return i2c_reg_update_byte_dt(&config->bus, |
| MAX20335_REG_CHG_CNTL_A, |
| MAX20335_CHRG_EN_MASK, |
| enable ? MAX20335_CHRG_EN : 0); |
| } |
| |
| static int max20335_get_prop(const struct device *dev, charger_prop_t prop, |
| union charger_propval *val) |
| { |
| switch (prop) { |
| case CHARGER_PROP_STATUS: |
| return max20335_get_status(dev, &val->status); |
| case CHARGER_PROP_CONSTANT_CHARGE_CURRENT_UA: |
| return max20335_get_constant_charge_current(dev, |
| &val->const_charge_current_ua); |
| case CHARGER_PROP_CONSTANT_CHARGE_VOLTAGE_UV: |
| return max20335_get_constant_charge_voltage(dev, |
| &val->const_charge_voltage_uv); |
| default: |
| return -ENOTSUP; |
| } |
| } |
| |
| static int max20335_set_prop(const struct device *dev, charger_prop_t prop, |
| const union charger_propval *val) |
| { |
| switch (prop) { |
| case CHARGER_PROP_STATUS: |
| switch (val->status) { |
| case CHARGER_STATUS_CHARGING: |
| return max20335_set_enabled(dev, true); |
| case CHARGER_STATUS_NOT_CHARGING: |
| return max20335_set_enabled(dev, false); |
| default: |
| return -ENOTSUP; |
| } |
| case CHARGER_PROP_CONSTANT_CHARGE_CURRENT_UA: |
| return max20335_set_constant_charge_current(dev, |
| val->const_charge_current_ua); |
| case CHARGER_PROP_CONSTANT_CHARGE_VOLTAGE_UV: |
| return max20335_set_constant_charge_voltage(dev, |
| val->const_charge_voltage_uv); |
| default: |
| return -ENOTSUP; |
| } |
| |
| } |
| |
| static int max20335_init(const struct device *dev) |
| { |
| const struct charger_max20335_config *config = dev->config; |
| |
| if (!i2c_is_ready_dt(&config->bus)) { |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static const struct charger_driver_api max20335_driver_api = { |
| .get_property = max20335_get_prop, |
| .set_property = max20335_set_prop, |
| }; |
| |
| #define MAX20335_DEFINE(inst) \ |
| static const struct charger_max20335_config charger_max20335_config_##inst = { \ |
| .bus = I2C_DT_SPEC_GET(DT_INST_PARENT(inst)), \ |
| .max_ichg_ua = DT_INST_PROP(inst, constant_charge_current_max_microamp), \ |
| .max_vreg_uv = DT_INST_PROP(inst, constant_charge_voltage_max_microvolt), \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(inst, &max20335_init, NULL, NULL, \ |
| &charger_max20335_config_##inst, \ |
| POST_KERNEL, CONFIG_MFD_INIT_PRIORITY, \ |
| &max20335_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(MAX20335_DEFINE) |