| /* |
| * Copyright (c) 2024 Microchip Technology Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT microchip_mec5_gpio |
| |
| #include <errno.h> |
| #include <zephyr/arch/cpu.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/dt-bindings/gpio/gpio.h> |
| #include <zephyr/dt-bindings/pinctrl/mchp-xec-pinctrl.h> |
| #include <soc.h> |
| #include <zephyr/irq.h> |
| #include <mec_gpio_api.h> |
| |
| #include <zephyr/drivers/gpio/gpio_utils.h> |
| |
| /* 32 pins per bank. Each pin has a 4-byte control register */ |
| #define MEC5_GPIO_PIN_CTRL_RSHFT 7 |
| #define MEC5_GPIO_PIN_CTRL_ADDR_MSK 0xfu |
| |
| struct gpio_mec5_data { |
| /* gpio_driver_data needs to be first */ |
| struct gpio_driver_data common; |
| /* port ISR callback routine address */ |
| sys_slist_t callbacks; |
| }; |
| |
| struct gpio_mec5_config { |
| /* gpio_driver_config needs to be first */ |
| struct gpio_driver_config common; |
| uintptr_t pcr1_base; |
| uintptr_t parin_addr; |
| uintptr_t parout_addr; |
| uint32_t flags; |
| }; |
| |
| static inline uint32_t mec5_addr_to_port(uint32_t base_addr) |
| { |
| return ((base_addr >> MEC5_GPIO_PIN_CTRL_RSHFT) & MEC5_GPIO_PIN_CTRL_ADDR_MSK); |
| } |
| |
| /* NOTE: gpio_flags_t b[0:15] are defined in the dt-binding gpio header. |
| * b[31:16] are defined in the driver gpio header. |
| */ |
| static int gpio_mec5_validate_flags(gpio_flags_t flags) |
| { |
| if (flags & GPIO_LINE_OPEN_SOURCE) { |
| return -ENOTSUP; |
| } |
| |
| if ((flags & GPIO_OUTPUT_INIT_LOW) && (flags & GPIO_OUTPUT_INIT_HIGH)) { |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static const struct mec_gpio_props cfg_props_init[] = { |
| {MEC_GPIO_PWRGT_PROP_ID, MEC_GPIO_PROP_PWRGT_VTR}, |
| {MEC_GPIO_OSEL_PROP_ID, MEC_GPIO_PROP_OSEL_CTRL}, |
| {MEC_GPIO_INPAD_DIS_PROP_ID, MEC_GPIO_PROP_INPAD_EN}, |
| }; |
| |
| /* Each GPIO pin has two 32-bit control registers. Control 1 configures pin |
| * features except for drive strength and slew rate in Control 2. |
| * A pin's input and output state can be read/written from either the Control 1 |
| * register or from corresponding bits in the GPIO parallel input/output registers. |
| * The parallel input and output registers group 32 pins into each register. |
| * The GPIO hardware restricts the pin output state to Control 1 or the parallel bit. |
| * Both output bits reflect each other on read and writes but only one is writable |
| * selected by the output control select bit in Control 1. In the configuration API |
| * we use Control 1 to configure all pin features and output state. Before exiting, |
| * we set the output select for parallel mode enabling writes to the parallel output bit. |
| */ |
| static int gpio_mec5_configure(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) |
| { |
| const struct gpio_mec5_config *config = dev->config; |
| uint32_t pin_num = 0u; |
| uint32_t port_num = 0u; |
| uint32_t temp = 0u; |
| int ret = 0; |
| size_t idx = 0; |
| struct mec_gpio_props props[8]; |
| |
| port_num = mec5_addr_to_port(config->pcr1_base); |
| ret = mec_hal_gpio_pin_num(port_num, pin, &pin_num); |
| if (ret != MEC_RET_OK) { |
| return -EINVAL; |
| } |
| |
| ret = mec_hal_gpio_port_pin_valid(port_num, pin); |
| if (ret != MEC_RET_OK) { |
| return -EINVAL; |
| } |
| |
| ret = gpio_mec5_validate_flags(flags); |
| if (ret) { |
| return ret; |
| } |
| |
| if (flags == GPIO_DISCONNECTED) { |
| ret = mec_hal_gpio_set_property(pin_num, MEC_GPIO_PWRGT_PROP_ID, |
| MEC_GPIO_PROP_PWRGT_OFF); |
| if (ret != MEC_RET_OK) { |
| ret = -EIO; |
| } |
| return ret; |
| } |
| |
| ret = mec_hal_gpio_set_props(pin_num, cfg_props_init, ARRAY_SIZE(cfg_props_init)); |
| if (ret != MEC_RET_OK) { |
| return -EIO; |
| } |
| |
| if (flags & GPIO_OUTPUT) { |
| props[idx].prop = MEC_GPIO_DIR_PROP_ID; |
| props[idx].val = MEC_GPIO_PROP_DIR_OUT; |
| idx++; |
| |
| props[idx].prop = MEC_GPIO_OBUFT_PROP_ID; |
| props[idx].val = MEC_GPIO_PROP_PUSH_PULL; |
| if (flags & GPIO_LINE_OPEN_DRAIN) { |
| props[idx].val = MEC_GPIO_PROP_OPEN_DRAIN; |
| } |
| idx++; |
| |
| props[idx].prop = MEC_GPIO_CTRL_OUT_VAL_ID; |
| props[idx].val = 0u; |
| if (flags & GPIO_OUTPUT_INIT_HIGH) { |
| props[idx].val = 1u; |
| } else if (flags & GPIO_OUTPUT_INIT_LOW) { |
| props[idx].val = 0u; |
| } else { |
| mec_hal_gpio_pad_in(pin_num, &props[idx].val); |
| } |
| idx++; |
| } |
| |
| if (flags & GPIO_INPUT) { |
| props[idx].prop = MEC_GPIO_DIR_PROP_ID; |
| props[idx].val = MEC_GPIO_PROP_DIR_IN; |
| idx++; |
| } |
| |
| temp = flags & (GPIO_PULL_UP | GPIO_PULL_DOWN); |
| if (temp) { |
| props[idx].prop = MEC_GPIO_PUD_PROP_ID; |
| if (temp == (GPIO_PULL_UP | GPIO_PULL_DOWN)) { |
| props[idx].val = MEC_GPIO_PROP_REPEATER; |
| } else if (temp & GPIO_PULL_UP) { |
| props[idx].val = MEC_GPIO_PROP_PULL_UP; |
| } else { |
| props[idx].val = MEC_GPIO_PROP_PULL_DN; |
| } |
| idx++; |
| } |
| |
| ret = mec_hal_gpio_set_props(pin_num, props, idx); |
| if (ret != MEC_RET_OK) { |
| return -EIO; |
| } |
| |
| /* make output state in control read-only in control and read-write in parallel reg */ |
| ret = mec_hal_gpio_set_property(pin_num, MEC_GPIO_OSEL_PROP_ID, MEC_GPIO_PROP_OSEL_PAROUT); |
| if (ret != MEC_RET_OK) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static uint8_t gen_gpio_ctrl_icfg(enum gpio_int_mode mode, enum gpio_int_trig trig) |
| { |
| uint8_t idet; |
| |
| if (mode == GPIO_INT_MODE_DISABLED) { |
| idet = MEC_GPIO_PROP_IDET_DIS; |
| } else { |
| if (mode == GPIO_INT_MODE_LEVEL) { |
| if (trig == GPIO_INT_TRIG_HIGH) { |
| idet = MEC_GPIO_PROP_IDET_HI_LVL; |
| } else { |
| idet = MEC_GPIO_PROP_IDET_LO_LVL; |
| } |
| } else { |
| switch (trig) { |
| case GPIO_INT_TRIG_LOW: |
| idet = MEC_GPIO_PROP_IDET_EDGE_DN; |
| break; |
| case GPIO_INT_TRIG_HIGH: |
| idet = MEC_GPIO_PROP_IDET_EDGE_UP; |
| break; |
| case GPIO_INT_TRIG_BOTH: |
| idet = MEC_GPIO_PROP_IDET_EDGE_BOTH; |
| break; |
| default: |
| idet = MEC_GPIO_PROP_IDET_DIS; |
| break; |
| } |
| } |
| } |
| |
| return idet; |
| } |
| |
| /* Enable interrupt to propagate via its GIRQ to the NVIC */ |
| static void gpio_mec5_intr_en(uint8_t port, gpio_pin_t pin, enum gpio_int_mode mode) |
| { |
| uint8_t en = 0u; |
| |
| if (mode != GPIO_INT_MODE_DISABLED) { |
| en = 1u; |
| } |
| |
| mec_hal_gpio_port_pin_ia_enable(port, pin, en); |
| } |
| |
| static const struct mec_gpio_props icfg_props_init[] = { |
| {MEC_GPIO_PWRGT_PROP_ID, MEC_GPIO_PROP_PWRGT_VTR}, |
| {MEC_GPIO_INPAD_DIS_PROP_ID, MEC_GPIO_PROP_INPAD_EN}, |
| }; |
| |
| static int gpio_mec5_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin, |
| enum gpio_int_mode mode, enum gpio_int_trig trig) |
| { |
| const struct gpio_mec5_config *config = dev->config; |
| uint32_t pin_num = (uint32_t)MEC_PIN_MAX; |
| uint32_t port_num = 0; |
| int ret = 0; |
| uint8_t idet = 0, idet_curr = 0; |
| |
| /* Validate pin number range in terms of current port */ |
| port_num = mec5_addr_to_port(config->pcr1_base); |
| ret = mec_hal_gpio_port_pin_valid(port_num, pin); |
| if (ret != MEC_RET_OK) { |
| return -EINVAL; |
| } |
| |
| /* Check if GPIO port supports interrupts */ |
| if ((mode != GPIO_INT_MODE_DISABLED) && !(config->flags & GPIO_INT_ENABLE)) { |
| return -ENOTSUP; |
| } |
| |
| /* Disable interrupt in the EC aggregator */ |
| gpio_mec5_intr_en(port_num, pin, 0); |
| |
| mec_hal_gpio_pin_num(port_num, pin, &pin_num); |
| ret = mec_hal_gpio_set_props(pin_num, icfg_props_init, ARRAY_SIZE(icfg_props_init)); |
| if (ret) { |
| return -EIO; |
| } |
| |
| idet_curr = MEC_GPIO_PROP_IDET_DIS; |
| ret = mec_hal_gpio_get_property(pin_num, MEC_GPIO_IDET_PROP_ID, &idet_curr); |
| if (ret != MEC_RET_OK) { |
| return -EIO; |
| } |
| |
| idet = gen_gpio_ctrl_icfg(mode, trig); |
| if (idet_curr == idet) { |
| gpio_mec5_intr_en(port_num, pin, mode); |
| return 0; |
| } |
| |
| ret = mec_hal_gpio_set_property(pin_num, MEC_GPIO_IDET_PROP_ID, idet); |
| if (ret != MEC_RET_OK) { |
| return -EIO; |
| } |
| |
| ret = mec_hal_gpio_pin_ia_status_clr(pin_num); |
| if (ret != MEC_RET_OK) { |
| return -EIO; |
| } |
| |
| gpio_mec5_intr_en(port_num, pin, mode); |
| |
| return 0; |
| } |
| |
| static int gpio_mec5_port_set_masked_raw(const struct device *dev, uint32_t mask, uint32_t value) |
| { |
| const struct gpio_mec5_config *config = dev->config; |
| uint32_t port_num = mec5_addr_to_port(config->pcr1_base); |
| int ret = mec_hal_gpio_parout_port_mask(port_num, value, (const uint32_t)mask); |
| |
| if (ret != MEC_RET_OK) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int gpio_mec5_port_set_bits_raw(const struct device *dev, uint32_t mask) |
| { |
| const struct gpio_mec5_config *config = dev->config; |
| uint32_t port_num = mec5_addr_to_port(config->pcr1_base); |
| int ret = mec_hal_gpio_parout_port_set_bits(port_num, (const uint32_t)mask); |
| |
| if (ret != MEC_RET_OK) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int gpio_mec5_port_clear_bits_raw(const struct device *dev, uint32_t mask) |
| { |
| const struct gpio_mec5_config *config = dev->config; |
| uint32_t port_num = mec5_addr_to_port(config->pcr1_base); |
| int ret = mec_hal_gpio_parout_port_mask(port_num, 0u, (const uint32_t)mask); |
| |
| if (ret != MEC_RET_OK) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int gpio_mec5_port_toggle_bits(const struct device *dev, uint32_t mask) |
| { |
| const struct gpio_mec5_config *config = dev->config; |
| uint32_t port_num = mec5_addr_to_port(config->pcr1_base); |
| |
| if (mec_hal_gpio_parout_port_xor(port_num, mask) != MEC_RET_OK) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int gpio_mec5_port_get_raw(const struct device *dev, uint32_t *value) |
| { |
| const struct gpio_mec5_config *config = dev->config; |
| uint32_t port_num = mec5_addr_to_port(config->pcr1_base); |
| |
| if (mec_hal_gpio_parin_port(port_num, value) != MEC_RET_OK) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int gpio_mec5_manage_callback(const struct device *dev, struct gpio_callback *callback, |
| bool set) |
| { |
| struct gpio_mec5_data *data = dev->data; |
| |
| gpio_manage_callback(&data->callbacks, callback, set); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_GPIO_GET_DIRECTION |
| static int gpio_mec5_get_direction(const struct device *port, gpio_port_pins_t map, |
| gpio_port_pins_t *inputs, gpio_port_pins_t *outputs) |
| { |
| if (!port) { |
| return -EINVAL; |
| } |
| |
| const struct gpio_mec5_config *config = port->config; |
| uint32_t valid_msk = 0u; |
| uint32_t pin_num = 0u, port_num = 0u; |
| int ret = 0; |
| uint8_t pwr_gate = 0u, dir = 0u, in_pad_dis = 0u; |
| |
| port_num = mec5_addr_to_port(config->pcr1_base); |
| ret = mec_hal_gpio_port_valid_mask(port_num, &valid_msk); |
| if (ret != MEC_RET_OK) { |
| return -EIO; |
| } |
| |
| *inputs = 0u; |
| *outputs = 0u; |
| for (uint8_t pin_pos = 0; pin_pos < 32; pin++) { |
| if (!map) { |
| break; |
| } |
| if ((map & BIT(pin_pos)) && (valid_msk & BIT(pin_pos))) { |
| mec_hal_gpio_pin_num(port_num, pin, &pin_num); |
| mec_hal_gpio_get_property(pin_num, MEC_GPIO_PWRGT_PROP_ID, &pwr_gate); |
| mec_hal_gpio_get_property(pin_num, MEC_GPIO_DIR_PROP_ID, &dir); |
| mec_hal_gpio_get_property(pin_num, MEC_GPIO_INPAD_DIS_PROP_ID, &in_pad_dis); |
| |
| if (pwr_gate != MEC_GPIO_PROP_PWRGT_OFF) { |
| if (outputs && (dir == MEC_GPIO_PROP_DIR_OUT)) { |
| *outputs |= BIT(pin_pos); |
| } else if (inputs && (in_pad_dis == MEC_GPIO_PROP_INPAD_EN)) { |
| *inputs |= BIT(pin_pos); |
| } |
| } |
| |
| map &= ~BIT(pin_pos); |
| } |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| #ifdef CONFIG_GPIO_GET_CONFIG |
| int gpio_mec5_get_config(const struct device *port, gpio_pin_t pin, gpio_flags_t *flags) |
| { |
| if (!port || !flags) { |
| return -EINVAL; |
| } |
| |
| const struct gpio_mec5_config *config = port->config; |
| uint32_t port_num = mec5_addr_to_port(config->pcr1_base); |
| int ret = mec_hal_gpio_port_pin_valid(port_num, pin); |
| |
| if (ret != MEC_RET_OK) { |
| return -EINVAL; |
| } |
| |
| uint32_t pin_ctrl = mec_hal_gpio_port_get_ctrl_nc(port_num, pin); |
| uint32_t pin_flags = 0u; |
| uint8_t prop = MEC_GPIO_PROP_DIR_IN; |
| |
| mec_hal_gpio_get_ctrl_property(pin_ctrl, MEC_GPIO_DIR_PROP_ID, &prop); |
| |
| if (prop == MEC_GPIO_PROP_DIR_OUT) { |
| pin_flags |= GPIO_OUTPUT; |
| mec_hal_gpio_get_ctrl_property(pin_ctrl, MEC_GPIO_CTRL_OUT_VAL_ID, &prop); |
| if (prop != 0) { |
| pin_flags |= GPIO_OUTPUT_INIT_HIGH; |
| } else { |
| pin_flags |= GPIO_OUTPUT_INIT_LOW; |
| } |
| |
| prop = MEC_GPIO_PROP_PUSH_PULL; |
| mec_hal_gpio_get_ctrl_property(pin_ctrl, MEC_GPIO_OBUFT_PROP_ID, &prop); |
| if (prop == MEC_GPIO_PROP_OPEN_DRAIN) { |
| pin_flags |= GPIO_OPEN_DRAIN; |
| } |
| } else { |
| prop = MEC_GPIO_PROP_INPAD_DIS; |
| mec_hal_gpio_get_ctrl_property(pin_ctrl, MEC_GPIO_INPAD_DIS_PROP_ID, &prop); |
| if (prop == MEC_GPIO_PROP_INPAD_EN) { |
| pin_flags |= GPIO_INPUT; |
| } |
| } |
| |
| if (pin_flags) { |
| *flags = pin_flags; |
| } else { |
| *flags = GPIO_DISCONNECTED; |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| static void gpio_mec5_port_isr(const struct device *dev) |
| { |
| const struct gpio_mec5_config *config = dev->config; |
| struct gpio_mec5_data *data = dev->data; |
| uint32_t girq_result = 0xffffffffu; |
| uint32_t port_num = 0u; |
| |
| /* Figure out which interrupts have been triggered from the EC |
| * aggregator result register |
| */ |
| port_num = mec5_addr_to_port(config->pcr1_base); |
| mec_hal_gpio_port_ia_result(port_num, &girq_result); |
| |
| /* Clear source register in aggregator before firing callbacks */ |
| mec_hal_gpio_port_ia_status_clr_mask(port_num, girq_result); |
| |
| gpio_fire_callbacks(&data->callbacks, dev, girq_result); |
| } |
| |
| /* GPIO driver official API table */ |
| static DEVICE_API(gpio, gpio_mec5_driver_api) = { |
| .pin_configure = gpio_mec5_configure, |
| .port_get_raw = gpio_mec5_port_get_raw, |
| .port_set_masked_raw = gpio_mec5_port_set_masked_raw, |
| .port_set_bits_raw = gpio_mec5_port_set_bits_raw, |
| .port_clear_bits_raw = gpio_mec5_port_clear_bits_raw, |
| .port_toggle_bits = gpio_mec5_port_toggle_bits, |
| .pin_interrupt_configure = gpio_mec5_pin_interrupt_configure, |
| .manage_callback = gpio_mec5_manage_callback, |
| #ifdef CONFIG_GPIO_GET_DIRECTION |
| .port_get_direction = gpio_mec5_get_direction, |
| #endif |
| #ifdef CONFIG_GPIO_GET_CONFIG |
| .pin_get_config = gpio_mec5_get_config, |
| #endif |
| }; |
| |
| #define MEC5_GPIO_PORT_FLAGS(n) ((DT_INST_IRQ_HAS_CELL(n, irq)) ? GPIO_INT_ENABLE : 0) |
| |
| /* TODO remove port_num. Derive it from pin & pin_num? */ |
| #define MEC5_GPIO_PORT(n) \ |
| static int gpio_mec5_port_init_##n(const struct device *dev) \ |
| { \ |
| if (!(DT_INST_IRQ_HAS_CELL(n, irq))) { \ |
| return 0; \ |
| } \ |
| \ |
| const struct gpio_mec5_config *config = dev->config; \ |
| uint32_t port_num = mec5_addr_to_port(config->pcr1_base); \ |
| \ |
| mec_hal_gpio_port_ia_ctrl(port_num, 1u); \ |
| IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), gpio_mec5_port_isr, \ |
| DEVICE_DT_INST_GET(n), 0u); \ |
| irq_enable(DT_INST_IRQN(n)); \ |
| return 0; \ |
| } \ |
| static struct gpio_mec5_data gpio_mec5_port_data_##n; \ |
| static const struct gpio_mec5_config gpio_mec5_config_##n = { \ |
| .common = \ |
| { \ |
| .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(n), \ |
| }, \ |
| .pcr1_base = (uintptr_t)DT_INST_REG_ADDR_BY_IDX(n, 0), \ |
| .parin_addr = (uintptr_t)DT_INST_REG_ADDR_BY_IDX(n, 1), \ |
| .parout_addr = (uintptr_t)DT_INST_REG_ADDR_BY_IDX(n, 2), \ |
| .flags = MEC5_GPIO_PORT_FLAGS(n), \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(n, gpio_mec5_port_init_##n, NULL, &gpio_mec5_port_data_##n, \ |
| &gpio_mec5_config_##n, PRE_KERNEL_1, CONFIG_GPIO_INIT_PRIORITY, \ |
| &gpio_mec5_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(MEC5_GPIO_PORT) |