| /* |
| * Copyright (c) 2018-2019 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @file |
| * @brief Intel Apollo Lake SoC GPIO Controller Driver |
| * |
| * The GPIO controller on Intel Apollo Lake SoC serves |
| * both GPIOs and Pinmuxing function. This driver provides |
| * the GPIO function. |
| * |
| * The GPIO controller has 245 pins divided into four sets. |
| * Each set has its own MMIO address space. Due to GPIO |
| * callback only allowing 32 pins (as a 32-bit mask) at once, |
| * each set is further sub-divided into multiple devices. |
| * Because of this, shared IRQ must be used. |
| */ |
| |
| #include <errno.h> |
| #include <gpio.h> |
| #include <shared_irq.h> |
| #include <soc.h> |
| #include <sys_io.h> |
| #include <misc/__assert.h> |
| #include <misc/slist.h> |
| #include <misc/speculation.h> |
| |
| #include "gpio_utils.h" |
| |
| #ifndef CONFIG_SHARED_IRQ |
| #error "Need CONFIG_SHARED_IRQ!" |
| #endif |
| |
| #define REG_PAD_BASE_ADDR 0x000C |
| |
| #define REG_MISCCFG 0x0010 |
| #define MISCCFG_IRQ_ROUTE_POS 3 |
| |
| #define REG_PAD_OWNER_BASE 0x0020 |
| #define PAD_OWN_MASK 0x03 |
| #define PAD_OWN_HOST 0 |
| #define PAD_OWN_CSME 1 |
| #define PAD_OWN_ISH 2 |
| #define PAD_OWN_IE 3 |
| |
| #define REG_PAD_HOST_SW_OWNER 0x0080 |
| #define PAD_HOST_SW_OWN_GPIO 1 |
| #define PAD_HOST_SW_OWN_ACPI 0 |
| |
| #define REG_GPI_INT_STS_BASE 0x0100 |
| #define REG_GPI_INT_EN_BASE 0x0110 |
| |
| #define PAD_CFG0_RXPADSTSEL BIT(29) |
| #define PAD_CFG0_RXRAW1 BIT(28) |
| |
| #define PAD_CFG0_PMODE_MASK (0x0F << 10) |
| |
| #define PAD_CFG0_RXEVCFG_POS 25 |
| #define PAD_CFG0_RXEVCFG_MASK (0x03 << PAD_CFG0_RXEVCFG_POS) |
| #define PAD_CFG0_RXEVCFG_LEVEL (0 << PAD_CFG0_RXEVCFG_POS) |
| #define PAD_CFG0_RXEVCFG_EDGE (1 << PAD_CFG0_RXEVCFG_POS) |
| #define PAD_CFG0_RXEVCFG_DRIVE0 (2 << PAD_CFG0_RXEVCFG_POS) |
| |
| #define PAD_CFG0_PREGFRXSEL BIT(24) |
| #define PAD_CFG0_RXINV BIT(23) |
| |
| #define PAD_CFG0_RXDIS BIT(9) |
| #define PAD_CFG0_TXDIS BIT(8) |
| #define PAD_CFG0_RXSTATE BIT(1) |
| #define PAD_CFG0_RXSTATE_POS 1 |
| #define PAD_CFG0_TXSTATE BIT(0) |
| #define PAD_CFG0_TXSTATE_POS 0 |
| |
| #define PAD_CFG1_IOSTERM_POS 8 |
| #define PAD_CFG1_IOSTERM_MASK (0x03 << PAD_CFG1_IOSTERM_POS) |
| #define PAD_CFG1_IOSTERM_FUNC (0 << PAD_CFG1_IOSTERM_POS) |
| #define PAD_CFG1_IOSTERM_DISPUD (1 << PAD_CFG1_IOSTERM_POS) |
| #define PAD_CFG1_IOSTERM_PU (2 << PAD_CFG1_IOSTERM_POS) |
| #define PAD_CFG1_IOSTERM_PD (3 << PAD_CFG1_IOSTERM_POS) |
| |
| #define PAD_CFG1_TERM_POS 10 |
| #define PAD_CFG1_TERM_MASK (0x0F << PAD_CFG1_TERM_POS) |
| #define PAD_CFG1_TERM_NONE (0x00 << PAD_CFG1_TERM_POS) |
| #define PAD_CFG1_TERM_PD (0x04 << PAD_CFG1_TERM_POS) |
| #define PAD_CFG1_TERM_PU (0x0C << PAD_CFG1_TERM_POS) |
| |
| #define PAD_CFG1_IOSSTATE_POS 14 |
| #define PAD_CFG1_IOSSTATE_MASK (0x0F << PAD_CFG1_IOSSTATE_POS) |
| #define PAD_CFG1_IOSSTATE_IGNORE (0x0F << PAD_CFG1_IOSSTATE_POS) |
| |
| struct gpio_intel_apl_config { |
| u32_t reg_base; |
| |
| u8_t pin_offset; |
| u8_t num_pins; |
| }; |
| |
| struct gpio_intel_apl_data { |
| /* Pad base address */ |
| u32_t pad_base; |
| |
| sys_slist_t cb; |
| }; |
| |
| #ifdef CONFIG_GPIO_INTEL_APL_CHECK_PERMS |
| /** |
| * @brief Check if host has permission to alter this GPIO pin. |
| * |
| * @param "struct device *dev" Device struct |
| * @param "u32_t raw_pin" Raw GPIO pin |
| * |
| * @return true if host owns the GPIO pin, false otherwise |
| */ |
| static bool check_perm(struct device *dev, u32_t raw_pin) |
| { |
| const struct gpio_intel_apl_config *cfg = dev->config->config_info; |
| struct gpio_intel_apl_data *data = dev->driver_data; |
| u32_t offset, val; |
| |
| /* First is to establish that host software owns the pin */ |
| |
| /* read the Pad Ownership register related to the pin */ |
| offset = REG_PAD_OWNER_BASE + ((raw_pin >> 3) << 2); |
| val = sys_read32(cfg->reg_base + offset); |
| |
| /* get the bits about ownership */ |
| offset = raw_pin % 8; |
| val = (val >> offset) & PAD_OWN_MASK; |
| if (val) { |
| /* PAD_OWN_HOST == 0, so !0 => false*/ |
| return false; |
| } |
| |
| /* Also need to make sure the function of pad is GPIO */ |
| offset = data->pad_base + (raw_pin << 3); |
| val = sys_read32(cfg->reg_base + offset); |
| if (val & PAD_CFG0_PMODE_MASK) { |
| /* mode is not zero => not functioning as GPIO */ |
| return false; |
| } |
| |
| return true; |
| } |
| #else |
| #define check_perm(...) (1) |
| #endif |
| |
| static int gpio_intel_apl_isr(struct device *dev) |
| { |
| const struct gpio_intel_apl_config *cfg = dev->config->config_info; |
| struct gpio_intel_apl_data *data = dev->driver_data; |
| struct gpio_callback *cb, *tmp; |
| u32_t reg, int_sts, cur_mask, acc_mask; |
| |
| reg = cfg->reg_base + REG_GPI_INT_STS_BASE |
| + ((cfg->pin_offset >> 5) << 2); |
| int_sts = sys_read32(reg); |
| acc_mask = 0U; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&data->cb, cb, tmp, node) { |
| cur_mask = int_sts & cb->pin_mask; |
| acc_mask |= cur_mask; |
| if (cur_mask) { |
| __ASSERT(cb->handler, "No callback handler!"); |
| cb->handler(dev, cb, cur_mask); |
| } |
| } |
| |
| /* clear handled interrupt bits */ |
| sys_write32(acc_mask, reg); |
| |
| return 0; |
| } |
| |
| static int gpio_intel_apl_config(struct device *dev, int access_op, |
| u32_t pin, int flags) |
| { |
| const struct gpio_intel_apl_config *cfg = dev->config->config_info; |
| struct gpio_intel_apl_data *data = dev->driver_data; |
| u32_t raw_pin, reg, cfg0, cfg1, val; |
| |
| if (access_op != GPIO_ACCESS_BY_PIN) { |
| return -ENOTSUP; |
| } |
| |
| /* |
| * Pin must be input for interrupt to work. |
| * And there is no double-edge trigger according |
| * to datasheet. |
| */ |
| if ((flags & GPIO_INT) |
| && ((flags & GPIO_DIR_OUT) |
| || (flags & GPIO_INT_DOUBLE_EDGE))) { |
| return -EINVAL; |
| } |
| |
| if ((flags & GPIO_POL_MASK) == GPIO_POL_INV) { |
| /* hardware cannot invert signal */ |
| return -EINVAL; |
| } |
| |
| if (pin > cfg->num_pins) { |
| return -EINVAL; |
| } |
| pin = k_array_index_sanitize(pin, cfg->num_pins + 1); |
| |
| raw_pin = cfg->pin_offset + pin; |
| |
| if (!check_perm(dev, raw_pin)) { |
| return -EPERM; |
| } |
| |
| /* Set GPIO to trigger legacy interrupt */ |
| if (flags & GPIO_INT) { |
| reg = cfg->reg_base + REG_PAD_HOST_SW_OWNER; |
| sys_bitfield_set_bit(reg, raw_pin); |
| } |
| |
| /* read in pad configuration register */ |
| reg = cfg->reg_base + data->pad_base + (raw_pin * 8U); |
| cfg0 = sys_read32(reg); |
| cfg1 = sys_read32(reg + 4); |
| |
| /* change direction */ |
| if ((flags & GPIO_DIR_MASK) == GPIO_DIR_OUT) { |
| /* pin to output */ |
| cfg0 &= ~PAD_CFG0_TXDIS; |
| cfg0 |= PAD_CFG0_RXDIS; |
| } else { |
| /* pin to input */ |
| cfg0 &= ~PAD_CFG0_RXDIS; |
| cfg0 |= PAD_CFG0_TXDIS; |
| |
| /* don't override RX to 1 */ |
| cfg0 &= ~PAD_CFG0_RXRAW1; |
| } |
| |
| /* clear some bits first before interrupt setup */ |
| cfg0 &= ~(PAD_CFG0_RXPADSTSEL | PAD_CFG0_RXINV |
| | PAD_CFG0_RXEVCFG_MASK); |
| |
| /* setup interrupt if desired */ |
| if (flags & GPIO_INT) { |
| /* invert signal for interrupt controller */ |
| if ((flags & GPIO_INT_ACTIVE_HIGH) == GPIO_INT_ACTIVE_LOW) { |
| cfg0 |= PAD_CFG0_RXINV; |
| } |
| |
| /* level == 0 / edge == 1*/ |
| if (flags & GPIO_INT_EDGE) { |
| cfg0 |= PAD_CFG0_RXEVCFG_EDGE; |
| } |
| } else { |
| /* set RX conf to drive 0 */ |
| cfg0 |= PAD_CFG0_RXEVCFG_DRIVE0; |
| } |
| |
| /* pull-up or pull-down */ |
| val = flags & GPIO_PUD_MASK; |
| cfg1 &= ~PAD_CFG1_TERM_MASK; |
| if (val == GPIO_PUD_PULL_UP) { |
| cfg1 |= PAD_CFG1_TERM_PU; |
| } else if (val == GPIO_PUD_PULL_DOWN) { |
| cfg1 |= PAD_CFG1_TERM_PD; |
| } else { |
| cfg1 |= PAD_CFG1_TERM_NONE; |
| } |
| |
| /* set IO Standby Termination to function mode */ |
| cfg1 &= ~PAD_CFG1_IOSTERM_MASK; |
| |
| /* IO Standby state to TX,RX enabled */ |
| cfg1 &= ~PAD_CFG1_IOSSTATE_MASK; |
| |
| /* write back pad configuration register after all changes */ |
| sys_write32(cfg0, reg); |
| sys_write32(cfg1, (reg + 4)); |
| |
| return 0; |
| } |
| |
| static int gpio_intel_apl_write(struct device *dev, int access_op, |
| u32_t pin, u32_t value) |
| { |
| const struct gpio_intel_apl_config *cfg = dev->config->config_info; |
| struct gpio_intel_apl_data *data = dev->driver_data; |
| u32_t raw_pin, reg, val; |
| |
| if (access_op != GPIO_ACCESS_BY_PIN) { |
| return -ENOTSUP; |
| } |
| |
| if (pin > cfg->num_pins) { |
| return -EINVAL; |
| } |
| pin = k_array_index_sanitize(pin, cfg->num_pins + 1); |
| |
| raw_pin = cfg->pin_offset + pin; |
| |
| if (!check_perm(dev, raw_pin)) { |
| return -EPERM; |
| } |
| |
| reg = cfg->reg_base + data->pad_base + (raw_pin * 8U); |
| val = sys_read32(reg); |
| |
| if (value) { |
| val |= PAD_CFG0_TXSTATE; |
| } else { |
| val &= ~PAD_CFG0_TXSTATE; |
| } |
| |
| sys_write32(val, reg); |
| |
| return 0; |
| } |
| |
| static int gpio_intel_apl_read(struct device *dev, int access_op, |
| u32_t pin, u32_t *value) |
| { |
| const struct gpio_intel_apl_config *cfg = dev->config->config_info; |
| struct gpio_intel_apl_data *data = dev->driver_data; |
| u32_t raw_pin, reg, val; |
| |
| if (access_op != GPIO_ACCESS_BY_PIN) { |
| return -ENOTSUP; |
| } |
| |
| if (pin > cfg->num_pins) { |
| return -EINVAL; |
| } |
| pin = k_array_index_sanitize(pin, cfg->num_pins + 1); |
| |
| raw_pin = cfg->pin_offset + pin; |
| |
| if (!check_perm(dev, raw_pin)) { |
| return -EPERM; |
| } |
| |
| reg = cfg->reg_base + data->pad_base + (raw_pin * 8U); |
| val = sys_read32(reg); |
| |
| if (!(val & PAD_CFG0_TXDIS)) { |
| /* If TX is not disabled, return TX_STATE */ |
| *value = val & PAD_CFG0_TXSTATE; |
| } else { |
| /* else just return RX_STATE */ |
| *value = val & PAD_CFG0_RXSTATE; |
| } |
| |
| return 0; |
| } |
| |
| static int gpio_intel_apl_manage_callback(struct device *dev, |
| struct gpio_callback *callback, |
| bool set) |
| { |
| struct gpio_intel_apl_data *data = dev->driver_data; |
| |
| return gpio_manage_callback(&data->cb, callback, set); |
| } |
| |
| static int gpio_intel_apl_enable_callback(struct device *dev, |
| int access_op, u32_t pin) |
| { |
| const struct gpio_intel_apl_config *cfg = dev->config->config_info; |
| u32_t raw_pin, reg; |
| |
| if (access_op != GPIO_ACCESS_BY_PIN) { |
| return -ENOTSUP; |
| } |
| |
| if (pin > cfg->num_pins) { |
| return -EINVAL; |
| } |
| pin = k_array_index_sanitize(pin, cfg->num_pins + 1); |
| |
| raw_pin = cfg->pin_offset + pin; |
| |
| if (!check_perm(dev, raw_pin)) { |
| return -EPERM; |
| } |
| |
| /* clear (by setting) interrupt status bit */ |
| reg = cfg->reg_base + REG_GPI_INT_STS_BASE; |
| sys_bitfield_set_bit(reg, raw_pin); |
| |
| /* enable interrupt bit */ |
| reg = cfg->reg_base + REG_GPI_INT_EN_BASE; |
| sys_bitfield_set_bit(reg, raw_pin); |
| |
| return 0; |
| } |
| |
| static int gpio_intel_apl_disable_callback(struct device *dev, |
| int access_op, u32_t pin) |
| { |
| const struct gpio_intel_apl_config *cfg = dev->config->config_info; |
| u32_t raw_pin, reg; |
| |
| if (access_op != GPIO_ACCESS_BY_PIN) { |
| return -ENOTSUP; |
| } |
| |
| if (pin > cfg->num_pins) { |
| return -EINVAL; |
| } |
| pin = k_array_index_sanitize(pin, cfg->num_pins + 1); |
| |
| raw_pin = cfg->pin_offset + pin; |
| |
| if (!check_perm(dev, raw_pin)) { |
| return -EPERM; |
| } |
| |
| /* disable interrupt bit */ |
| reg = cfg->reg_base + REG_GPI_INT_EN_BASE; |
| sys_bitfield_clear_bit(reg, raw_pin); |
| |
| return 0; |
| } |
| |
| static const struct gpio_driver_api gpio_intel_apl_api = { |
| .config = gpio_intel_apl_config, |
| .write = gpio_intel_apl_write, |
| .read = gpio_intel_apl_read, |
| .manage_callback = gpio_intel_apl_manage_callback, |
| .enable_callback = gpio_intel_apl_enable_callback, |
| .disable_callback = gpio_intel_apl_disable_callback, |
| }; |
| |
| static void gpio_intel_apl_irq_config(struct device *dev); |
| |
| int gpio_intel_apl_init(struct device *dev) |
| { |
| const struct gpio_intel_apl_config *cfg = dev->config->config_info; |
| struct gpio_intel_apl_data *data = dev->driver_data; |
| |
| gpio_intel_apl_irq_config(dev); |
| |
| data->pad_base = sys_read32(cfg->reg_base + REG_PAD_BASE_ADDR); |
| |
| /* Set to route interrupt through IRQ 14 */ |
| sys_bitfield_clear_bit(data->pad_base + REG_MISCCFG, |
| MISCCFG_IRQ_ROUTE_POS); |
| |
| dev->driver_api = &gpio_intel_apl_api; |
| |
| return 0; |
| } |
| |
| #define GPIO_INTEL_APL_DEV_CFG_DATA(dir_l, dir_u, pos, offset, pins) \ |
| static const struct gpio_intel_apl_config \ |
| gpio_intel_apl_cfg_##dir_l##_##pos = { \ |
| .reg_base = DT_APL_GPIO_BASE_ADDRESS_##dir_u, \ |
| .pin_offset = offset, \ |
| .num_pins = pins, \ |
| }; \ |
| \ |
| static struct gpio_intel_apl_data gpio_intel_apl_data_##dir_l##_##pos; \ |
| \ |
| DEVICE_AND_API_INIT(gpio_intel_apl_##dir_l##_##pos, \ |
| DT_APL_GPIO_LABEL_##dir_u##_##pos, \ |
| gpio_intel_apl_init, \ |
| &gpio_intel_apl_data_##dir_l##_##pos, \ |
| &gpio_intel_apl_cfg_##dir_l##_##pos, \ |
| POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ |
| &gpio_intel_apl_api) |
| |
| GPIO_INTEL_APL_DEV_CFG_DATA(n, N, 0, 0, 32); |
| GPIO_INTEL_APL_DEV_CFG_DATA(n, N, 1, 32, 32); |
| GPIO_INTEL_APL_DEV_CFG_DATA(n, N, 2, 32, 14); |
| |
| GPIO_INTEL_APL_DEV_CFG_DATA(nw, NW, 0, 0, 32); |
| GPIO_INTEL_APL_DEV_CFG_DATA(nw, NW, 1, 32, 32); |
| GPIO_INTEL_APL_DEV_CFG_DATA(nw, NW, 2, 32, 13); |
| |
| GPIO_INTEL_APL_DEV_CFG_DATA(w, W, 0, 0, 32); |
| GPIO_INTEL_APL_DEV_CFG_DATA(w, W, 1, 32, 15); |
| |
| GPIO_INTEL_APL_DEV_CFG_DATA(sw, SW, 0, 0, 32); |
| GPIO_INTEL_APL_DEV_CFG_DATA(sw, SW, 1, 32, 11); |
| |
| static void gpio_intel_apl_irq_config(struct device *dev) |
| { |
| struct device *irq_dev; |
| |
| irq_dev = device_get_binding(DT_SHARED_IRQ_SHAREDIRQ0_LABEL); |
| __ASSERT(irq_dev != NULL, |
| "Failed to get shared IRQ device binding"); |
| |
| shared_irq_isr_register(irq_dev, gpio_intel_apl_isr, dev); |
| shared_irq_enable(irq_dev, dev); |
| } |