From 79421b49bd72f25eece5900b51d00bad44610f05 Mon Sep 17 00:00:00 2001 From: Martino Facchin Date: Thu, 1 Aug 2024 13:36:27 +0200 Subject: [PATCH 1/4] drivers: add nxp pf1550 mfd (charger+regulator) Add driver for nxp pf1550 PMIC Signed-off-by: Martino Facchin --- drivers/charger/CMakeLists.txt | 1 + drivers/charger/Kconfig | 1 + drivers/charger/Kconfig.pf1550 | 12 + drivers/charger/charger_pf1550.c | 691 ++++++++++++++++++ drivers/mfd/CMakeLists.txt | 1 + drivers/mfd/Kconfig | 1 + drivers/mfd/Kconfig.pf1550 | 10 + drivers/mfd/mfd_pf1550.c | 50 ++ drivers/regulator/CMakeLists.txt | 1 + drivers/regulator/Kconfig | 1 + drivers/regulator/Kconfig.pf1550 | 11 + drivers/regulator/regulator_pf1550.c | 432 +++++++++++ dts/bindings/charger/nxp,pf1550-charger.yaml | 58 ++ dts/bindings/mfd/nxp,pf1550.yaml | 12 + .../regulator/nxp,pf1550-regulator.yaml | 54 ++ 15 files changed, 1336 insertions(+) create mode 100644 drivers/charger/Kconfig.pf1550 create mode 100644 drivers/charger/charger_pf1550.c create mode 100644 drivers/mfd/Kconfig.pf1550 create mode 100644 drivers/mfd/mfd_pf1550.c create mode 100644 drivers/regulator/Kconfig.pf1550 create mode 100644 drivers/regulator/regulator_pf1550.c create mode 100644 dts/bindings/charger/nxp,pf1550-charger.yaml create mode 100644 dts/bindings/mfd/nxp,pf1550.yaml create mode 100644 dts/bindings/regulator/nxp,pf1550-regulator.yaml diff --git a/drivers/charger/CMakeLists.txt b/drivers/charger/CMakeLists.txt index 60b80b0ed25c..fd4832eded35 100644 --- a/drivers/charger/CMakeLists.txt +++ b/drivers/charger/CMakeLists.txt @@ -6,6 +6,7 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/charger.h) zephyr_library_sources_ifdef(CONFIG_CHARGER_BQ24190 charger_bq24190.c) zephyr_library_sources_ifdef(CONFIG_CHARGER_BQ25180 charger_bq25180.c) zephyr_library_sources_ifdef(CONFIG_CHARGER_MAX20335 charger_max20335.c) +zephyr_library_sources_ifdef(CONFIG_CHARGER_PF1550 charger_pf1550.c) zephyr_library_sources_ifdef(CONFIG_SBS_CHARGER sbs_charger.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE charger_handlers.c) zephyr_library_sources_ifdef(CONFIG_EMUL_SBS_CHARGER emul_sbs_charger.c) diff --git a/drivers/charger/Kconfig b/drivers/charger/Kconfig index 4e0b6b3c7a74..c6c68bdaba58 100644 --- a/drivers/charger/Kconfig +++ b/drivers/charger/Kconfig @@ -54,5 +54,6 @@ source "drivers/charger/Kconfig.sbs_charger" source "drivers/charger/Kconfig.bq24190" source "drivers/charger/Kconfig.bq25180" source "drivers/charger/Kconfig.max20335" +source "drivers/charger/Kconfig.pf1550" endif # CHARGER diff --git a/drivers/charger/Kconfig.pf1550 b/drivers/charger/Kconfig.pf1550 new file mode 100644 index 000000000000..f81e0b085b91 --- /dev/null +++ b/drivers/charger/Kconfig.pf1550 @@ -0,0 +1,12 @@ +# Copyright 2024 Arduino SA +# SPDX-License-Identifier: Apache-2.0 + +config CHARGER_PF1550 + bool "NXP PF1550 battery charger driver" + default y + depends on DT_HAS_NXP_PF1550_CHARGER_ENABLED + select GPIO + select I2C + select MFD + help + Enable the NXP PF1550 battery charger driver. diff --git a/drivers/charger/charger_pf1550.c b/drivers/charger/charger_pf1550.c new file mode 100644 index 000000000000..06ce4d3bc5c9 --- /dev/null +++ b/drivers/charger/charger_pf1550.c @@ -0,0 +1,691 @@ +/* + * Copyright 2024 Arduino SA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_pf1550_charger + +#include +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(pf1550_charger, CONFIG_CHARGER_LOG_LEVEL); + +#define INT_ENABLE_DELAY K_MSEC(500) + +#define CHARGER_CHG_INT (0x80 + 0x00) +#define CHARGER_CHG_INT_MASK (0x80 + 0x02) +#define CHARGER_CHG_INT_OK (0x80 + 0x04) +#define CHARGER_VBUS_SNS (0x80 + 0x06) +#define CHARGER_CHG_SNS (0x80 + 0x07) +#define CHARGER_BATT_SNS (0x80 + 0x08) +#define CHARGER_CHG_OPER (0x80 + 0x09) +#define CHARGER_CHG_TMR (0x80 + 0x0A) +#define CHARGER_CHG_EOC_CNFG (0x80 + 0x0D) +#define CHARGER_CHG_CURR_CNFG (0x80 + 0x0E) +#define CHARGER_BATT_REG (0x80 + 0x0F) +#define CHARGER_BATFET_CNFG (0x80 + 0x11) +#define CHARGER_THM_REG_CNFG (0x80 + 0x12) +#define CHARGER_VBUS_INLIM_CNFG (0x80 + 0x14) +#define CHARGER_VBUS_LIN_DPM (0x80 + 0x15) +#define CHARGER_USB_PHY_LDO_CNFG (0x80 + 0x16) +#define CHARGER_DBNC_DELAY_TIME (0x80 + 0x18) +#define CHARGER_CHG_INT_CNFG (0x80 + 0x19) +#define CHARGER_THM_ADJ_SETTING (0x80 + 0x1A) +#define CHARGER_VBUS2SYS_CNFG (0x80 + 0x1B) +#define CHARGER_LED_PWM (0x80 + 0x1C) +#define CHARGER_FAULT_BATFET_CNFG (0x80 + 0x1D) +#define CHARGER_LED_CNFG (0x80 + 0x1E) +#define CHARGER_CHGR_KEY2 (0x80 + 0x1F) + +#define PF1550_BAT_IRQ BIT(2) +#define PF1550_CHG_IRQ BIT(3) +#define PF1550_VBUS_IRQ BIT(5) +#define PF1550_VBUS_DPM_IRQ BIT(5) +#define CHG_INT_ENABLE_ALL (0xFF) + +#define LED_PWM_LED_EN BIT(7) +#define LED_PWM_FULL_ON BIT(5) + +#define LED_CNFG_LED_CFG BIT(4) +#define LED_CNFG_LEDOVRD BIT(5) + +#define CHG_OPER_CHG_OPER_MASK GENMASK(1, 0) +#define CHG_CURR_CNFG_CHG_CC_MASK GENMASK(4, 0) +#define CHG_SNS_CHG_SNS_MASK GENMASK(3, 0) +#define VBUS_INLIM_CNFG_VBUS_INLIM_MASK GENMASK(7, 3) +#define BATT_REG_CHGCV_MASK GENMASK(5, 0) +#define BATT_REG_VSYSMIN_MASK GENMASK(7, 6) +#define THM_REG_CNFG_THM_CNFG_MASK GENMASK(1, 0) + +#define CHG_OPER_CHARGER_OFF_LINEAR_OFF 0 +#define CHG_OPER_CHARGER_OFF_LINEAR_ON 1 +#define CHG_OPER_CHARGER_ON_LINEAR_ON 2 + +enum charger_pf1550_therm_mode { + PF1550_THERM_MODE_DISABLED, + PF1550_THERM_MODE_THERMISTOR, + PF1550_THERM_MODE_JEITA_1, + PF1550_THERM_MODE_JEITA_2, + PF1550_THERM_MODE_UNKNOWN, +}; + +/* synced with YAML binding */ +enum charger_pf1550_led_behaviour { + PF1550_LED_ON_IN_CHARGING_FLASH_IN_FAULT, + PF1550_LED_FLASH_IN_CHARGING_ON_IN_FAULT, + PF1550_LED_MANUAL_OFF +}; + +struct charger_pf1550_led_config { + bool enabled; + bool manual; + enum charger_pf1550_led_behaviour behaviour; +}; + +struct charger_pf1550_config { + struct i2c_dt_spec bus; + struct gpio_dt_spec int_gpio; + char *therm_mon_mode; + uint32_t charge_current_ua; + uint32_t vbus_ilim_ua; + uint32_t charge_voltage_max_uv; + uint32_t vsys_min_uv; +}; + +struct charger_pf1550_data { + const struct device *dev; + struct gpio_callback gpio_cb; + struct k_work int_routine_work; + struct k_work_delayable int_enable_work; + enum charger_status charger_status; + enum charger_online charger_online; + charger_status_notifier_t charger_status_notifier; + charger_online_notifier_t charger_online_notifier; + bool charger_enabled; + uint32_t charge_current_ua; + uint32_t vbus_ilim_ua; + struct charger_pf1550_led_config *led_config; +}; + +static const struct linear_range charger_vbus_ilim_range[] = { + LINEAR_RANGE_INIT(10000, 5000, 0, 8), + LINEAR_RANGE_INIT(100000, 50000, 9, 10), + LINEAR_RANGE_INIT(200000, 100000, 11, 19), + LINEAR_RANGE_INIT(1500000, 0, 20, 20), +}; + +static const struct linear_range charger_fast_charge_ua_range[] = { + LINEAR_RANGE_INIT(100000, 50000, 0, 18), +}; + +static const struct linear_range charger_battery_termination_uv_range[] = { + LINEAR_RANGE_INIT(3500000, 20000, 8, 55), +}; + +static const struct linear_range charger_vsysmin_uv[] = { + LINEAR_RANGE_INIT(3500000, 0, 0, 0), + LINEAR_RANGE_INIT(3700000, 0, 1, 1), + LINEAR_RANGE_INIT(4300000, 0, 2, 2), +}; + +static int pf1550_get_charger_status(const struct device *dev, enum charger_status *status) +{ + enum chg_sns { + PF1550_CHARGER_PRECHARGE, + PF1550_FAST_CHARGE_CONSTANT_CURRENT, + PF1550_FAST_CHARGE_CONSTANT_VOLTAGE, + PF1550_END_OF_CHARGE, + PF1550_CHARGE_DONE, + PF1550_TIMER_FAULT = 6, + PF1550_THERMISTOR_SUSPEND, + PF1550_CHARGER_OFF_INVALID_INPUT, + PF1550_BATTERY_OVERVOLTAGE, + PF1550_BATTERY_OVERTEMPERATURE, + PF1550_CHARGER_OFF_LINEAR_MODE = 12, + }; + + const struct charger_pf1550_config *const config = dev->config; + uint8_t val; + int ret; + + ret = i2c_reg_read_byte_dt(&config->bus, CHARGER_CHG_SNS, &val); + if (ret) { + return ret; + } + + val = FIELD_GET(CHG_SNS_CHG_SNS_MASK, val); + + if (val == PF1550_CHARGE_DONE) { + *status = CHARGER_STATUS_FULL; + } else if (val < PF1550_CHARGE_DONE) { + *status = CHARGER_STATUS_CHARGING; + } else { + *status = CHARGER_STATUS_NOT_CHARGING; + } + + return 0; +} + +static int pf1550_get_charger_online(const struct device *dev, enum charger_online *online) +{ + const struct charger_pf1550_config *const config = dev->config; + uint8_t val; + int ret; + + ret = i2c_reg_read_byte_dt(&config->bus, CHARGER_CHG_OPER, &val); + if (ret) { + return ret; + } + + val = FIELD_GET(CHG_OPER_CHG_OPER_MASK, val); + + switch (val) { + case CHG_OPER_CHARGER_ON_LINEAR_ON: + *online = CHARGER_ONLINE_FIXED; + break; + default: + *online = CHARGER_ONLINE_OFFLINE; + break; + }; + + return 0; +} + +static int pf1550_set_constant_charge_current(const struct device *dev, uint32_t current_ua) +{ + const struct charger_pf1550_config *const config = dev->config; + uint16_t idx; + uint8_t val; + int ret; + + ret = linear_range_group_get_index(charger_fast_charge_ua_range, + ARRAY_SIZE(charger_fast_charge_ua_range), current_ua, + &idx); + if (ret < 0) { + return ret; + } + + val = FIELD_PREP(CHG_CURR_CNFG_CHG_CC_MASK, idx); + + return i2c_reg_update_byte_dt(&config->bus, CHARGER_CHG_CURR_CNFG, + CHG_CURR_CNFG_CHG_CC_MASK, val); +} + +static int pf1550_set_vbus_ilim(const struct device *dev, uint32_t current_ua) +{ + const struct charger_pf1550_config *const config = dev->config; + uint16_t idx; + uint8_t val; + int ret; + + ret = linear_range_group_get_index(charger_vbus_ilim_range, + ARRAY_SIZE(charger_vbus_ilim_range), current_ua, &idx); + if (ret < 0) { + return ret; + } + + val = FIELD_PREP(VBUS_INLIM_CNFG_VBUS_INLIM_MASK, idx); + + return i2c_reg_update_byte_dt(&config->bus, CHARGER_VBUS_INLIM_CNFG, + VBUS_INLIM_CNFG_VBUS_INLIM_MASK, val); +} + +static int pf1550_set_vsys_min(const struct device *dev, uint32_t voltage_uv) +{ + const struct charger_pf1550_config *const config = dev->config; + uint16_t idx; + uint8_t val; + int ret; + + ret = linear_range_group_get_index(charger_vsysmin_uv, ARRAY_SIZE(charger_vsysmin_uv), + voltage_uv, &idx); + if (ret < 0) { + return ret; + } + + val = FIELD_PREP(BATT_REG_VSYSMIN_MASK, idx); + + return i2c_reg_update_byte_dt(&config->bus, CHARGER_BATT_REG, BATT_REG_VSYSMIN_MASK, val); +} + +static int pf1550_set_charge_termination_uv(const struct device *dev, uint32_t voltage_uv) +{ + const struct charger_pf1550_config *const config = dev->config; + uint16_t idx; + uint8_t val; + int ret; + + ret = linear_range_group_get_index(charger_battery_termination_uv_range, + ARRAY_SIZE(charger_battery_termination_uv_range), + voltage_uv, &idx); + if (ret < 0) { + return ret; + } + + val = FIELD_PREP(BATT_REG_CHGCV_MASK, idx); + + return i2c_reg_update_byte_dt(&config->bus, CHARGER_BATT_REG, BATT_REG_CHGCV_MASK, val); +} + +static int pf1550_set_thermistor_mode(const struct device *dev, enum charger_pf1550_therm_mode mode) +{ + const struct charger_pf1550_config *const config = dev->config; + uint8_t val; + + val = FIELD_PREP(THM_REG_CNFG_THM_CNFG_MASK, mode); + + return i2c_reg_update_byte_dt(&config->bus, CHARGER_THM_REG_CNFG, + THM_REG_CNFG_THM_CNFG_MASK, val); +} + +static int pf1550_set_enabled(const struct device *dev, bool enable) +{ + struct charger_pf1550_data *data = dev->data; + const struct charger_pf1550_config *const config = dev->config; + + int ret = i2c_reg_update_byte_dt(&config->bus, CHARGER_CHG_OPER, CHG_OPER_CHG_OPER_MASK, + enable ? 2 : 0); + + if (ret == 0) { + data->charger_enabled = enable; + } + + return ret; +} + +static int pf1550_get_interrupt_source(const struct device *dev, uint8_t *int_a) +{ + const struct charger_pf1550_config *config = dev->config; + uint8_t buf = 0; + int ret; + + ret = i2c_reg_read_byte_dt(&config->bus, CHARGER_CHG_INT, &buf); + + if (int_a) { + *int_a = buf; + } + return ret; +} + +static int pf1550_enable_interrupts(const struct device *dev) +{ + const struct charger_pf1550_config *config = dev->config; + int ret; + + ret = pf1550_get_interrupt_source(dev, NULL); + if (ret < 0) { + LOG_WRN("Failed to clear pending interrupts: %d", ret); + return ret; + } + + return i2c_reg_write_byte_dt(&config->bus, CHARGER_CHG_INT_MASK, CHG_INT_ENABLE_ALL); +} + +static int pf1550_led_config(const struct device *dev) +{ + struct charger_pf1550_data *data = dev->data; + const struct charger_pf1550_config *config = dev->config; + struct charger_pf1550_led_config *cfg = data->led_config; + int ret; + uint8_t val; + + cfg->enabled = true; + + if (cfg->behaviour == PF1550_LED_MANUAL_OFF) { + cfg->manual = true; + cfg->enabled = false; + } + + val = (cfg->enabled ? LED_PWM_LED_EN : 0) | LED_PWM_FULL_ON; + + ret = i2c_reg_write_byte_dt(&config->bus, CHARGER_LED_PWM, val); + if (ret < 0) { + return ret; + } + + val = (cfg->manual ? LED_CNFG_LEDOVRD : 0) | + (cfg->behaviour == PF1550_LED_FLASH_IN_CHARGING_ON_IN_FAULT ? + LED_CNFG_LED_CFG : 0); + + return i2c_reg_write_byte_dt(&config->bus, CHARGER_LED_CNFG, val); +} + +static int pf1550_init_properties(const struct device *dev) +{ + struct charger_pf1550_data *data = dev->data; + const struct charger_pf1550_config *config = dev->config; + int ret; + + data->charger_enabled = true; + data->charge_current_ua = config->charge_current_ua; + data->vbus_ilim_ua = config->vbus_ilim_ua; + + ret = pf1550_get_charger_status(dev, &data->charger_status); + if (ret < 0) { + LOG_ERR("Failed to read charger status: %d", ret); + return ret; + } + + ret = pf1550_get_charger_online(dev, &data->charger_online); + if (ret < 0) { + LOG_ERR("Failed to read charger online: %d", ret); + return ret; + } + + return 0; +} + +enum charger_pf1550_therm_mode pf1550_string_to_therm_mode(const char *mode_string) +{ + static const char *const modes[] = { + [PF1550_THERM_MODE_DISABLED] = "disabled", + [PF1550_THERM_MODE_THERMISTOR] = "thermistor", + [PF1550_THERM_MODE_JEITA_1] = "JEITA-1", + [PF1550_THERM_MODE_JEITA_2] = "JEITA-2", + }; + enum charger_pf1550_therm_mode i; + + for (i = PF1550_THERM_MODE_DISABLED; i < ARRAY_SIZE(modes); i++) { + if (strncmp(mode_string, modes[i], strlen(modes[i])) == 0) { + return i; + } + } + + return PF1550_THERM_MODE_UNKNOWN; +} + +static int pf1550_update_properties(const struct device *dev) +{ + struct charger_pf1550_data *data = dev->data; + const struct charger_pf1550_config *config = dev->config; + enum charger_pf1550_therm_mode therm_mode; + int ret; + + ret = pf1550_set_vbus_ilim(dev, config->vbus_ilim_ua); + if (ret < 0) { + LOG_ERR("Failed to set vbus current limit: %d", ret); + return ret; + } + + ret = pf1550_set_vsys_min(dev, config->vsys_min_uv); + if (ret < 0) { + LOG_ERR("Failed to set minimum system voltage threshold: %d", ret); + return ret; + } + + ret = pf1550_set_charge_termination_uv(dev, config->charge_voltage_max_uv); + if (ret < 0) { + LOG_ERR("Failed to set recharge threshold: %d", ret); + return ret; + } + + therm_mode = pf1550_string_to_therm_mode(config->therm_mon_mode); + ret = pf1550_set_thermistor_mode(dev, therm_mode); + if (ret < 0) { + LOG_ERR("Failed to set thermistor mode: %d", ret); + return ret; + } + + ret = pf1550_set_constant_charge_current(dev, data->charge_current_ua); + if (ret < 0) { + LOG_ERR("Failed to set charge voltage: %d", ret); + return ret; + } + + ret = pf1550_set_enabled(dev, data->charger_enabled); + if (ret < 0) { + LOG_ERR("Failed to set enabled: %d", ret); + return ret; + } + + ret = pf1550_led_config(dev); + if (ret < 0) { + LOG_ERR("Failed to configure led: %d", ret); + return ret; + } + + return 0; +} + +static int pf1550_get_prop(const struct device *dev, charger_prop_t prop, + union charger_propval *val) +{ + struct charger_pf1550_data *data = dev->data; + + switch (prop) { + case CHARGER_PROP_ONLINE: + val->online = data->charger_online; + return 0; + case CHARGER_PROP_STATUS: + val->status = data->charger_status; + return 0; + case CHARGER_PROP_CONSTANT_CHARGE_CURRENT_UA: + val->const_charge_current_ua = data->charge_current_ua; + return 0; + default: + return -ENOTSUP; + } +} + +static int pf1550_set_prop(const struct device *dev, charger_prop_t prop, + const union charger_propval *val) +{ + struct charger_pf1550_data *data = dev->data; + int ret; + + switch (prop) { + case CHARGER_PROP_CONSTANT_CHARGE_CURRENT_UA: + ret = pf1550_set_constant_charge_current(dev, val->const_charge_current_ua); + if (ret == 0) { + data->charge_current_ua = val->const_charge_current_ua; + } + return ret; + case CHARGER_PROP_INPUT_REGULATION_CURRENT_UA: + ret = pf1550_set_vbus_ilim(dev, val->input_current_regulation_current_ua); + if (ret == 0) { + data->vbus_ilim_ua = val->input_current_regulation_current_ua; + } + return ret; + case CHARGER_PROP_STATUS_NOTIFICATION: + data->charger_status_notifier = val->status_notification; + return 0; + case CHARGER_PROP_ONLINE_NOTIFICATION: + data->charger_online_notifier = val->online_notification; + return 0; + default: + return -ENOTSUP; + } +} + +static int pf1550_enable_interrupt_pin(const struct device *dev, bool enabled) +{ + const struct charger_pf1550_config *const config = dev->config; + gpio_flags_t flags; + int ret; + + flags = enabled ? GPIO_INT_EDGE_TO_ACTIVE : GPIO_INT_DISABLE; + + ret = gpio_pin_interrupt_configure_dt(&config->int_gpio, flags); + if (ret < 0) { + LOG_ERR("Could not %s interrupt GPIO callback: %d", enabled ? "enable" : "disable", + ret); + } + + return ret; +} + +static void pf1550_gpio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins) +{ + struct charger_pf1550_data *data = CONTAINER_OF(cb, struct charger_pf1550_data, gpio_cb); + int ret; + + (void)pf1550_enable_interrupt_pin(data->dev, false); + + ret = k_work_submit(&data->int_routine_work); + if (ret < 0) { + LOG_WRN("Could not submit int work: %d", ret); + } +} + +static void pf1550_int_routine_work_handler(struct k_work *work) +{ + struct charger_pf1550_data *data = + CONTAINER_OF(work, struct charger_pf1550_data, int_routine_work); + uint8_t int_src; + int ret; + + ret = pf1550_get_interrupt_source(data->dev, &int_src); + if (ret < 0) { + LOG_WRN("Failed to read interrupt source: %d", ret); + return; + } + + LOG_DBG("Interrupt received: %x", int_src); + + ret = pf1550_get_charger_status(data->dev, &data->charger_status); + if (ret < 0) { + LOG_WRN("Failed to read charger status: %d", ret); + return; + } + + ret = pf1550_get_charger_online(data->dev, &data->charger_online); + if (ret < 0) { + LOG_WRN("Failed to read charger online %d", ret); + return; + } + + if (data->charger_status_notifier != NULL) { + data->charger_status_notifier(data->charger_status); + } + if (data->charger_online_notifier != NULL) { + data->charger_online_notifier(data->charger_online); + } + + if (data->charger_online != CHARGER_ONLINE_OFFLINE) { + (void)pf1550_update_properties(data->dev); + } + + ret = k_work_reschedule(&data->int_enable_work, INT_ENABLE_DELAY); + if (ret < 0) { + LOG_WRN("Could not reschedule int_enable_work: %d", ret); + } +} + +static void pf1550_int_enable_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct charger_pf1550_data *data = + CONTAINER_OF(dwork, struct charger_pf1550_data, int_enable_work); + + (void)pf1550_enable_interrupt_pin(data->dev, true); +} + +static int pf1550_configure_interrupt_pin(const struct device *dev) +{ + struct charger_pf1550_data *data = dev->data; + const struct charger_pf1550_config *config = dev->config; + int ret; + + ret = gpio_is_ready_dt(&config->int_gpio) ? 0 : -ENODEV; + if (ret < 0) { + LOG_ERR("Interrupt GPIO device not ready: %d", ret); + return ret; + } + + ret = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT); + if (ret < 0) { + LOG_ERR("Could not configure interrupt GPIO: %d", ret); + return ret; + } + + gpio_init_callback(&data->gpio_cb, pf1550_gpio_callback, BIT(config->int_gpio.pin)); + ret = gpio_add_callback_dt(&config->int_gpio, &data->gpio_cb); + if (ret < 0) { + LOG_ERR("Could not add interrupt GPIO callback: %d", ret); + return ret; + } + + return 0; +} + +static int pf1550_init(const struct device *dev) +{ + struct charger_pf1550_data *data = dev->data; + const struct charger_pf1550_config *config = dev->config; + int ret; + + if (!i2c_is_ready_dt(&config->bus)) { + return -ENODEV; + } + + data->dev = dev; + + ret = pf1550_init_properties(dev); + if (ret < 0) { + return ret; + } + + k_work_init(&data->int_routine_work, pf1550_int_routine_work_handler); + k_work_init_delayable(&data->int_enable_work, pf1550_int_enable_work_handler); + + ret = pf1550_configure_interrupt_pin(dev); + if (ret < 0) { + return ret; + } + + ret = pf1550_enable_interrupt_pin(dev, true); + if (ret < 0) { + return ret; + } + + ret = pf1550_enable_interrupts(dev); + if (ret < 0) { + LOG_ERR("Failed to enable interrupts: %d", ret); + return ret; + } + + ret = pf1550_update_properties(dev); + if (ret < 0) { + LOG_ERR("Failed to setup charger: %d", ret); + return ret; + } + + return 0; +} + +static const struct charger_driver_api pf1550_driver_api = { + .get_property = pf1550_get_prop, + .set_property = pf1550_set_prop, + .charge_enable = pf1550_set_enabled, +}; + +#define PF1550_DEFINE(inst) \ + static struct charger_pf1550_led_config charger_pf1550_led_config_##inst = { \ + .behaviour = DT_INST_ENUM_IDX(inst, pf1550_led_behaviour), \ + }; \ + static struct charger_pf1550_data charger_pf1550_data_##inst = { \ + .led_config = &charger_pf1550_led_config_##inst, \ + }; \ + static const struct charger_pf1550_config charger_pf1550_config_##inst = { \ + .bus = I2C_DT_SPEC_GET(DT_INST_PARENT(inst)), \ + .int_gpio = GPIO_DT_SPEC_INST_GET(inst, pf1550_int_gpios), \ + .charge_current_ua = DT_INST_PROP(inst, constant_charge_current_max_microamp), \ + .vsys_min_uv = DT_INST_PROP(inst, pf1550_system_voltage_min_threshold_microvolt), \ + .therm_mon_mode = DT_INST_PROP(inst, pf1550_thermistor_monitoring_mode), \ + .vbus_ilim_ua = DT_INST_PROP(inst, pf1550_vbus_current_limit_microamp), \ + .charge_voltage_max_uv = \ + DT_INST_PROP(inst, constant_charge_voltage_max_microvolt), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, &pf1550_init, NULL, &charger_pf1550_data_##inst, \ + &charger_pf1550_config_##inst, POST_KERNEL, \ + CONFIG_MFD_INIT_PRIORITY, &pf1550_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(PF1550_DEFINE) diff --git a/drivers/mfd/CMakeLists.txt b/drivers/mfd/CMakeLists.txt index 5a7f5faee90a..4bbb7eb637cb 100644 --- a/drivers/mfd/CMakeLists.txt +++ b/drivers/mfd/CMakeLists.txt @@ -22,3 +22,4 @@ zephyr_library_sources_ifdef(CONFIG_MFD_ITE_IT8801_ALTCTRL mfd_it8801_altctrl.c) zephyr_library_sources_ifdef(CONFIG_MFD_AW9523B mfd_aw9523b.c) zephyr_library_sources_ifdef(CONFIG_MFD_DS3231 mfd_ds3231.c) zephyr_library_sources_ifdef(CONFIG_MFD_MAX22017 mfd_max22017.c) +zephyr_library_sources_ifdef(CONFIG_MFD_PF1550 mfd_pf1550.c) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 0ccb41f6ba1b..ed3af13ddd7d 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -31,6 +31,7 @@ source "drivers/mfd/Kconfig.nct38xx" source "drivers/mfd/Kconfig.npm1300" source "drivers/mfd/Kconfig.npm2100" source "drivers/mfd/Kconfig.npm6001" +source "drivers/mfd/Kconfig.pf1550" source "drivers/mfd/Kconfig.lpflexcomm" source "drivers/mfd/Kconfig.tle9104" source "drivers/mfd/Kconfig.it8801" diff --git a/drivers/mfd/Kconfig.pf1550 b/drivers/mfd/Kconfig.pf1550 new file mode 100644 index 000000000000..127bcbabe0b5 --- /dev/null +++ b/drivers/mfd/Kconfig.pf1550 @@ -0,0 +1,10 @@ +# Copyright (c) 2024 Arduino SA +# SPDX-License-Identifier: Apache-2.0 + +config MFD_PF1550 + bool "PF1550 PMIC multi-function device driver" + default y + depends on DT_HAS_NXP_PF1550_ENABLED + select I2C + help + Enable the NXP PF1550 PMIC multi-function device driver diff --git a/drivers/mfd/mfd_pf1550.c b/drivers/mfd/mfd_pf1550.c new file mode 100644 index 000000000000..492f516e2533 --- /dev/null +++ b/drivers/mfd/mfd_pf1550.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Arduino SA + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_pf1550 + +#include + +#include +#include + +#define PF1550_REG_CHIP_ID 0x00 +#define PF1550_CHIP_ID_VAL ((15 << 3) | 4) + +struct mfd_pf1550_config { + struct i2c_dt_spec bus; +}; + +static int mfd_pf1550_init(const struct device *dev) +{ + const struct mfd_pf1550_config *config = dev->config; + uint8_t val; + int ret; + + if (!i2c_is_ready_dt(&config->bus)) { + return -ENODEV; + } + + ret = i2c_reg_read_byte_dt(&config->bus, PF1550_REG_CHIP_ID, &val); + if (ret < 0) { + return ret; + } + + if (val != PF1550_CHIP_ID_VAL) { + return -ENODEV; + } + + return 0; +} + +#define MFD_PF1550_DEFINE(inst) \ + static const struct mfd_pf1550_config mfd_pf1550_config##inst = { \ + .bus = I2C_DT_SPEC_INST_GET(inst), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, mfd_pf1550_init, NULL, NULL, &mfd_pf1550_config##inst, \ + POST_KERNEL, CONFIG_MFD_INIT_PRIORITY, NULL); + +DT_INST_FOREACH_STATUS_OKAY(MFD_PF1550_DEFINE) diff --git a/drivers/regulator/CMakeLists.txt b/drivers/regulator/CMakeLists.txt index 7ed90137c3f2..1a71ae534b64 100644 --- a/drivers/regulator/CMakeLists.txt +++ b/drivers/regulator/CMakeLists.txt @@ -17,6 +17,7 @@ zephyr_library_sources_ifdef(CONFIG_REGULATOR_NPM1300 regulator_npm1300.c) zephyr_library_sources_ifdef(CONFIG_REGULATOR_NPM2100 regulator_npm2100.c) zephyr_library_sources_ifdef(CONFIG_REGULATOR_NPM6001 regulator_npm6001.c) zephyr_library_sources_ifdef(CONFIG_REGULATOR_PCA9420 regulator_pca9420.c) +zephyr_library_sources_ifdef(CONFIG_REGULATOR_PF1550 regulator_pf1550.c) zephyr_library_sources_ifdef(CONFIG_REGULATOR_SHELL regulator_shell.c) zephyr_library_sources_ifdef(CONFIG_REGULATOR_RPI_PICO regulator_rpi_pico.c) zephyr_library_sources_ifdef(CONFIG_REGULATOR_NXP_VREF regulator_nxp_vref.c) diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index d4b70e6b76ad..dc331b2139d3 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -39,6 +39,7 @@ source "drivers/regulator/Kconfig.npm1300" source "drivers/regulator/Kconfig.npm2100" source "drivers/regulator/Kconfig.npm6001" source "drivers/regulator/Kconfig.pca9420" +source "drivers/regulator/Kconfig.pf1550" source "drivers/regulator/Kconfig.rpi_pico" source "drivers/regulator/Kconfig.nxp_vref" source "drivers/regulator/Kconfig.mpm54304" diff --git a/drivers/regulator/Kconfig.pf1550 b/drivers/regulator/Kconfig.pf1550 new file mode 100644 index 000000000000..667a4b411ee0 --- /dev/null +++ b/drivers/regulator/Kconfig.pf1550 @@ -0,0 +1,11 @@ +# Copyright (c) 2024 Arduino SA +# SPDX-License-Identifier: Apache-2.0 + +config REGULATOR_PF1550 + bool "NXP PF1550 PMIC regulator driver" + default y + depends on DT_HAS_NXP_PF1550_REGULATOR_ENABLED + select I2C + select MFD + help + Enable the NXP PF1550 PMIC regulator driver diff --git a/drivers/regulator/regulator_pf1550.c b/drivers/regulator/regulator_pf1550.c new file mode 100644 index 000000000000..3759e8e91239 --- /dev/null +++ b/drivers/regulator/regulator_pf1550.c @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2024 Arduino SA + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_pf1550_regulator + +#include +#include +#include +#include + +#define PMIC_DEVICE_ID 0x00 +#define PMIC_OTP_FLAVOR 0x01 +#define PMIC_SILICON_REV 0x02 +#define PMIC_INT_CATEGORY 0x06 +#define PMIC_SW_INT_STAT0 0x08 +#define PMIC_SW_INT_MASK0 0x09 +#define PMIC_SW_INT_SENSE0 0x0A +#define PMIC_SW_INT_STAT1 0x0B +#define PMIC_SW_INT_MASK1 0x0C +#define PMIC_SW_INT_SENSE1 0x0D +#define PMIC_SW_INT_STAT2 0x0E +#define PMIC_SW_INT_MASK2 0x0F +#define PMIC_SW_INT_SENSE2 0x10 +#define PMIC_LDO_INT_STAT0 0x18 +#define PMIC_LDO_INT_MASK0 0x19 +#define PMIC_LDO_INT_SENSE0 0x1A +#define PMIC_TEMP_INT_STAT0 0x20 +#define PMIC_TEMP_INT_MASK0 0x21 +#define PMIC_TEMP_INT_SENSE0 0x22 +#define PMIC_ONKEY_INT_STAT0 0x24 +#define PMIC_ONKEY_INT_MASK0 0x25 +#define PMIC_ONKEY_INT_SENSE0 0x26 +#define PMIC_MISC_INT_STAT0 0x28 +#define PMIC_MISC_INT_MASK0 0x29 +#define PMIC_MISC_INT_SENSE0 0x2A +#define PMIC_COINCELL_CONTROL 0x30 +#define PMIC_SW1_VOLT 0x32 +#define PMIC_SW1_STBY_VOLT 0x33 +#define PMIC_SW1_SLP_VOLT 0x34 +#define PMIC_SW1_CTRL 0x35 +#define PMIC_SW1_CTRL1 0x36 +#define PMIC_SW2_VOLT 0x38 +#define PMIC_SW2_STBY_VOLT 0x39 +#define PMIC_SW2_SLP_VOLT 0x3A +#define PMIC_SW2_CTRL 0x3B +#define PMIC_SW2_CTRL1 0x3C +#define PMIC_SW3_VOLT 0x3E +#define PMIC_SW3_STBY_VOLT 0x3F +#define PMIC_SW3_SLP_VOLT 0x40 +#define PMIC_SW3_CTRL 0x41 +#define PMIC_SW3_CTRL1 0x42 +#define PMIC_VSNVS_CTRL 0x48 +#define PMIC_VREFDDR_CTRL 0x4A +#define PMIC_LDO1_VOLT 0x4C +#define PMIC_LDO1_CTRL 0x4D +#define PMIC_LDO2_VOLT 0x4F +#define PMIC_LDO2_CTRL 0x50 +#define PMIC_LDO3_VOLT 0x52 +#define PMIC_LDO3_CTRL 0x53 +#define PMIC_PWRCTRL0 0x58 +#define PMIC_PWRCTRL1 0x59 +#define PMIC_PWRCTRL2 0x5A +#define PMIC_PWRCTRL3 0x5B +#define PMIC_SW1_PWRDN_SEQ 0x5F +#define PMIC_SW2_PWRDN_SEQ 0x60 +#define PMIC_SW3_PWRDN_SEQ 0x61 +#define PMIC_LDO1_PWRDN_SEQ 0x62 +#define PMIC_LDO2_PWRDN_SEQ 0x63 +#define PMIC_LDO3_PWRDN_SEQ 0x64 +#define PMIC_VREFDDR_PWRDN_SEQ 0x65 +#define PMIC_STATE_INFO 0x67 +#define PMIC_I2C_ADDR 0x68 +#define PMIC_RC_16MHZ 0x6B +#define PMIC_KEY1 0x6B + +enum pf1550_pmic_sources { + PF1550_PMIC_SOURCE_BUCK1, + PF1550_PMIC_SOURCE_BUCK2, + PF1550_PMIC_SOURCE_BUCK3, + PF1550_PMIC_SOURCE_LDO1, + PF1550_PMIC_SOURCE_LDO2, + PF1550_PMIC_SOURCE_LDO3, +}; + +struct regulator_pf1550_desc { + uint8_t vsel_reg; + uint8_t enable_mask; + uint8_t enable_val; + uint8_t cfg_reg; + const struct linear_range *uv_range; + uint8_t uv_nranges; + const struct linear_range *ua_range; + uint8_t ua_nranges; +}; + +struct regulator_pf1550_common_config { + struct i2c_dt_spec bus; +}; + +struct regulator_pf1550_config { + struct regulator_common_config common; + struct i2c_dt_spec bus; + const struct regulator_pf1550_desc *desc; + uint8_t source; +}; + +struct regulator_pf1550_data { + struct regulator_common_data common; +}; + +/* + * Output voltage for BUCK1/2 with DVS disabled (OTP_SWx_DVS_SEL = 1). + * This is needed to reach the 3V3 maximum range + */ +static const struct linear_range buck12_range[] = { + LINEAR_RANGE_INIT(1100000, 0, 0, 0), LINEAR_RANGE_INIT(1200000, 0, 1, 1), + LINEAR_RANGE_INIT(1350000, 0, 2, 2), LINEAR_RANGE_INIT(1500000, 0, 3, 3), + LINEAR_RANGE_INIT(1800000, 0, 4, 4), LINEAR_RANGE_INIT(2500000, 0, 5, 5), + LINEAR_RANGE_INIT(3000000, 0, 6, 6), LINEAR_RANGE_INIT(3300000, 0, 7, 7), +}; +static const struct linear_range buck3_range[] = { + LINEAR_RANGE_INIT(1800000, 100000, 0, 15), +}; +static const struct linear_range buck123_current_limit_range[] = { + LINEAR_RANGE_INIT(1000000, 0, 0, 0), + LINEAR_RANGE_INIT(1200000, 0, 0, 0), + LINEAR_RANGE_INIT(1500000, 0, 0, 0), + LINEAR_RANGE_INIT(2000000, 0, 0, 0), +}; +static const struct linear_range ldo13_range[] = { + LINEAR_RANGE_INIT(750000, 50000, 0, 15), + LINEAR_RANGE_INIT(1800000, 100000, 16, 31), +}; +static const struct linear_range ldo2_range[] = { + LINEAR_RANGE_INIT(1800000, 100000, 0, 15), +}; + +#define PF1550_RAIL_EN BIT(0) +#define PF1550_RAIL_EN_MASK GENMASK(1, 0) +#define PF1550_GOTO_SHIP BIT(0) +#define PF1550_GOTO_SHIP_MASK GENMASK(1, 0) + +static const struct regulator_pf1550_desc __maybe_unused buck1_desc = { + .vsel_reg = PMIC_SW1_VOLT, + .enable_mask = PF1550_RAIL_EN, + .enable_val = PF1550_RAIL_EN_MASK, + .cfg_reg = PMIC_SW1_CTRL, + .uv_range = buck12_range, + .uv_nranges = ARRAY_SIZE(buck12_range), + .ua_range = buck123_current_limit_range, + .ua_nranges = ARRAY_SIZE(buck123_current_limit_range), +}; + +static const struct regulator_pf1550_desc __maybe_unused buck2_desc = { + .vsel_reg = PMIC_SW2_VOLT, + .enable_mask = PF1550_RAIL_EN, + .enable_val = PF1550_RAIL_EN_MASK, + .cfg_reg = PMIC_SW2_CTRL, + .uv_range = buck12_range, + .uv_nranges = ARRAY_SIZE(buck12_range), + .ua_range = buck123_current_limit_range, + .ua_nranges = ARRAY_SIZE(buck123_current_limit_range), +}; + +static const struct regulator_pf1550_desc __maybe_unused buck3_desc = { + .vsel_reg = PMIC_SW3_VOLT, + .enable_mask = PF1550_RAIL_EN, + .enable_val = PF1550_RAIL_EN_MASK, + .cfg_reg = PMIC_SW3_CTRL, + .uv_range = buck3_range, + .uv_nranges = ARRAY_SIZE(buck3_range), + .ua_range = buck123_current_limit_range, + .ua_nranges = ARRAY_SIZE(buck123_current_limit_range), +}; + +static const struct regulator_pf1550_desc __maybe_unused ldo1_desc = { + .vsel_reg = PMIC_LDO1_VOLT, + .enable_mask = PF1550_RAIL_EN, + .enable_val = PF1550_RAIL_EN_MASK, + .cfg_reg = PMIC_LDO1_CTRL, + .uv_range = ldo13_range, + .uv_nranges = ARRAY_SIZE(ldo13_range), +}; + +static const struct regulator_pf1550_desc __maybe_unused ldo2_desc = { + .vsel_reg = PMIC_LDO2_VOLT, + .enable_mask = PF1550_RAIL_EN, + .enable_val = PF1550_RAIL_EN_MASK, + .cfg_reg = PMIC_LDO2_CTRL, + .uv_range = ldo2_range, + .uv_nranges = ARRAY_SIZE(ldo2_range), +}; + +static const struct regulator_pf1550_desc __maybe_unused ldo3_desc = { + .vsel_reg = PMIC_LDO3_VOLT, + .enable_mask = PF1550_RAIL_EN, + .enable_val = PF1550_RAIL_EN_MASK, + .cfg_reg = PMIC_LDO3_CTRL, + .uv_range = ldo13_range, + .uv_nranges = ARRAY_SIZE(ldo13_range), +}; + +static int regulator_pf1550_set_enable(const struct device *dev, bool enable) +{ + const struct regulator_pf1550_config *config = dev->config; + + return i2c_reg_update_byte_dt(&config->bus, config->desc->cfg_reg, + config->desc->enable_mask, + enable ? config->desc->enable_val : 0); +} + +static int regulator_pf1550_enable(const struct device *dev) +{ + return regulator_pf1550_set_enable(dev, true); +} + +static int regulator_pf1550_disable(const struct device *dev) +{ + return regulator_pf1550_set_enable(dev, false); +} + +static unsigned int regulator_pf1550_count_voltages(const struct device *dev) +{ + const struct regulator_pf1550_config *config = dev->config; + + return linear_range_group_values_count(config->desc->uv_range, config->desc->uv_nranges); +} + +static int regulator_pf1550_list_voltage(const struct device *dev, unsigned int idx, + int32_t *volt_uv) +{ + const struct regulator_pf1550_config *config = dev->config; + + return linear_range_group_get_value(config->desc->uv_range, config->desc->uv_nranges, idx, + volt_uv); +} + +static int regulator_pf1550_set_buck_ldo_voltage(const struct device *dev, int32_t min_uv, + int32_t max_uv, const struct linear_range *range, + const uint8_t nranges, uint8_t vout_reg) +{ + const struct regulator_pf1550_config *config = dev->config; + uint16_t idx; + int ret; + + ret = linear_range_group_get_win_index(range, nranges, min_uv, max_uv, &idx); + if (ret < 0) { + return ret; + } + + return i2c_reg_write_byte_dt(&config->bus, vout_reg, (uint8_t)idx); +} + +static int regulator_pf1550_buck12_ldo123_get_voltage(const struct device *dev, + const struct linear_range *range, + const uint8_t nranges, uint8_t vout_reg, + int32_t *volt_uv) +{ + const struct regulator_pf1550_config *config = dev->config; + uint8_t idx; + int ret; + + ret = i2c_reg_read_byte_dt(&config->bus, vout_reg, &idx); + if (ret < 0) { + return ret; + } + + return linear_range_group_get_value(range, nranges, idx, volt_uv); +} + +static int regulator_pf1550_get_voltage(const struct device *dev, int32_t *volt_uv) +{ + const struct regulator_pf1550_config *config = dev->config; + + return regulator_pf1550_buck12_ldo123_get_voltage(dev, config->desc->uv_range, + config->desc->uv_nranges, + config->desc->vsel_reg, volt_uv); +} + +static int regulator_pf1550_set_voltage(const struct device *dev, int32_t min_uv, int32_t max_uv) +{ + const struct regulator_pf1550_config *config = dev->config; + + return regulator_pf1550_set_buck_ldo_voltage(dev, min_uv, max_uv, config->desc->uv_range, + config->desc->uv_nranges, + config->desc->vsel_reg); +} + +static unsigned int regulator_pf1550_count_current_limits(const struct device *dev) +{ + const struct regulator_pf1550_config *config = dev->config; + + if (config->source != PF1550_PMIC_SOURCE_BUCK1 && + config->source != PF1550_PMIC_SOURCE_BUCK2 && + config->source != PF1550_PMIC_SOURCE_BUCK3) { + return -ENOTSUP; + } + + return linear_range_group_values_count(config->desc->ua_range, config->desc->ua_nranges); +} + +static int regulator_pf1550_list_current_limit(const struct device *dev, unsigned int idx, + int32_t *current_ua) +{ + const struct regulator_pf1550_config *config = dev->config; + + if (config->source != PF1550_PMIC_SOURCE_BUCK1 && + config->source != PF1550_PMIC_SOURCE_BUCK2 && + config->source != PF1550_PMIC_SOURCE_BUCK3) { + return -ENOTSUP; + } + + return linear_range_group_get_value(config->desc->ua_range, config->desc->ua_nranges, idx, + current_ua); +} + +static int regulator_pf1550_set_current_limit(const struct device *dev, int32_t min_ua, + int32_t max_ua) +{ + const struct regulator_pf1550_config *config = dev->config; + uint8_t val; + uint16_t idx; + int ret; + + if (config->source != PF1550_PMIC_SOURCE_BUCK1 && + config->source != PF1550_PMIC_SOURCE_BUCK2 && + config->source != PF1550_PMIC_SOURCE_BUCK3) { + return -ENOTSUP; + } + + /* Current is stored in SW*_CTRL1 register */ + ret = i2c_reg_read_byte_dt(&config->bus, config->desc->cfg_reg + 1, &val); + if (ret < 0) { + return ret; + } + + ret = linear_range_group_get_win_index(config->desc->ua_range, config->desc->ua_nranges, + min_ua, max_ua, &idx); + if (ret < 0) { + return ret; + } + + val |= idx; + return i2c_reg_write_byte_dt(&config->bus, config->desc->cfg_reg + 1, val); +} + +static int regulator_pf1550_power_off(const struct device *dev) +{ + const struct regulator_pf1550_common_config *common_config = dev->config; + + return i2c_reg_update_byte_dt(&common_config->bus, PMIC_PWRCTRL3, PF1550_GOTO_SHIP_MASK, + PF1550_GOTO_SHIP); +} + +static int regulator_pf1550_init(const struct device *dev) +{ + const struct regulator_pf1550_config *config = dev->config; + + if (!i2c_is_ready_dt(&config->bus)) { + return -ENODEV; + } + + regulator_common_data_init(dev); + + return regulator_common_init(dev, false); +} + +static int regulator_pf1550_common_init(const struct device *dev) +{ + const struct regulator_pf1550_common_config *common_config = dev->config; + + if (!i2c_is_ready_dt(&common_config->bus)) { + return -ENODEV; + } + + return 0; +} + +static const struct regulator_parent_driver_api parent_api = { + .ship_mode = regulator_pf1550_power_off, +}; + +static const struct regulator_driver_api api = { + .enable = regulator_pf1550_enable, + .disable = regulator_pf1550_disable, + .count_voltages = regulator_pf1550_count_voltages, + .list_voltage = regulator_pf1550_list_voltage, + .set_voltage = regulator_pf1550_set_voltage, + .get_voltage = regulator_pf1550_get_voltage, + .count_current_limits = regulator_pf1550_count_current_limits, + .list_current_limit = regulator_pf1550_list_current_limit, + .set_current_limit = regulator_pf1550_set_current_limit, +}; + +#define REGULATOR_PF1550_DEFINE(node_id, id, child_name, _source) \ + static const struct regulator_pf1550_config regulator_pf1550_config_##id = { \ + .common = REGULATOR_DT_COMMON_CONFIG_INIT(node_id), \ + .bus = I2C_DT_SPEC_GET(DT_GPARENT(node_id)), \ + .desc = &child_name##_desc, \ + .source = _source, \ + }; \ + \ + static struct regulator_pf1550_data regulator_pf1550_data_##id; \ + DEVICE_DT_DEFINE(node_id, regulator_pf1550_init, NULL, ®ulator_pf1550_data_##id, \ + ®ulator_pf1550_config_##id, POST_KERNEL, \ + CONFIG_MFD_INIT_PRIORITY, &api); + +#define REGULATOR_PF1550_DEFINE_COND(inst, child, source) \ + COND_CODE_1( \ + DT_NODE_EXISTS(DT_INST_CHILD(inst, child)), \ + (REGULATOR_PF1550_DEFINE(DT_INST_CHILD(inst, child), child##inst, child, source)), \ + ()) + +#define REGULATOR_PF1550_DEFINE_ALL(inst) \ + static const struct regulator_pf1550_common_config common_config_##inst = { \ + .bus = I2C_DT_SPEC_GET(DT_INST_PARENT(inst)), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, regulator_pf1550_common_init, NULL, NULL, \ + &common_config_##inst, POST_KERNEL, \ + CONFIG_MFD_INIT_PRIORITY, &parent_api); \ + \ + REGULATOR_PF1550_DEFINE_COND(inst, buck1, PF1550_PMIC_SOURCE_BUCK1) \ + REGULATOR_PF1550_DEFINE_COND(inst, buck2, PF1550_PMIC_SOURCE_BUCK2) \ + REGULATOR_PF1550_DEFINE_COND(inst, buck3, PF1550_PMIC_SOURCE_BUCK3) \ + REGULATOR_PF1550_DEFINE_COND(inst, ldo1, PF1550_PMIC_SOURCE_LDO1) \ + REGULATOR_PF1550_DEFINE_COND(inst, ldo2, PF1550_PMIC_SOURCE_LDO2) \ + REGULATOR_PF1550_DEFINE_COND(inst, ldo3, PF1550_PMIC_SOURCE_LDO3) + +DT_INST_FOREACH_STATUS_OKAY(REGULATOR_PF1550_DEFINE_ALL) diff --git a/dts/bindings/charger/nxp,pf1550-charger.yaml b/dts/bindings/charger/nxp,pf1550-charger.yaml new file mode 100644 index 000000000000..c14922114385 --- /dev/null +++ b/dts/bindings/charger/nxp,pf1550-charger.yaml @@ -0,0 +1,58 @@ +# Copyright (c), 2024 Arduino SA +# SPDX-License-Identifier: Apache-2.0 + +description: NXP PF1550 battery charger + +include: battery.yaml + +compatible: "nxp,pf1550-charger" + +properties: + constant-charge-voltage-max-microvolt: + required: true + + constant-charge-current-max-microamp: + required: true + + pf1550,vbus-current-limit-microamp: + type: int + required: true + description: | + VBUS current limit in microamperes. + + pf1550,system-voltage-min-threshold-microvolt: + type: int + required: true + enum: + - 3500000 + - 3700000 + - 4300000 + description: | + System voltage minimum threshold. + + pf1550,thermistor-monitoring-mode: + type: string + required: true + enum: + - "disabled" + - "thermistor" + - "JEITA-1" + - "JEITA-2" + description: | + Thermistor monitoring mode. + Refer to ThrmCfg register description and Table 2 for details. + + pf1550,int-gpios: + type: phandle-array + required: true + description: Interrupt pin + + pf1550,led-behaviour: + type: string + required: true + enum: + - "on-in-charging-flash-in-fault" + - "flash-in-charging-on-in-fault" + - "manual-off" + description: | + Behaviour for charger LED. diff --git a/dts/bindings/mfd/nxp,pf1550.yaml b/dts/bindings/mfd/nxp,pf1550.yaml new file mode 100644 index 000000000000..53f6cdf2e239 --- /dev/null +++ b/dts/bindings/mfd/nxp,pf1550.yaml @@ -0,0 +1,12 @@ +# Copyright (c) 2024 Arduino SA +# SPDX-License-Identifier: Apache-2.0 + +description: NXP PF1550 + +compatible: "nxp,pf1550" + +include: i2c-device.yaml + +properties: + reg: + required: true diff --git a/dts/bindings/regulator/nxp,pf1550-regulator.yaml b/dts/bindings/regulator/nxp,pf1550-regulator.yaml new file mode 100644 index 000000000000..8964b1857ef8 --- /dev/null +++ b/dts/bindings/regulator/nxp,pf1550-regulator.yaml @@ -0,0 +1,54 @@ +# Copyright (c), 2024 Arduino SA +# SPDX-License-Identifier: Apache-2.0 + +description: | + NXP PF1550 PMIC + + The PMIC has two buck converters and three LDOs. All need to be defined as + children nodes, strictly following the BUCK1..3, LDO1..3 node names. For + example: + + pmic@8 { + reg = <0x8>; + ... + regulators { + compatible = nxp,pf1550-regulator"; + + BUCK1 { + /* all properties for BUCK1 */ + }; + BUCK2 { + /* all properties for BUCK2 */ + }; + BUCK3 { + /* all properties for BUCK3 */ + }; + LDO1 { + /* all properties for LDO1 */ + }; + LDO2 { + /* all properties for LDO2 */ + }; + LDO3 { + /* all properties for LDO3 */ + }; + }; + }; + +compatible: "nxp,pf1550-regulator" + +include: base.yaml + +child-binding: + include: + - name: regulator.yaml + property-allowlist: + - regulator-init-microvolt + - regulator-min-microvolt + - regulator-max-microvolt + - regulator-init-microamp + - regulator-max-microamp + - regulator-always-on + - regulator-boot-on + - regulator-initial-mode + - regulator-allowed-modes From 68c0b7db0fa30a4146f32864e95b91ef31198aa1 Mon Sep 17 00:00:00 2001 From: Martino Facchin Date: Thu, 1 Aug 2024 14:01:16 +0200 Subject: [PATCH 2/4] portenta_h7: enable onboard regulator Enable PF1550 PMIC for Arduino Portenta H7 Signed-off-by: Martino Facchin --- .../arduino_portenta_h7_stm32h747xx_m7.dts | 48 +++++++++++++++++++ ...duino_portenta_h7_stm32h747xx_m7_defconfig | 3 ++ 2 files changed, 51 insertions(+) diff --git a/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7.dts b/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7.dts index 3a3745c5a7d5..001d8509059e 100644 --- a/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7.dts +++ b/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7.dts @@ -86,6 +86,54 @@ &i2c1 { status = "okay"; + + pf1550: pmic@8 { + status = "okay"; + reg = <0x8>; + compatible = "nxp,pf1550"; + + pmic_regulators: regulators { + status = "okay"; + compatible = "nxp,pf1550-regulator"; + pf1550_sw1: BUCK1 { + regulator-init-microvolt = <3000000>; + regulator-boot-on; + }; + pf1550_sw2: BUCK2 { + regulator-init-microvolt = <3300000>; + regulator-boot-on; + }; + pf1550_sw3: BUCK3 { + regulator-init-microvolt = <3300000>; + regulator-init-microamp = <2000000>; + regulator-boot-on; + }; + pf1550_ldo1: LDO1 { + regulator-init-microvolt = <1000000>; + regulator-boot-on; + }; + pf1550_ldo2: LDO2 { + regulator-init-microvolt = <1800000>; + regulator-boot-on; + }; + pf1550_ldo3: LDO3 { + regulator-init-microvolt = <1200000>; + regulator-boot-on; + }; + }; + + pmic_charger: charger { + status = "okay"; + compatible = "nxp,pf1550-charger"; + constant-charge-current-max-microamp = <100000>; + constant-charge-voltage-max-microvolt = <4200000>; + pf1550,int-gpios = <&gpiok 0 0>; + pf1550,led-behaviour = "manual-off"; + pf1550,system-voltage-min-threshold-microvolt = <3500000>; + pf1550,thermistor-monitoring-mode = "thermistor"; + pf1550,vbus-current-limit-microamp = <1500000>; + }; + }; }; /* Only one should be enabled */ diff --git a/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7_defconfig b/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7_defconfig index acc978d70c98..06d55f8db39a 100644 --- a/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7_defconfig +++ b/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7_defconfig @@ -25,3 +25,6 @@ CONFIG_UART_LINE_CTRL=y # Enable regulator CONFIG_REGULATOR=y CONFIG_REGULATOR_FIXED=y + +# Enable USB Stack +CONFIG_USB_DEVICE_STACK=y From d44d2e6d52519d37b01e1580e94154b31016c364 Mon Sep 17 00:00:00 2001 From: Martino Facchin Date: Thu, 1 Aug 2024 14:01:45 +0200 Subject: [PATCH 3/4] portenta_h7: enable ethernet driver On older Portenta H7, 1V2 power rail must be enabled to get a functional ethernet phy. Signed-off-by: Martino Facchin --- boards/arduino/portenta_h7/Kconfig.defconfig | 3 +++ .../arduino_portenta_h7-common.dtsi | 19 +++++++++++++++++-- .../arduino_portenta_h7_stm32h747xx_m7.yaml | 7 +++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/boards/arduino/portenta_h7/Kconfig.defconfig b/boards/arduino/portenta_h7/Kconfig.defconfig index b2a4abd35e73..3a59127ffa71 100644 --- a/boards/arduino/portenta_h7/Kconfig.defconfig +++ b/boards/arduino/portenta_h7/Kconfig.defconfig @@ -5,6 +5,9 @@ if BOARD_ARDUINO_PORTENTA_H7 if NETWORKING +config REGULATOR + default y + config NET_L2_ETHERNET default y diff --git a/boards/arduino/portenta_h7/arduino_portenta_h7-common.dtsi b/boards/arduino/portenta_h7/arduino_portenta_h7-common.dtsi index 7a61e830e526..bf6f3348c930 100644 --- a/boards/arduino/portenta_h7/arduino_portenta_h7-common.dtsi +++ b/boards/arduino/portenta_h7/arduino_portenta_h7-common.dtsi @@ -209,17 +209,32 @@ }; }; +&rng { + status = "okay"; +}; + &mac { pinctrl-0 = < ð_ref_clk_pa1 - ð_mdio_pa2 ð_crs_dv_pa7 - ð_mdc_pc1 ð_rxd0_pc4 ð_rxd1_pc5 ð_tx_en_pg11 ð_txd1_pg12 ð_txd0_pg13 >; pinctrl-names = "default"; + status = "okay"; +}; + +&mdio { + status = "okay"; + pinctrl-0 = <ð_mdio_pa2 ð_mdc_pc1>; + pinctrl-names = "default"; + + ethernet-phy@0 { + compatible = "ethernet-phy"; + reg = <0x00>; + status = "okay"; + }; }; zephyr_udc0: &usbotg_hs { diff --git a/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7.yaml b/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7.yaml index e4179c6e5b9c..97a23152c59f 100644 --- a/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7.yaml +++ b/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7.yaml @@ -9,4 +9,11 @@ ram: 512 flash: 1024 supported: - gpio + - netif:eth + - i2c + - spi + - qspi + - memc + - usb_cdc + - usb_device vendor: arduino From b281743a9cce08296f9f117b621f212ff2675f5c Mon Sep 17 00:00:00 2001 From: Martino Facchin Date: Wed, 31 Jul 2024 11:41:16 +0200 Subject: [PATCH 4/4] portenta_h7: fix dangerous misconfiguration Selecting the wrong power scheme could potentially destroy the board. Luckily, the bit can only be set once and the default build still uses the Arduino bootloader (which has the correct setting). Signed-off-by: Martino Facchin --- .../portenta_h7/arduino_portenta_h7_stm32h747xx_m7_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7_defconfig b/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7_defconfig index 06d55f8db39a..1260af5f054b 100644 --- a/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7_defconfig +++ b/boards/arduino/portenta_h7/arduino_portenta_h7_stm32h747xx_m7_defconfig @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # Enable the internal SMPS regulator -CONFIG_POWER_SUPPLY_DIRECT_SMPS=y +CONFIG_POWER_SUPPLY_SMPS_1V8_SUPPLIES_LDO=y # Enable GPIO CONFIG_GPIO=y