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.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.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 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..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 @@ -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 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