|  | /* | 
|  | * 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 DEVICE_API(gpio, 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); |