diff --git a/CMakeLists.txt b/CMakeLists.txt index 78b102e..e2c0eba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(blinky) zephyr_include_directories(inc) +add_subdirectory(drivers) target_sources(app PRIVATE src/main.c diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..9e33318 --- /dev/null +++ b/Kconfig @@ -0,0 +1,7 @@ +mainmenu "blinky" + +source "Kconfig.zephyr" + +menu "Application Drivers" +rsource "drivers/Kconfig" +endmenu diff --git a/boards/atguigu/mini_keyboard/mini_keyboard-pinctrl.dtsi b/boards/atguigu/mini_keyboard/mini_keyboard-pinctrl.dtsi index eb124f5..85e9a33 100644 --- a/boards/atguigu/mini_keyboard/mini_keyboard-pinctrl.dtsi +++ b/boards/atguigu/mini_keyboard/mini_keyboard-pinctrl.dtsi @@ -1,2 +1,16 @@ &pinctrl { + i2c0_default: i2c0_default { + group1 { + psels = , + ; + }; + }; + + i2c0_sleep: i2c0_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; }; diff --git a/boards/atguigu/mini_keyboard/mini_keyboard.dts b/boards/atguigu/mini_keyboard/mini_keyboard.dts index 4dd8408..3de68c7 100644 --- a/boards/atguigu/mini_keyboard/mini_keyboard.dts +++ b/boards/atguigu/mini_keyboard/mini_keyboard.dts @@ -82,6 +82,23 @@ }; }; +&i2c0 { + status = "okay"; + pinctrl-0 = <&i2c0_default>; + pinctrl-1 = <&i2c0_sleep>; + pinctrl-names = "default", "sleep"; + clock-frequency = <400000>; + + ip5306: pmic@75 { + compatible = "injoinic,ip5306"; + reg = <0x75>; + wakeup-gpios = <&gpio0 22 GPIO_ACTIVE_LOW>; + keepalive-interval-ms = <8000>; + keepalive-pulse-width-ms = <500>; + status = "okay"; + }; +}; + &gpio0 { status = "okay"; }; diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt new file mode 100644 index 0000000..adcc168 --- /dev/null +++ b/drivers/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(pmic) diff --git a/drivers/Kconfig b/drivers/Kconfig new file mode 100644 index 0000000..cdcd32e --- /dev/null +++ b/drivers/Kconfig @@ -0,0 +1 @@ +rsource "pmic/Kconfig" diff --git a/drivers/pmic/CMakeLists.txt b/drivers/pmic/CMakeLists.txt new file mode 100644 index 0000000..8f39708 --- /dev/null +++ b/drivers/pmic/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ip5306) diff --git a/drivers/pmic/Kconfig b/drivers/pmic/Kconfig new file mode 100644 index 0000000..3374f25 --- /dev/null +++ b/drivers/pmic/Kconfig @@ -0,0 +1 @@ +rsource "ip5306/Kconfig" diff --git a/drivers/pmic/ip5306/CMakeLists.txt b/drivers/pmic/ip5306/CMakeLists.txt new file mode 100644 index 0000000..dbd5c80 --- /dev/null +++ b/drivers/pmic/ip5306/CMakeLists.txt @@ -0,0 +1 @@ +target_sources_ifdef(CONFIG_IP5306 app PRIVATE ip5306.c) diff --git a/drivers/pmic/ip5306/Kconfig b/drivers/pmic/ip5306/Kconfig new file mode 100644 index 0000000..43464b7 --- /dev/null +++ b/drivers/pmic/ip5306/Kconfig @@ -0,0 +1,11 @@ +menu "PMIC Drivers" + +config IP5306 + bool "IP5306 PMIC driver" + default y + depends on I2C + depends on DT_HAS_INJOINIC_IP5306_ENABLED + help + Enable the out-of-tree IP5306 PMIC driver. + +endmenu diff --git a/drivers/pmic/ip5306/ip5306.c b/drivers/pmic/ip5306/ip5306.c new file mode 100644 index 0000000..248b7aa --- /dev/null +++ b/drivers/pmic/ip5306/ip5306.c @@ -0,0 +1,196 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DT_DRV_COMPAT injoinic_ip5306 + +LOG_MODULE_REGISTER(ip5306, LOG_LEVEL_INF); + +#define IP5306_REG_READ0 0x70 +#define IP5306_REG_READ1 0x71 +#define IP5306_CHARGING_BIT BIT(3) +#define IP5306_FULL_BIT BIT(3) + +struct ip5306_config { + struct i2c_dt_spec bus; + struct gpio_dt_spec wakeup_gpio; + uint32_t keepalive_interval_ms; + uint32_t keepalive_pulse_width_ms; +}; + +struct ip5306_data { + const struct device *dev; + struct k_work_delayable keepalive_work; + bool started; + bool pulse_active; +}; + +static uint32_t keepalive_low_delay_ms(const struct ip5306_config *config) +{ + if (config->keepalive_interval_ms > config->keepalive_pulse_width_ms) { + return config->keepalive_interval_ms - + config->keepalive_pulse_width_ms; + } + + return 0; +} + +static void keepalive_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct ip5306_data *data = + CONTAINER_OF(dwork, struct ip5306_data, keepalive_work); + const struct device *dev = data->dev; + const struct ip5306_config *config = dev->config; + int err; + + if (!data->started) { + return; + } + + if (!data->pulse_active) { + err = gpio_pin_set_dt(&config->wakeup_gpio, 1); + if (err) { + LOG_ERR("Failed to assert wakeup pulse (%d)", err); + } + + data->pulse_active = true; + k_work_reschedule(&data->keepalive_work, + K_MSEC(config->keepalive_pulse_width_ms)); + return; + } + + err = gpio_pin_set_dt(&config->wakeup_gpio, 0); + if (err) { + LOG_ERR("Failed to deassert wakeup pulse (%d)", err); + } + + data->pulse_active = false; + k_work_reschedule(&data->keepalive_work, + K_MSEC(keepalive_low_delay_ms(config))); +} + +static int ip5306_init_api(const struct device *dev) +{ + const struct ip5306_config *config = dev->config; + struct ip5306_data *data = dev->data; + uint8_t reg_val; + int err; + + if (data->started) { + return 0; + } + + err = i2c_reg_read_byte_dt(&config->bus, IP5306_REG_READ0, ®_val); + if (err) { + LOG_ERR("IP5306 probe failed (%d)", err); + return err; + } + + data->started = true; + data->pulse_active = false; + k_work_reschedule(&data->keepalive_work, + K_MSEC(keepalive_low_delay_ms(config))); + + return 0; +} + +static int ip5306_get_status_api(const struct device *dev, + struct ip5306_status *status) +{ + const struct ip5306_config *config = dev->config; + uint8_t read0; + uint8_t read1; + int err; + + if (status == NULL) { + return -EINVAL; + } + + err = i2c_reg_read_byte_dt(&config->bus, IP5306_REG_READ0, &read0); + if (err) { + return err; + } + + err = i2c_reg_read_byte_dt(&config->bus, IP5306_REG_READ1, &read1); + if (err) { + return err; + } + + status->charging = (read0 & IP5306_CHARGING_BIT) != 0U; + status->full = (read1 & IP5306_FULL_BIT) != 0U; + + return 0; +} + +static int ip5306_dev_init(const struct device *dev) +{ + const struct ip5306_config *config = dev->config; + struct ip5306_data *data = dev->data; + + if (!i2c_is_ready_dt(&config->bus)) { + LOG_ERR("I2C bus not ready"); + return -ENODEV; + } + + if (!gpio_is_ready_dt(&config->wakeup_gpio)) { + LOG_ERR("Wakeup GPIO not ready"); + return -ENODEV; + } + + if (config->keepalive_pulse_width_ms == 0U) { + LOG_ERR("Invalid keepalive pulse width"); + return -EINVAL; + } + + if (config->keepalive_interval_ms == 0U) { + LOG_ERR("Invalid keepalive interval"); + return -EINVAL; + } + + if (config->keepalive_pulse_width_ms > config->keepalive_interval_ms) { + LOG_ERR("Pulse width cannot exceed interval"); + return -EINVAL; + } + + data->dev = dev; + data->started = false; + data->pulse_active = false; + + k_work_init_delayable(&data->keepalive_work, keepalive_work_handler); + + return gpio_pin_configure_dt(&config->wakeup_gpio, GPIO_OUTPUT_INACTIVE); +} + +static const struct ip5306_driver_api ip5306_driver_api = { + .init = ip5306_init_api, + .get_status = ip5306_get_status_api, +}; + +#define IP5306_DEFINE(inst) \ + static struct ip5306_data ip5306_data_##inst; \ + \ + static const struct ip5306_config ip5306_config_##inst = { \ + .bus = I2C_DT_SPEC_INST_GET(inst), \ + .wakeup_gpio = GPIO_DT_SPEC_INST_GET(inst, wakeup_gpios), \ + .keepalive_interval_ms = \ + DT_INST_PROP(inst, keepalive_interval_ms), \ + .keepalive_pulse_width_ms = \ + DT_INST_PROP(inst, keepalive_pulse_width_ms), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, ip5306_dev_init, NULL, \ + &ip5306_data_##inst, &ip5306_config_##inst, \ + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ + &ip5306_driver_api) + +DT_INST_FOREACH_STATUS_OKAY(IP5306_DEFINE) diff --git a/dts/bindings/pmic/injoinic,ip5306.yaml b/dts/bindings/pmic/injoinic,ip5306.yaml new file mode 100644 index 0000000..47aa8d6 --- /dev/null +++ b/dts/bindings/pmic/injoinic,ip5306.yaml @@ -0,0 +1,21 @@ +description: Injoinic IP5306 PMIC with software wakeup keepalive support + +compatible: "injoinic,ip5306" + +include: i2c-device.yaml + +properties: + wakeup-gpios: + type: phandle-array + required: true + description: GPIO used to generate the wakeup keepalive pulse. + + keepalive-interval-ms: + type: int + required: true + description: Period between two keepalive pulses, measured from pulse start. + + keepalive-pulse-width-ms: + type: int + required: true + description: Active-high pulse width for the keepalive wakeup GPIO. diff --git a/dts/bindings/vendor-prefixes.txt b/dts/bindings/vendor-prefixes.txt new file mode 100644 index 0000000..262ee04 --- /dev/null +++ b/dts/bindings/vendor-prefixes.txt @@ -0,0 +1 @@ +injoinic Injoinic diff --git a/inc/drivers/pmic/ip5306.h b/inc/drivers/pmic/ip5306.h new file mode 100644 index 0000000..63357ae --- /dev/null +++ b/inc/drivers/pmic/ip5306.h @@ -0,0 +1,44 @@ +#ifndef BLINKY_DRIVERS_PMIC_IP5306_H_ +#define BLINKY_DRIVERS_PMIC_IP5306_H_ + +#include +#include + +#include + +struct ip5306_status { + bool charging; + bool full; +}; + +struct ip5306_driver_api { + int (*init)(const struct device *dev); + int (*get_status)(const struct device *dev, struct ip5306_status *status); +}; + +static inline int ip5306_init(const struct device *dev) +{ + const struct ip5306_driver_api *api = + (const struct ip5306_driver_api *)dev->api; + + if ((api == NULL) || (api->init == NULL)) { + return -ENOSYS; + } + + return api->init(dev); +} + +static inline int ip5306_get_status(const struct device *dev, + struct ip5306_status *status) +{ + const struct ip5306_driver_api *api = + (const struct ip5306_driver_api *)dev->api; + + if ((api == NULL) || (api->get_status == NULL)) { + return -ENOSYS; + } + + return api->get_status(dev, status); +} + +#endif /* BLINKY_DRIVERS_PMIC_IP5306_H_ */ diff --git a/prj.conf b/prj.conf index 073bab0..28711f7 100644 --- a/prj.conf +++ b/prj.conf @@ -2,6 +2,7 @@ CONFIG_CAF=y CONFIG_CAF_BUTTONS=y CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h" CONFIG_GPIO=y +CONFIG_I2C=y CONFIG_REBOOT=y CONFIG_SENSOR=y CONFIG_ADC=y diff --git a/src/battery_module.c b/src/battery_module.c index 11f96ac..fbecbf8 100644 --- a/src/battery_module.c +++ b/src/battery_module.c @@ -5,8 +5,10 @@ #define MODULE battery_module #include +#include #include +#include #include #include #include @@ -17,12 +19,16 @@ LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); #define VBATT_NODE DT_PATH(vbatt) +#define IP5306_NODE DT_NODELABEL(ip5306) #define BATTERY_SAMPLE_INTERVAL K_SECONDS(1) BUILD_ASSERT(DT_NODE_HAS_STATUS(VBATT_NODE, okay), "Missing /vbatt voltage-divider node in devicetree"); +BUILD_ASSERT(DT_NODE_HAS_STATUS(IP5306_NODE, okay), + "Missing ip5306 node in devicetree"); static const struct device *const vbatt_dev = DEVICE_DT_GET(VBATT_NODE); +static const struct device *const ip5306_dev = DEVICE_DT_GET(IP5306_NODE); static struct k_work_delayable battery_sample_work; static bool initialized; static bool running; @@ -48,7 +54,9 @@ static int measurement_enable(bool enable) static void battery_sample_fn(struct k_work *work) { + struct ip5306_status pmic_status; struct sensor_value voltage; + int voltage_mv; int err; ARG_UNUSED(work); @@ -69,7 +77,15 @@ static void battery_sample_fn(struct k_work *work) goto reschedule; } - LOG_INF("Battery voltage: %d mV", sensor_value_to_mv(&voltage)); + err = ip5306_get_status(ip5306_dev, &pmic_status); + if (err) { + LOG_WRN("IP5306 status read failed (%d)", err); + goto reschedule; + } + + voltage_mv = sensor_value_to_mv(&voltage); + LOG_INF("Battery: %d mV, charging=%d, full=%d", + voltage_mv, pmic_status.charging, pmic_status.full); reschedule: if (running) { @@ -84,7 +100,19 @@ static int module_init(void) return -ENODEV; } + if (!device_is_ready(ip5306_dev)) { + LOG_ERR("ip5306 device not ready"); + return -ENODEV; + } + + int err = ip5306_init(ip5306_dev); + if (err) { + LOG_ERR("ip5306 init failed (%d)", err); + return err; + } + k_work_init_delayable(&battery_sample_work, battery_sample_fn); + power_manager_restrict(MODULE_IDX(MODULE), POWER_MANAGER_LEVEL_SUSPENDED); return 0; }