| /* |
| * Copyright (c) 2020, Antmicro <www.antmicro.com> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT quicklogic_eos_s3_gpio |
| |
| #include <errno.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/irq.h> |
| #include <soc.h> |
| #include <eoss3_hal_gpio.h> |
| #include <eoss3_hal_pads.h> |
| #include <eoss3_hal_pad_config.h> |
| |
| #include <zephyr/drivers/gpio/gpio_utils.h> |
| |
| #define MAX_GPIOS 8U |
| #define GPIOS_MASK (BIT(MAX_GPIOS) - 1) |
| #define DISABLED_GPIO_IRQ 0xFFU |
| |
| struct gpio_eos_s3_config { |
| /* gpio_driver_config needs to be first */ |
| struct gpio_driver_config common; |
| /* Pin configuration to determine whether use primary |
| * or secondary pin for a target GPIO. Secondary pin is used |
| * when the proper bit is set to 1. |
| * |
| * bit_index : primary_pin_number / secondary_pin_number |
| * |
| * 0 : 6 / 24 |
| * 1 : 9 / 26 |
| * 2 : 11 / 28 |
| * 3 : 14 / 30 |
| * 4 : 18 / 31 |
| * 5 : 21 / 36 |
| * 6 : 22 / 38 |
| * 7 : 23 / 45 |
| */ |
| uint8_t pin_secondary_config; |
| }; |
| |
| struct gpio_eos_s3_data { |
| /* gpio_driver_data needs to be first */ |
| struct gpio_driver_data common; |
| /* port ISR callback routine address */ |
| sys_slist_t callbacks; |
| /* array of interrupts mapped to the gpio number */ |
| uint8_t gpio_irqs[MAX_GPIOS]; |
| }; |
| |
| /* Connection table to configure GPIOs with pads */ |
| static const PadConfig pad_configs[] = { |
| {.ucPin = PAD_6, .ucFunc = PAD6_FUNC_SEL_GPIO_0}, |
| {.ucPin = PAD_9, .ucFunc = PAD9_FUNC_SEL_GPIO_1}, |
| {.ucPin = PAD_11, .ucFunc = PAD11_FUNC_SEL_GPIO_2}, |
| {.ucPin = PAD_14, .ucFunc = PAD14_FUNC_SEL_GPIO_3}, |
| {.ucPin = PAD_18, .ucFunc = PAD18_FUNC_SEL_GPIO_4}, |
| {.ucPin = PAD_21, .ucFunc = PAD21_FUNC_SEL_GPIO_5}, |
| {.ucPin = PAD_22, .ucFunc = PAD22_FUNC_SEL_GPIO_6}, |
| {.ucPin = PAD_23, .ucFunc = PAD23_FUNC_SEL_GPIO_7}, |
| {.ucPin = PAD_24, .ucFunc = PAD24_FUNC_SEL_GPIO_0}, |
| {.ucPin = PAD_26, .ucFunc = PAD26_FUNC_SEL_GPIO_1}, |
| {.ucPin = PAD_28, .ucFunc = PAD28_FUNC_SEL_GPIO_2}, |
| {.ucPin = PAD_30, .ucFunc = PAD30_FUNC_SEL_GPIO_3}, |
| {.ucPin = PAD_31, .ucFunc = PAD31_FUNC_SEL_GPIO_4}, |
| {.ucPin = PAD_36, .ucFunc = PAD36_FUNC_SEL_GPIO_5}, |
| {.ucPin = PAD_38, .ucFunc = PAD38_FUNC_SEL_GPIO_6}, |
| {.ucPin = PAD_45, .ucFunc = PAD45_FUNC_SEL_GPIO_7}, |
| }; |
| |
| static PadConfig gpio_eos_s3_pad_select(const struct device *dev, |
| uint8_t gpio_num) |
| { |
| const struct gpio_eos_s3_config *config = dev->config; |
| uint8_t is_secondary = (config->pin_secondary_config >> gpio_num) & 1; |
| |
| return pad_configs[(MAX_GPIOS * is_secondary) + gpio_num]; |
| } |
| |
| /* This function maps pad number to IRQ number */ |
| static int gpio_eos_s3_get_irq_num(uint8_t pad) |
| { |
| int gpio_irq_num; |
| |
| switch (pad) { |
| case PAD_6: |
| gpio_irq_num = 1; |
| break; |
| case PAD_9: |
| gpio_irq_num = 3; |
| break; |
| case PAD_11: |
| gpio_irq_num = 5; |
| break; |
| case PAD_14: |
| gpio_irq_num = 5; |
| break; |
| case PAD_18: |
| gpio_irq_num = 1; |
| break; |
| case PAD_21: |
| gpio_irq_num = 2; |
| break; |
| case PAD_22: |
| gpio_irq_num = 3; |
| break; |
| case PAD_23: |
| gpio_irq_num = 7; |
| break; |
| case PAD_24: |
| gpio_irq_num = 1; |
| break; |
| case PAD_26: |
| gpio_irq_num = 4; |
| break; |
| case PAD_28: |
| gpio_irq_num = 3; |
| break; |
| case PAD_30: |
| gpio_irq_num = 5; |
| break; |
| case PAD_31: |
| gpio_irq_num = 6; |
| break; |
| case PAD_36: |
| gpio_irq_num = 1; |
| break; |
| case PAD_38: |
| gpio_irq_num = 2; |
| break; |
| case PAD_45: |
| gpio_irq_num = 5; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return gpio_irq_num; |
| } |
| |
| static int gpio_eos_s3_configure(const struct device *dev, |
| gpio_pin_t gpio_num, |
| gpio_flags_t flags) |
| { |
| uint32_t *io_mux = (uint32_t *)IO_MUX; |
| GPIOCfgTypeDef gpio_cfg; |
| PadConfig pad_config = gpio_eos_s3_pad_select(dev, gpio_num); |
| |
| if (flags & GPIO_SINGLE_ENDED) { |
| return -ENOTSUP; |
| } |
| |
| gpio_cfg.ucGpioNum = gpio_num; |
| gpio_cfg.xPadConf = &pad_config; |
| |
| /* Configure PAD */ |
| if (flags & GPIO_PULL_UP) { |
| gpio_cfg.xPadConf->ucPull = PAD_PULLUP; |
| } else if (flags & GPIO_PULL_DOWN) { |
| gpio_cfg.xPadConf->ucPull = PAD_PULLDOWN; |
| } else { |
| /* High impedance */ |
| gpio_cfg.xPadConf->ucPull = PAD_NOPULL; |
| } |
| |
| if ((flags & GPIO_INPUT) != 0) { |
| gpio_cfg.xPadConf->ucMode = PAD_MODE_INPUT_EN; |
| gpio_cfg.xPadConf->ucSmtTrg = PAD_SMT_TRIG_EN; |
| } |
| |
| if ((flags & GPIO_OUTPUT) != 0) { |
| if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0) { |
| MISC_CTRL->IO_OUTPUT |= (BIT(gpio_num) & GPIOS_MASK); |
| } else if ((flags & GPIO_OUTPUT_INIT_LOW) != 0) { |
| MISC_CTRL->IO_OUTPUT &= ~(BIT(gpio_num) & GPIOS_MASK); |
| } |
| gpio_cfg.xPadConf->ucMode = PAD_MODE_OUTPUT_EN; |
| } |
| |
| if (flags == GPIO_DISCONNECTED) { |
| gpio_cfg.xPadConf->ucMode = PAD_MODE_INPUT_EN; |
| gpio_cfg.xPadConf->ucSmtTrg = PAD_SMT_TRIG_DIS; |
| } |
| |
| /* Initial PAD configuration */ |
| HAL_PAD_Config(gpio_cfg.xPadConf); |
| |
| /* Override direction setup to support bidirectional config */ |
| if ((flags & GPIO_DIR_MASK) == (GPIO_INPUT | GPIO_OUTPUT)) { |
| io_mux += gpio_cfg.xPadConf->ucPin; |
| *io_mux &= ~PAD_OEN_DISABLE; |
| *io_mux |= PAD_REN_ENABLE; |
| } |
| |
| return 0; |
| } |
| |
| static int gpio_eos_s3_port_get_raw(const struct device *dev, |
| uint32_t *value) |
| { |
| ARG_UNUSED(dev); |
| |
| *value = (MISC_CTRL->IO_INPUT & GPIOS_MASK); |
| |
| return 0; |
| } |
| |
| static int gpio_eos_s3_port_set_masked_raw(const struct device *dev, |
| uint32_t mask, |
| uint32_t value) |
| { |
| ARG_UNUSED(dev); |
| uint32_t target_value; |
| uint32_t output_states = MISC_CTRL->IO_OUTPUT; |
| |
| target_value = ((output_states & ~mask) | (value & mask)); |
| MISC_CTRL->IO_OUTPUT = (target_value & GPIOS_MASK); |
| |
| return 0; |
| } |
| |
| static int gpio_eos_s3_port_set_bits_raw(const struct device *dev, |
| uint32_t mask) |
| { |
| ARG_UNUSED(dev); |
| |
| MISC_CTRL->IO_OUTPUT |= (mask & GPIOS_MASK); |
| |
| return 0; |
| } |
| |
| static int gpio_eos_s3_port_clear_bits_raw(const struct device *dev, |
| uint32_t mask) |
| { |
| ARG_UNUSED(dev); |
| |
| MISC_CTRL->IO_OUTPUT &= ~(mask & GPIOS_MASK); |
| |
| return 0; |
| } |
| |
| static int gpio_eos_s3_port_toggle_bits(const struct device *dev, |
| uint32_t mask) |
| { |
| ARG_UNUSED(dev); |
| uint32_t target_value; |
| uint32_t output_states = MISC_CTRL->IO_OUTPUT; |
| |
| target_value = output_states ^ mask; |
| MISC_CTRL->IO_OUTPUT = (target_value & GPIOS_MASK); |
| |
| return 0; |
| } |
| |
| static int gpio_eos_s3_manage_callback(const struct device *dev, |
| struct gpio_callback *callback, bool set) |
| { |
| struct gpio_eos_s3_data *data = dev->data; |
| |
| return gpio_manage_callback(&data->callbacks, callback, set); |
| } |
| |
| static int gpio_eos_s3_pin_interrupt_configure(const struct device *dev, |
| gpio_pin_t gpio_num, |
| enum gpio_int_mode mode, |
| enum gpio_int_trig trig) |
| { |
| struct gpio_eos_s3_data *data = dev->data; |
| GPIOCfgTypeDef gpio_cfg; |
| PadConfig pad_config = gpio_eos_s3_pad_select(dev, gpio_num); |
| |
| gpio_cfg.ucGpioNum = gpio_num; |
| gpio_cfg.xPadConf = &pad_config; |
| |
| if (mode == GPIO_INT_MODE_DISABLED) { |
| /* Get IRQ number which should be disabled */ |
| int irq_num = gpio_eos_s3_get_irq_num(pad_config.ucPin); |
| |
| if (irq_num < 0) { |
| return -EINVAL; |
| } |
| |
| /* Disable IRQ */ |
| INTR_CTRL->GPIO_INTR_EN_M4 &= ~BIT((uint32_t)irq_num); |
| |
| /* Mark corresponding IRQ number as disabled */ |
| data->gpio_irqs[irq_num] = DISABLED_GPIO_IRQ; |
| |
| /* Clear configuration */ |
| INTR_CTRL->GPIO_INTR_TYPE &= ~((uint32_t)(BIT(irq_num))); |
| INTR_CTRL->GPIO_INTR_POL &= ~((uint32_t)(BIT(irq_num))); |
| } else { |
| /* Prepare configuration */ |
| if (mode == GPIO_INT_MODE_LEVEL) { |
| gpio_cfg.intr_type = LEVEL_TRIGGERED; |
| if (trig == GPIO_INT_TRIG_LOW) { |
| gpio_cfg.pol_type = FALL_LOW; |
| } else { |
| gpio_cfg.pol_type = RISE_HIGH; |
| } |
| } else { |
| gpio_cfg.intr_type = EDGE_TRIGGERED; |
| switch (trig) { |
| case GPIO_INT_TRIG_LOW: |
| gpio_cfg.pol_type = FALL_LOW; |
| break; |
| case GPIO_INT_TRIG_HIGH: |
| gpio_cfg.pol_type = RISE_HIGH; |
| break; |
| case GPIO_INT_TRIG_BOTH: |
| return -ENOTSUP; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| /* Set IRQ configuration */ |
| int irq_num = HAL_GPIO_IntrCfg(&gpio_cfg); |
| |
| if (irq_num < 0) { |
| return -EINVAL; |
| } |
| |
| /* Set corresponding IRQ number as enabled */ |
| data->gpio_irqs[irq_num] = gpio_num; |
| |
| /* Clear pending GPIO interrupts */ |
| INTR_CTRL->GPIO_INTR |= BIT((uint32_t)irq_num); |
| |
| /* Enable IRQ */ |
| INTR_CTRL->GPIO_INTR_EN_M4 |= BIT((uint32_t)irq_num); |
| } |
| |
| return 0; |
| } |
| |
| static void gpio_eos_s3_isr(const struct device *dev) |
| { |
| struct gpio_eos_s3_data *data = dev->data; |
| /* Level interrupts can be only checked from read-only GPIO_INTR_RAW, |
| * we need to add it to the intr_status. |
| */ |
| uint32_t intr_status = (INTR_CTRL->GPIO_INTR | INTR_CTRL->GPIO_INTR_RAW); |
| |
| /* Clear pending GPIO interrupts */ |
| INTR_CTRL->GPIO_INTR |= intr_status; |
| |
| /* Fire callbacks */ |
| for (int irq_num = 0; irq_num < MAX_GPIOS; irq_num++) { |
| if (data->gpio_irqs[irq_num] != DISABLED_GPIO_IRQ) { |
| gpio_fire_callbacks(&data->callbacks, |
| dev, BIT(data->gpio_irqs[irq_num])); |
| } |
| } |
| } |
| |
| #ifdef CONFIG_GPIO_GET_DIRECTION |
| static int gpio_eos_s3_port_get_direction(const struct device *port, gpio_port_pins_t map, |
| gpio_port_pins_t *inputs, gpio_port_pins_t *outputs) |
| { |
| uint32_t pin; |
| PadConfig pad_config; |
| gpio_port_pins_t ip = 0; |
| gpio_port_pins_t op = 0; |
| const struct gpio_eos_s3_config *config = dev->config; |
| |
| map &= config->common.port_pin_mask; |
| |
| if (inputs != NULL) { |
| for (pin = find_lsb_set(pins) - 1; pins; |
| pins &= ~BIT(pin), pin = find_lsb_set(pins) - 1) { |
| pad_config = gpio_eos_s3_pad_select(port, pin); |
| ip |= (pad_config.ucMode == PAD_MODE_INPUT_EN && |
| pad_config.ucSmtTrg == PAD_SMT_TRIG_EN) * |
| BIT(pin); |
| } |
| |
| *inputs = ip; |
| } |
| |
| if (outputs != NULL) { |
| for (pin = find_lsb_set(pins) - 1; pins; |
| pins &= ~BIT(pin), pin = find_lsb_set(pins) - 1) { |
| pad_config = gpio_eos_s3_pad_select(port, pin); |
| op |= (pad_config.ucMode == PAD_MODE_OUTPUT_EN) * BIT(pin); |
| } |
| |
| *outputs = op; |
| } |
| |
| return 0; |
| } |
| #endif /* CONFIG_GPIO_GET_DIRECTION */ |
| |
| static const struct gpio_driver_api gpio_eos_s3_driver_api = { |
| .pin_configure = gpio_eos_s3_configure, |
| .port_get_raw = gpio_eos_s3_port_get_raw, |
| .port_set_masked_raw = gpio_eos_s3_port_set_masked_raw, |
| .port_set_bits_raw = gpio_eos_s3_port_set_bits_raw, |
| .port_clear_bits_raw = gpio_eos_s3_port_clear_bits_raw, |
| .port_toggle_bits = gpio_eos_s3_port_toggle_bits, |
| .pin_interrupt_configure = gpio_eos_s3_pin_interrupt_configure, |
| .manage_callback = gpio_eos_s3_manage_callback, |
| #ifdef CONFIG_GPIO_GET_DIRECTION |
| .port_get_direction = gpio_eos_s3_port_get_direction, |
| #endif /* CONFIG_GPIO_GET_DIRECTION */ |
| }; |
| |
| static int gpio_eos_s3_init(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| IRQ_CONNECT(DT_INST_IRQN(0), |
| DT_INST_IRQ(0, priority), |
| gpio_eos_s3_isr, |
| DEVICE_DT_INST_GET(0), |
| 0); |
| |
| irq_enable(DT_INST_IRQN(0)); |
| |
| return 0; |
| } |
| |
| const struct gpio_eos_s3_config gpio_eos_s3_config = { |
| .common = { |
| .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(0), |
| }, |
| .pin_secondary_config = DT_INST_PROP(0, pin_secondary_config), |
| }; |
| |
| static struct gpio_eos_s3_data gpio_eos_s3_data = { |
| .gpio_irqs = { |
| DISABLED_GPIO_IRQ, |
| DISABLED_GPIO_IRQ, |
| DISABLED_GPIO_IRQ, |
| DISABLED_GPIO_IRQ, |
| DISABLED_GPIO_IRQ, |
| DISABLED_GPIO_IRQ, |
| DISABLED_GPIO_IRQ, |
| DISABLED_GPIO_IRQ |
| }, |
| }; |
| |
| DEVICE_DT_INST_DEFINE(0, |
| gpio_eos_s3_init, |
| NULL, |
| &gpio_eos_s3_data, |
| &gpio_eos_s3_config, |
| PRE_KERNEL_1, |
| CONFIG_GPIO_INIT_PRIORITY, |
| &gpio_eos_s3_driver_api); |