diff --git a/boards/atguigu/atguigu_keyboard_dongle/Kconfig.atguigu_keyboard_dongle b/boards/atguigu/atguigu_keyboard_dongle/Kconfig.atguigu_keyboard_dongle new file mode 100644 index 0000000..3c11697 --- /dev/null +++ b/boards/atguigu/atguigu_keyboard_dongle/Kconfig.atguigu_keyboard_dongle @@ -0,0 +1,2 @@ +config BOARD_ATGUIGU_KEYBOARD_DONGLE + select SOC_NRF52833_QDAA \ No newline at end of file diff --git a/boards/atguigu/atguigu_keyboard_dongle/atguigu_keyboard_dongle-pinctrl.dtsi b/boards/atguigu/atguigu_keyboard_dongle/atguigu_keyboard_dongle-pinctrl.dtsi new file mode 100644 index 0000000..eb124f5 --- /dev/null +++ b/boards/atguigu/atguigu_keyboard_dongle/atguigu_keyboard_dongle-pinctrl.dtsi @@ -0,0 +1,2 @@ +&pinctrl { +}; diff --git a/boards/atguigu/atguigu_keyboard_dongle/atguigu_keyboard_dongle.dts b/boards/atguigu/atguigu_keyboard_dongle/atguigu_keyboard_dongle.dts new file mode 100644 index 0000000..e7cf05d --- /dev/null +++ b/boards/atguigu/atguigu_keyboard_dongle/atguigu_keyboard_dongle.dts @@ -0,0 +1,42 @@ +/dts-v1/; +#include +#include "atguigu_keyboard_dongle-pinctrl.dtsi" + +/ { + model = "Keyboard Dongle"; + compatible = "atguigu,atguigu-keyboard-dongle"; + + chosen { + zephyr,sram = &sram0; + zephyr,flash = &flash0; + zephyr,code-partition = &slot0_partition; + }; +}; + +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + boot_partition: partition@0 { + label = "mcuboot"; + reg = <0x00000000 DT_SIZE_K(48)>; + }; + + slot0_partition: partition@c000 { + label = "image-0"; + reg = <0x0000c000 DT_SIZE_K(220)>; + }; + + slot1_partition: partition@43000 { + label = "image-1"; + reg = <0x00043000 DT_SIZE_K(220)>; + }; + + storage_partition: partition@7a000 { + label = "storage"; + reg = <0x0007a000 DT_SIZE_K(24)>; + }; + }; +}; \ No newline at end of file diff --git a/boards/atguigu/atguigu_keyboard_dongle/atguigu_keyboard_dongle.yaml b/boards/atguigu/atguigu_keyboard_dongle/atguigu_keyboard_dongle.yaml new file mode 100644 index 0000000..6862e75 --- /dev/null +++ b/boards/atguigu/atguigu_keyboard_dongle/atguigu_keyboard_dongle.yaml @@ -0,0 +1,10 @@ +identifier: atguigu_keyboard_dongle/nrf52833 +name: Keyboard Dongle +vendor: atguigu +type: mcu +arch: arm +ram: 128 +flash: 512 +toolchain: + - zephyr +supported: [] \ No newline at end of file diff --git a/boards/atguigu/atguigu_keyboard_dongle/atguigu_keyboard_dongle_defconfig b/boards/atguigu/atguigu_keyboard_dongle/atguigu_keyboard_dongle_defconfig new file mode 100644 index 0000000..fe65101 --- /dev/null +++ b/boards/atguigu/atguigu_keyboard_dongle/atguigu_keyboard_dongle_defconfig @@ -0,0 +1,2 @@ +CONFIG_ARM_MPU=y +CONFIG_HW_STACK_PROTECTION=y \ No newline at end of file diff --git a/boards/atguigu/atguigu_keyboard_dongle/board.cmake b/boards/atguigu/atguigu_keyboard_dongle/board.cmake new file mode 100644 index 0000000..9998f8d --- /dev/null +++ b/boards/atguigu/atguigu_keyboard_dongle/board.cmake @@ -0,0 +1,9 @@ +set(OPENOCD_NRF5_SUBFAMILY "nrf52") +board_runner_args(jlink "--device=nRF52833_xxAA" "--speed=4000") +board_runner_args(pyocd "--target=nrf52833" "--frequency=4000000") + +include(${ZEPHYR_BASE}/boards/common/nrfutil.board.cmake) +include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake) +include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake) +include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake) +include(${ZEPHYR_BASE}/boards/common/openocd-nrf5.board.cmake) \ No newline at end of file diff --git a/boards/atguigu/atguigu_keyboard_dongle/board.yml b/boards/atguigu/atguigu_keyboard_dongle/board.yml new file mode 100644 index 0000000..d011983 --- /dev/null +++ b/boards/atguigu/atguigu_keyboard_dongle/board.yml @@ -0,0 +1,5 @@ +board: + name: atguigu_keyboard_dongle + vendor: atguigu + socs: + - name: nrf52833 \ No newline at end of file diff --git a/boards/atguigu/atguigu_keyboard_dongle/pre_dt_board.cmake b/boards/atguigu/atguigu_keyboard_dongle/pre_dt_board.cmake new file mode 100644 index 0000000..519d784 --- /dev/null +++ b/boards/atguigu/atguigu_keyboard_dongle/pre_dt_board.cmake @@ -0,0 +1,2 @@ +# Suppress "unique_unit_address_if_enabled" to handle some overlaps +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") diff --git a/boards/atguigu/atguigu_mini_keyboard/Kconfig.atguigu_mini_keyboard b/boards/atguigu/atguigu_mini_keyboard/Kconfig.atguigu_mini_keyboard new file mode 100644 index 0000000..59baf73 --- /dev/null +++ b/boards/atguigu/atguigu_mini_keyboard/Kconfig.atguigu_mini_keyboard @@ -0,0 +1,2 @@ +config BOARD_ATGUIGU_MINI_KEYBOARD + select SOC_NRF52840_QFAA \ No newline at end of file diff --git a/boards/atguigu/atguigu_mini_keyboard/atguigu_mini_keyboard-pinctrl.dtsi b/boards/atguigu/atguigu_mini_keyboard/atguigu_mini_keyboard-pinctrl.dtsi new file mode 100644 index 0000000..91829d8 --- /dev/null +++ b/boards/atguigu/atguigu_mini_keyboard/atguigu_mini_keyboard-pinctrl.dtsi @@ -0,0 +1,73 @@ +&pinctrl { + qdec_default: qdec_default { + group1 { + psels = , + ; + bias-pull-up; + }; + }; + + qdec_sleep: qdec_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + + led_spi_default: led_spi_default { + group1 { + psels = ; + }; + }; + + led_spi_sleep: led_spi_sleep { + group1 { + psels = ; + low-power-enable; + }; + }; + + lcd_spi_default: lcd_spi_default { + group1 { + psels = , + ; + }; + }; + + lcd_spi_sleep: lcd_spi_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + + i2c1_default: i2c1_default { + group1 { + psels = , + ; + bias-pull-up; + }; + }; + + i2c1_sleep: i2c1_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + + pwm0_default: pwm0_default { + group1 { + psels = ; + }; + }; + pwm0_sleep: pwm0_sleep { + group1 { + psels = ; + low-power-enable; + }; + }; +}; diff --git a/boards/atguigu/atguigu_mini_keyboard/atguigu_mini_keyboard.dts b/boards/atguigu/atguigu_mini_keyboard/atguigu_mini_keyboard.dts new file mode 100644 index 0000000..307ac9a --- /dev/null +++ b/boards/atguigu/atguigu_mini_keyboard/atguigu_mini_keyboard.dts @@ -0,0 +1,240 @@ +/dts-v1/; +#include +#include "atguigu_mini_keyboard-pinctrl.dtsi" +#include +#include + +/ { + model = "Mini Keyboard, 17 keys"; + compatible = "atguigu,atguigu-mini-keyboard"; + + chosen { + zephyr,sram = &sram0; + zephyr,flash = &flash0; + zephyr,code-partition = &slot0_partition; + zephyr,display = &st7789v3; + }; + + aliases { + backlight = &backlight; + }; + + mode_sense: mode-sense { + compatible = "voltage-divider"; + io-channels = <&adc 5>; + output-ohms = <1>; + full-ohms = <1>; + }; + + battery_sense: battery-sense { + compatible = "voltage-divider"; + io-channels = <&adc 7>; + output-ohms = <100000>; + full-ohms = <200000>; + power-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>; + power-on-sample-delay-us = <100>; + }; + + pwm_leds: pwm_leds { + compatible = "pwm-leds"; + status = "okay"; + backlight: pwm_led_0 { + pwms = <&pwm0 0 PWM_MSEC(10) PWM_POLARITY_INVERTED>; + }; + }; + + led_0: led_0 { + compatible = "gpio-leds"; + status = "okay"; + + chan0 { + gpios = <&gpio0 17 GPIO_ACTIVE_LOW>; + }; + }; + + led_1: led_1 { + compatible = "gpio-leds"; + status = "okay"; + + chan0 { + gpios = <&gpio1 2 GPIO_ACTIVE_LOW>; + }; + }; + + mipi_dbi: mipi_dbi { + compatible = "zephyr,mipi-dbi-spi"; + status = "okay"; + spi-dev = <&spi3>; + dc-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>; + reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; + write-only; + #address-cells = <1>; + #size-cells = <0>; + + st7789v3: st7789v@0 { + compatible = "sitronix,st7789v"; + status = "okay"; + reg = <0>; + mipi-max-frequency = <32000000>; + width = <320>; + height = <172>; + x-offset = <0>; + y-offset = <34>; + vcom = <0x2b>; + gctrl = <0x35>; + vrhs = <0x11>; + vdvs = <0x20>; + mdac = <0xA0>; + lcm = <0x2c>; + colmod = <0x55>; + gamma = <0x01>; + porch-param = [ 0c 0c 00 33 33 ]; + cmd2en-param = [ 5a 69 02 01 ]; + pwctrl1-param = [ a4 a1 ]; + pvgam-param = [ d0 00 02 07 0a 28 32 44 42 06 0e 12 14 17 ]; + nvgam-param = [ d0 00 02 07 0a 28 31 54 47 0e 1c 17 1b 1e ]; + ram-param = [ 00 f0 ]; + rgb-param = [ c0 02 14 ]; + mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE"; + }; + }; + +}; + +&spi2 { + status = "okay"; + pinctrl-0 = <&led_spi_default>; + pinctrl-1 = <&led_spi_sleep>; + pinctrl-names = "default", "sleep"; + cs-gpios = <&gpio0 21 GPIO_ACTIVE_LOW>; + + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-spi"; + supply-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; + reg = <0>; + spi-max-frequency = <8000000>; + chain-length = <17>; + color-mapping = ; + spi-one-frame = <0xFC>; + spi-zero-frame = <0xC0>; + }; +}; + +&i2c1 { + status = "okay"; + pinctrl-0 = <&i2c1_default>; + pinctrl-1 = <&i2c1_sleep>; + pinctrl-names = "default", "sleep"; + clock-frequency = <400000>; + + ip5306: pmic@75 { + status = "okay"; + compatible = "injoinic,ip5306"; + reg = <0x75>; + keepalive-gpios = <&gpio0 22 GPIO_ACTIVE_LOW>; + keepalive-interval-ms = <10000>; + keepalive-offload; + }; +}; + +/* 编码器 */ +&qdec { + status = "okay"; + /* 引用上面定义的标签 */ + pinctrl-0 = <&qdec_default>; + pinctrl-1 = <&qdec_sleep>; + /* 指定 pinctrl-0 为默认,pinctrl-1 为睡眠 */ + pinctrl-names = "default", "sleep"; + + /* 别忘了 QDEC 必须的两个属性 */ + steps = <40>; + led-pre = <0>; +}; + +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + boot_partition: partition@0 { + label = "mcuboot"; + reg = <0x00000000 DT_SIZE_K(48)>; + }; + + slot0_partition: partition@c000 { + label = "image-0"; + reg = <0x0000c000 DT_SIZE_K(472)>; + }; + + slot1_partition: partition@82000 { + label = "image-1"; + reg = <0x00082000 DT_SIZE_K(472)>; + }; + + storage_partition: partition@f8000 { + label = "storage"; + reg = <0x000f8000 DT_SIZE_K(32)>; + }; + }; +}; + +&spi3 { + status = "okay"; + pinctrl-0 = <&lcd_spi_default>; + pinctrl-1 = <&lcd_spi_sleep>; + pinctrl-names = "default", "sleep"; + cs-gpios = <&gpio0 2 GPIO_ACTIVE_LOW>; +}; + +&adc { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@5 { + reg = <5>; + zephyr,gain = "ADC_GAIN_1_6"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + zephyr,resolution = <12>; + }; + + channel@7 { + reg = <7>; + zephyr,gain = "ADC_GAIN_1_6"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + zephyr,resolution = <12>; + }; +}; + +&pwm0 { + status = "okay"; + pinctrl-0 = <&pwm0_default>; + pinctrl-1 = <&pwm0_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&gpiote { + status = "okay"; +}; + +&usbd { + status = "okay"; +}; + +&uicr { + nfct-pins-as-gpios; + gpio-as-nreset; +}; diff --git a/boards/atguigu/atguigu_mini_keyboard/atguigu_mini_keyboard.yaml b/boards/atguigu/atguigu_mini_keyboard/atguigu_mini_keyboard.yaml new file mode 100644 index 0000000..ab1619d --- /dev/null +++ b/boards/atguigu/atguigu_mini_keyboard/atguigu_mini_keyboard.yaml @@ -0,0 +1,10 @@ +identifier: atguigu_mini_keyboard/nrf52840 +name: Mini Keyboard, 17 keys +vendor: atguigu +type: mcu +arch: arm +ram: 256 +flash: 1024 +toolchain: + - zephyr +supported: [] \ No newline at end of file diff --git a/boards/atguigu/atguigu_mini_keyboard/atguigu_mini_keyboard_defconfig b/boards/atguigu/atguigu_mini_keyboard/atguigu_mini_keyboard_defconfig new file mode 100644 index 0000000..fe65101 --- /dev/null +++ b/boards/atguigu/atguigu_mini_keyboard/atguigu_mini_keyboard_defconfig @@ -0,0 +1,2 @@ +CONFIG_ARM_MPU=y +CONFIG_HW_STACK_PROTECTION=y \ No newline at end of file diff --git a/boards/atguigu/atguigu_mini_keyboard/board.cmake b/boards/atguigu/atguigu_mini_keyboard/board.cmake new file mode 100644 index 0000000..03e8860 --- /dev/null +++ b/boards/atguigu/atguigu_mini_keyboard/board.cmake @@ -0,0 +1,9 @@ +set(OPENOCD_NRF5_SUBFAMILY "nrf52") +board_runner_args(jlink "--device=nRF52840_xxAA" "--speed=4000") +board_runner_args(pyocd "--target=nrf52840" "--frequency=4000000") + +include(${ZEPHYR_BASE}/boards/common/nrfutil.board.cmake) +include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake) +include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake) +include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake) +include(${ZEPHYR_BASE}/boards/common/openocd-nrf5.board.cmake) \ No newline at end of file diff --git a/boards/atguigu/atguigu_mini_keyboard/board.yml b/boards/atguigu/atguigu_mini_keyboard/board.yml new file mode 100644 index 0000000..6b5c87e --- /dev/null +++ b/boards/atguigu/atguigu_mini_keyboard/board.yml @@ -0,0 +1,5 @@ +board: + name: atguigu_mini_keyboard + vendor: atguigu + socs: + - name: nrf52840 \ No newline at end of file diff --git a/boards/atguigu/atguigu_mini_keyboard/pre_dt_board.cmake b/boards/atguigu/atguigu_mini_keyboard/pre_dt_board.cmake new file mode 100644 index 0000000..519d784 --- /dev/null +++ b/boards/atguigu/atguigu_mini_keyboard/pre_dt_board.cmake @@ -0,0 +1,2 @@ +# Suppress "unique_unit_address_if_enabled" to handle some overlaps +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") diff --git a/modules/ip5306/CMakeLists.txt b/modules/ip5306/CMakeLists.txt new file mode 100644 index 0000000..fa23967 --- /dev/null +++ b/modules/ip5306/CMakeLists.txt @@ -0,0 +1,5 @@ +zephyr_library() +zephyr_library_sources_ifdef(CONFIG_IP5306 drivers/power/ip5306.c) +zephyr_library_sources_ifdef(CONFIG_IP5306 drivers/power/ip5306_keepalive_sw.c) +zephyr_library_sources_ifdef(CONFIG_IP5306_KEEPALIVE_HW_NRF drivers/power/ip5306_keepalive_nrf.c) +zephyr_include_directories(include) diff --git a/modules/ip5306/Kconfig b/modules/ip5306/Kconfig new file mode 100644 index 0000000..293f118 --- /dev/null +++ b/modules/ip5306/Kconfig @@ -0,0 +1,5 @@ +menu "IP5306 Module" + +rsource "drivers/power/Kconfig" + +endmenu diff --git a/modules/ip5306/drivers/power/Kconfig b/modules/ip5306/drivers/power/Kconfig new file mode 100644 index 0000000..70e0a06 --- /dev/null +++ b/modules/ip5306/drivers/power/Kconfig @@ -0,0 +1,17 @@ +config IP5306 + bool "Injoinic IP5306 PMIC driver" + depends on I2C && GPIO + default n + help + Enable IP5306 PMIC driver over I2C. + +config IP5306_KEEPALIVE_HW_NRF + bool "Enable nRF keepalive HW offload backend" + depends on IP5306 && SOC_FAMILY_NORDIC_NRF + select NRFX_GPIOTE + select NRFX_GPPI + select NRFX_RTC2 + default y + help + Enable low-power keepalive offload backend using RTC2 + GPIOTE + GPPI. + Runtime selection is controlled by devicetree property keepalive-offload. diff --git a/modules/ip5306/drivers/power/ip5306.c b/modules/ip5306/drivers/power/ip5306.c new file mode 100644 index 0000000..ea78e66 --- /dev/null +++ b/modules/ip5306/drivers/power/ip5306.c @@ -0,0 +1,152 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "ip5306_priv.h" + +#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF) +#include +#define IP5306_CFG_KEEPALIVE_PSEL_INIT(inst) \ + .keepalive_psel = NRF_DT_GPIOS_TO_PSEL_OR(DT_DRV_INST(inst), keepalive_gpios, 0), +#else +#define IP5306_CFG_KEEPALIVE_PSEL_INIT(inst) +#endif + +LOG_MODULE_REGISTER(ip5306, LOG_LEVEL_INF); + +#define DT_DRV_COMPAT injoinic_ip5306 + +#define IP5306_REG_READ0 0x70 +#define IP5306_REG_READ1 0x71 +#define IP5306_STATUS_BIT BIT(3) + +static int ip5306_read_reg(const struct device *dev, uint8_t reg, uint8_t *val) +{ + const struct ip5306_config *cfg = dev->config; + + return i2c_reg_read_byte_dt(&cfg->i2c, reg, val); +} + +static int ip5306_get_status_bit(const struct device *dev, uint8_t reg, bool *flag) +{ + uint8_t value = 0U; + int ret; + + if (flag == NULL) { + return -EINVAL; + } + + ret = ip5306_read_reg(dev, reg, &value); + if (ret != 0) { + return ret; + } + + *flag = ((value & IP5306_STATUS_BIT) != 0U); + return 0; +} + +static int ip5306_api_is_charging(const struct device *dev, bool *charging) +{ + return ip5306_get_status_bit(dev, IP5306_REG_READ0, charging); +} + +static int ip5306_api_is_charge_full(const struct device *dev, bool *full) +{ + return ip5306_get_status_bit(dev, IP5306_REG_READ1, full); +} + +static void ip5306_keepalive_start(const struct device *dev) +{ + struct ip5306_data *data = dev->data; + const struct ip5306_config *cfg = dev->config; + + if (!cfg->has_keepalive_gpio || (data->keepalive_interval_ms == 0U)) { + data->backend = IP5306_KEEPALIVE_BACKEND_NONE; + return; + } + + /* + * 选择策略: + * 1) DTS 显式请求硬件 offload 时,先尝试硬件后端; + * 2) 若硬件依赖不可用或资源被占用,则告警后回退软件后端。 + */ + if (cfg->keepalive_offload) { +#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF) + int ret = ip5306_keepalive_hw_nrf_start(dev); + + if (ret == 0) { + LOG_INF("Keepalive backend=HW(nRF), pulse=%ums interval=%ums", + data->keepalive_pulse_ms, data->keepalive_interval_ms); + return; + } + + LOG_WRN("HW keepalive unavailable (%d), fallback to SW backend", ret); +#else + LOG_WRN("HW keepalive requested but HW backend is not built, fallback to SW backend"); +#endif + } + + + ip5306_keepalive_sw_start(dev); + LOG_INF("Keepalive backend=SW, pulse=%ums interval=%ums", + data->keepalive_pulse_ms, data->keepalive_interval_ms); +} + +static int ip5306_init(const struct device *dev) +{ + const struct ip5306_config *cfg = dev->config; + struct ip5306_data *data = dev->data; + + if (!i2c_is_ready_dt(&cfg->i2c)) { + return -ENODEV; + } + + if (cfg->has_keepalive_gpio) { + if (!gpio_is_ready_dt(&cfg->keepalive_gpio)) { + return -ENODEV; + } + + if (gpio_pin_configure_dt(&cfg->keepalive_gpio, GPIO_OUTPUT_INACTIVE) != 0) { + return -EIO; + } + } + + data->keepalive_high = false; + data->dev = dev; + data->backend = IP5306_KEEPALIVE_BACKEND_NONE; + data->keepalive_pulse_ms = (cfg->keepalive_pulse_ms != 0U) ? + cfg->keepalive_pulse_ms : IP5306_KEEPALIVE_DEFAULT_PULSE_MS; + data->keepalive_interval_ms = (cfg->keepalive_interval_ms != 0U) ? + cfg->keepalive_interval_ms : IP5306_KEEPALIVE_DEFAULT_INTERVAL_MS; + + ip5306_keepalive_start(dev); + + return 0; +} + +static const struct ip5306_driver_api ip5306_api = { + .is_charging = ip5306_api_is_charging, + .is_charge_full = ip5306_api_is_charge_full, +}; + +#define IP5306_DEFINE(inst) \ + static struct ip5306_data ip5306_data_##inst; \ + static const struct ip5306_config ip5306_cfg_##inst = { \ + .i2c = I2C_DT_SPEC_INST_GET(inst), \ + .keepalive_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, keepalive_gpios, {0}), \ + IP5306_CFG_KEEPALIVE_PSEL_INIT(inst) \ + .keepalive_pulse_ms = DT_INST_PROP(inst, keepalive_pulse_ms), \ + .keepalive_interval_ms = DT_INST_PROP(inst, keepalive_interval_ms), \ + .has_keepalive_gpio = DT_INST_NODE_HAS_PROP(inst, keepalive_gpios), \ + .keepalive_offload = DT_INST_PROP_OR(inst, keepalive_offload, false), \ + }; \ + DEVICE_DT_INST_DEFINE(inst, ip5306_init, NULL, &ip5306_data_##inst, \ + &ip5306_cfg_##inst, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ + &ip5306_api); + +DT_INST_FOREACH_STATUS_OKAY(IP5306_DEFINE) diff --git a/modules/ip5306/drivers/power/ip5306_keepalive_nrf.c b/modules/ip5306/drivers/power/ip5306_keepalive_nrf.c new file mode 100644 index 0000000..446038d --- /dev/null +++ b/modules/ip5306/drivers/power/ip5306_keepalive_nrf.c @@ -0,0 +1,198 @@ +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include "ip5306_priv.h" + +LOG_MODULE_DECLARE(ip5306, LOG_LEVEL_INF); + +#define IP5306_KEEPALIVE_RTC_FREQUENCY_HZ 32768U +#define IP5306_KEEPALIVE_RTC_CC_HIGH 0U +#define IP5306_KEEPALIVE_RTC_CC_LOW 1U +#define IP5306_KEEPALIVE_RTC_CC_PERIOD 2U +#define IP5306_KEEPALIVE_MIN_PERIOD_TICKS 3U + +/* nRF52 单核场景默认使用 GPIOTE0 + RTC2。 */ +static nrfx_gpiote_t ip5306_keepalive_gpiote = NRFX_GPIOTE_INSTANCE(NRF_GPIOTE_INST_GET(0)); +static const nrfx_rtc_t ip5306_keepalive_rtc = NRFX_RTC_INSTANCE(2); +static bool ip5306_keepalive_hw_claimed; + +static void ip5306_keepalive_rtc_handler(nrfx_rtc_int_type_t int_type) +{ + /* + * 这里故意不处理任何中断逻辑: + * 本模块的保活脉冲由 RTC event -> GPPI/PPI -> GPIOTE task 的硬件链路完成, + * 不依赖线程或 ISR 参与,从而减少 CPU 唤醒次数、降低功耗。 + */ + ARG_UNUSED(int_type); +} + +static uint32_t ip5306_keepalive_ms_to_rtc_ticks(uint32_t time_ms) +{ + /* 四舍五入到最近 tick,降低长时间运行时累计漂移。 */ + uint64_t ticks = ((uint64_t)time_ms * IP5306_KEEPALIVE_RTC_FREQUENCY_HZ + 500U) / 1000U; + + return (uint32_t)MAX(ticks, 1U); +} + +int ip5306_keepalive_hw_nrf_start(const struct device *dev) +{ + struct ip5306_data *data = dev->data; + const struct ip5306_config *cfg = dev->config; + const bool active_high = ((cfg->keepalive_gpio.dt_flags & GPIO_ACTIVE_LOW) == 0U); + const uint32_t period_ticks = ip5306_keepalive_ms_to_rtc_ticks(data->keepalive_interval_ms); + uint32_t high_ticks = ip5306_keepalive_ms_to_rtc_ticks(data->keepalive_pulse_ms); + int ret; + + if (ip5306_keepalive_hw_claimed) { + return -EBUSY; + } + + if (period_ticks < IP5306_KEEPALIVE_MIN_PERIOD_TICKS) { + return -EINVAL; + } + + if (high_ticks >= (period_ticks - 1U)) { + high_ticks = period_ticks - 1U; + } + + if (!nrfx_gpiote_init_check(&ip5306_keepalive_gpiote)) { + ret = nrfx_gpiote_init(&ip5306_keepalive_gpiote, NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY); + if ((ret != 0) && (ret != -EALREADY)) { + return ret; + } + } + + ret = nrfx_gpiote_channel_alloc(&ip5306_keepalive_gpiote, &data->keepalive_gpiote_channel); + if (ret != 0) { + /* GPIOTE 通道不足通常意味着被其他驱动占用。 */ + LOG_ERR("Failed to allocate GPIOTE channel"); + return ret; + } + + const nrfx_gpiote_output_config_t output_config = { + .drive = NRF_GPIO_PIN_S0S1, + .input_connect = NRF_GPIO_PIN_INPUT_DISCONNECT, + .pull = NRF_GPIO_PIN_NOPULL, + }; + const nrfx_gpiote_task_config_t task_config = { + .task_ch = data->keepalive_gpiote_channel, + .polarity = NRF_GPIOTE_POLARITY_TOGGLE, + .init_val = active_high ? NRF_GPIOTE_INITIAL_VALUE_LOW : NRF_GPIOTE_INITIAL_VALUE_HIGH, + }; + + ret = nrfx_gpiote_output_configure(&ip5306_keepalive_gpiote, cfg->keepalive_psel, + &output_config, &task_config); + if (ret != 0) { + goto err_free_channel; + } + + nrfx_gpiote_out_task_enable(&ip5306_keepalive_gpiote, cfg->keepalive_psel); + nrfx_gpiote_out_task_force(&ip5306_keepalive_gpiote, cfg->keepalive_psel, active_high ? 0U : 1U); + + /* + * RTC2 初始化前先检查是否已被外部占用: + * 若占用则直接返回 -EBUSY,让上层走软件保活回退。 + */ + if (nrfx_rtc_init_check(&ip5306_keepalive_rtc)) { + ret = -EBUSY; + goto err_disable_task; + } + + const nrfx_rtc_config_t rtc_cfg = NRFX_RTC_DEFAULT_CONFIG; + + ret = nrfx_rtc_init(&ip5306_keepalive_rtc, &rtc_cfg, ip5306_keepalive_rtc_handler); + if ((ret != 0) && (ret != -EALREADY)) { + goto err_disable_task; + } + + nrfx_rtc_disable(&ip5306_keepalive_rtc); + nrfx_rtc_counter_clear(&ip5306_keepalive_rtc); + + /* + * RTC 角色:时间基准,按 compare 点产生事件。 + * - CC0 到点 -> 产生 COMPARE0 event(脉冲上升沿触发点) + * - CC1 到点 -> 产生 COMPARE1 event(脉冲下降沿触发点) + * - CC2 到点 -> 触发周期边界,配合 SHORT 实现自动循环 + */ + ret = nrfx_rtc_cc_set(&ip5306_keepalive_rtc, IP5306_KEEPALIVE_RTC_CC_HIGH, period_ticks - high_ticks - 10U, false); + if (ret != 0) { + goto err_disable_task; + } + + ret = nrfx_rtc_cc_set(&ip5306_keepalive_rtc, IP5306_KEEPALIVE_RTC_CC_LOW, period_ticks - 10U, false); + if (ret != 0) { + goto err_disable_task; + } + + ret = nrfx_rtc_cc_set(&ip5306_keepalive_rtc, IP5306_KEEPALIVE_RTC_CC_PERIOD, period_ticks, false); + if (ret != 0) { + goto err_disable_task; + } + + const uint32_t set_task = active_high ? + nrfx_gpiote_set_task_address_get(&ip5306_keepalive_gpiote, cfg->keepalive_psel) : + nrfx_gpiote_clr_task_address_get(&ip5306_keepalive_gpiote, cfg->keepalive_psel); + const uint32_t clr_task = active_high ? + nrfx_gpiote_clr_task_address_get(&ip5306_keepalive_gpiote, cfg->keepalive_psel) : + nrfx_gpiote_set_task_address_get(&ip5306_keepalive_gpiote, cfg->keepalive_psel); + const uint32_t high_event = nrfx_rtc_event_address_get(&ip5306_keepalive_rtc, NRF_RTC_EVENT_COMPARE_0); + const uint32_t low_event = nrfx_rtc_event_address_get(&ip5306_keepalive_rtc, NRF_RTC_EVENT_COMPARE_1); + const uint32_t period_event = nrfx_rtc_event_address_get(&ip5306_keepalive_rtc, NRF_RTC_EVENT_COMPARE_2); + const uint32_t clear_task = nrfx_rtc_task_address_get(&ip5306_keepalive_rtc, NRF_RTC_TASK_CLEAR); + + /* + * GPPI/PPI 角色:事件路由器。 + * 将 RTC compare event 直接连接到 GPIOTE task,不经过线程/ISR。 + */ + ret = nrfx_gppi_conn_alloc(high_event, set_task, &data->keepalive_set_handle); + if (ret != 0) { + goto err_disable_task; + } + + ret = nrfx_gppi_conn_alloc(low_event, clr_task, &data->keepalive_clr_handle); + if (ret != 0) { + nrfx_gppi_conn_free(high_event, set_task, data->keepalive_set_handle); + goto err_disable_task; + } + + /* + * 某些 SoC/SDK 组合下 RTC SHORT 宏不可用,因此这里用第三条 GPPI 链路实现“周期自动清零”。 + * 这样仍保持纯硬件闭环:COMPARE2 event -> RTC CLEAR task,不需要 CPU/ISR 参与。 + */ + ret = nrfx_gppi_conn_alloc(period_event, clear_task, &data->keepalive_period_handle); + if (ret != 0) { + nrfx_gppi_conn_free(low_event, clr_task, data->keepalive_clr_handle); + nrfx_gppi_conn_free(high_event, set_task, data->keepalive_set_handle); + goto err_disable_task; + } + + /* + * GPIOTE 角色:GPIO 执行器。 + * 收到 task 后立即对目标 pin 执行 SET/CLR,输出保活脉冲波形。 + */ + nrfx_gppi_conn_enable(data->keepalive_set_handle); + nrfx_gppi_conn_enable(data->keepalive_clr_handle); + nrfx_gppi_conn_enable(data->keepalive_period_handle); + nrfx_rtc_enable(&ip5306_keepalive_rtc); + + ip5306_keepalive_hw_claimed = true; + data->backend = IP5306_KEEPALIVE_BACKEND_HW_NRF; + data->dev = dev; + + return 0; + +err_disable_task: + nrfx_gpiote_out_task_disable(&ip5306_keepalive_gpiote, cfg->keepalive_psel); +err_free_channel: + (void)nrfx_gpiote_channel_free(&ip5306_keepalive_gpiote, data->keepalive_gpiote_channel); + return ret; +} diff --git a/modules/ip5306/drivers/power/ip5306_keepalive_sw.c b/modules/ip5306/drivers/power/ip5306_keepalive_sw.c new file mode 100644 index 0000000..37a62ea --- /dev/null +++ b/modules/ip5306/drivers/power/ip5306_keepalive_sw.c @@ -0,0 +1,54 @@ +#include +#include + +#include "ip5306_priv.h" + +LOG_MODULE_DECLARE(ip5306, LOG_LEVEL_INF); + +void ip5306_keepalive_sw_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 ip5306_config *cfg = data->dev->config; + int ret; + + if ((data->backend != IP5306_KEEPALIVE_BACKEND_SW) || (data->keepalive_interval_ms == 0U)) + { + return; + } + + if (!data->keepalive_high) + { + ret = gpio_pin_set_dt(&cfg->keepalive_gpio, GPIO_OUTPUT_ACTIVE); + if (ret != 0) + { + LOG_ERR("SW keepalive set high failed: %d", ret); + (void)k_work_schedule(&data->keepalive_work, K_MSEC(data->keepalive_interval_ms)); + return; + } + + data->keepalive_high = true; + (void)k_work_schedule(&data->keepalive_work, K_MSEC(data->keepalive_pulse_ms)); + } + else + { + ret = gpio_pin_set_dt(&cfg->keepalive_gpio, GPIO_OUTPUT_INACTIVE); + if (ret != 0) + { + LOG_ERR("SW keepalive set low failed: %d", ret); + } + + data->keepalive_high = false; + (void)k_work_schedule(&data->keepalive_work, K_MSEC(data->keepalive_interval_ms)); + } +} + +int ip5306_keepalive_sw_start(const struct device *dev) +{ + struct ip5306_data *data = dev->data; + + data->backend = IP5306_KEEPALIVE_BACKEND_SW; + data->dev = dev; + k_work_init_delayable(&data->keepalive_work, ip5306_keepalive_sw_work_handler); + return (int)k_work_schedule(&data->keepalive_work, K_MSEC(data->keepalive_interval_ms)); +} diff --git a/modules/ip5306/drivers/power/ip5306_priv.h b/modules/ip5306/drivers/power/ip5306_priv.h new file mode 100644 index 0000000..1b3d211 --- /dev/null +++ b/modules/ip5306/drivers/power/ip5306_priv.h @@ -0,0 +1,58 @@ +#ifndef IP5306_KEEPALIVE_PRIV_H_ +#define IP5306_KEEPALIVE_PRIV_H_ + +#include +#include + +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF) +#include +#endif + +#define IP5306_KEEPALIVE_DEFAULT_PULSE_MS 100U +#define IP5306_KEEPALIVE_DEFAULT_INTERVAL_MS 20000U + +enum ip5306_keepalive_backend { + IP5306_KEEPALIVE_BACKEND_NONE, + IP5306_KEEPALIVE_BACKEND_SW, + IP5306_KEEPALIVE_BACKEND_HW_NRF, +}; + +struct ip5306_config { + struct i2c_dt_spec i2c; + struct gpio_dt_spec keepalive_gpio; +#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF) + uint32_t keepalive_psel; +#endif + uint32_t keepalive_pulse_ms; + uint32_t keepalive_interval_ms; + bool has_keepalive_gpio; + bool keepalive_offload; +}; + +struct ip5306_data { + struct k_work_delayable keepalive_work; + bool keepalive_high; + uint32_t keepalive_pulse_ms; + uint32_t keepalive_interval_ms; + enum ip5306_keepalive_backend backend; + const struct device *dev; + +#if IS_ENABLED(CONFIG_IP5306_KEEPALIVE_HW_NRF) + uint8_t keepalive_gpiote_channel; + nrfx_gppi_handle_t keepalive_set_handle; + nrfx_gppi_handle_t keepalive_clr_handle; + nrfx_gppi_handle_t keepalive_period_handle; +#endif +}; + +int ip5306_keepalive_sw_start(const struct device *dev); + +int ip5306_keepalive_hw_nrf_start(const struct device *dev); + +#endif /* IP5306_KEEPALIVE_PRIV_H_ */ diff --git a/modules/ip5306/dts/bindings/power/injoinic,ip5306.yaml b/modules/ip5306/dts/bindings/power/injoinic,ip5306.yaml new file mode 100644 index 0000000..ae7eb49 --- /dev/null +++ b/modules/ip5306/dts/bindings/power/injoinic,ip5306.yaml @@ -0,0 +1,33 @@ +title: Injoinic IP5306 PMIC + +description: | + Injoinic IP5306 power management IC accessed over I2C. + This binding exposes two status bits and an optional KEY pin keepalive + pulse output. + +compatible: "injoinic,ip5306" + +include: i2c-device.yaml + +properties: + keepalive-gpios: + type: phandle-array + description: | + GPIO connected to KEY pin. The driver sends periodic pulses on this pin + to keep boost output alive. + + keepalive-pulse-ms: + type: int + default: 100 + description: Pulse width in milliseconds for each keepalive pulse. + + keepalive-interval-ms: + type: int + default: 20000 + description: Interval in milliseconds between keepalive pulses. + + keepalive-offload: + type: boolean + description: | + Prefer hardware keepalive offload backend when available on this SoC. + If not set, driver always uses software keepalive backend. diff --git a/modules/ip5306/dts/bindings/vendor-prefixes.txt b/modules/ip5306/dts/bindings/vendor-prefixes.txt new file mode 100644 index 0000000..262ee04 --- /dev/null +++ b/modules/ip5306/dts/bindings/vendor-prefixes.txt @@ -0,0 +1 @@ +injoinic Injoinic diff --git a/modules/ip5306/include/zephyr/drivers/power/ip5306.h b/modules/ip5306/include/zephyr/drivers/power/ip5306.h new file mode 100644 index 0000000..97a75e4 --- /dev/null +++ b/modules/ip5306/include/zephyr/drivers/power/ip5306.h @@ -0,0 +1,48 @@ +#ifndef ZEPHYR_INCLUDE_DRIVERS_POWER_IP5306_H_ +#define ZEPHYR_INCLUDE_DRIVERS_POWER_IP5306_H_ + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int (*ip5306_get_flag_t)(const struct device *dev, bool *flag); + +struct ip5306_driver_api { + ip5306_get_flag_t is_charging; + ip5306_get_flag_t is_charge_full; +}; + +static inline int ip5306_is_charging(const struct device *dev, bool *charging) +{ + const struct ip5306_driver_api *api = + (const struct ip5306_driver_api *)dev->api; + + if (api == NULL || api->is_charging == NULL) { + return -ENOSYS; + } + + return api->is_charging(dev, charging); +} + +static inline int ip5306_is_charge_full(const struct device *dev, bool *full) +{ + const struct ip5306_driver_api *api = + (const struct ip5306_driver_api *)dev->api; + + if (api == NULL || api->is_charge_full == NULL) { + return -ENOSYS; + } + + return api->is_charge_full(dev, full); +} + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_POWER_IP5306_H_ */ diff --git a/modules/ip5306/zephyr/module.yml b/modules/ip5306/zephyr/module.yml new file mode 100644 index 0000000..219b2cf --- /dev/null +++ b/modules/ip5306/zephyr/module.yml @@ -0,0 +1,5 @@ +build: + cmake: . + kconfig: Kconfig + settings: + dts_root: .