From 6504b7e72a678a7d767a97bfc67b3906957da2c4 Mon Sep 17 00:00:00 2001 From: Jesus Sanchez-Palencia Date: Fri, 27 Nov 2015 08:24:36 -0500 Subject: [PATCH] gpio: Add QMSI-based implementation Introduce the GPIO QMSI-based implementation. This is basically a shim layer that implement's Zephyr's GPIO APIs on top of QMSI drivers. This is an alternative driver that conflicts with the previous GPIO_DW implementation. In order to enable it you must set: - CONFIG_GPIO=n - CONFIG_GPIO_QMSI=y - CONFIG_GPIO_0=y - CONFIG_QMSI_DRIVERS=y - CONFIG_QMSI_INTALL_PATH="PATH_TO_QMSI" Note that this driver currently only supports one controller instance, GPIO_0. It is implemented this way due to a limitation from the current version of QMSI. QMSI versions later than 1.0 doesn't have this limitation. Missing: - support multiple controller instances (gpio_0, gpio_1, etc); - enable level triggered interrupts in sync with system clock, through setting INT_CLOCK_SYNC properly. Change-Id: Ib61b153dae9741806a9a31d7dc1f82b96d000fbe Signed-off-by: Jesus Sanchez-Palencia Signed-off-by: Anas Nashif Signed-off-by: Vinicius Costa Gomes --- arch/x86/soc/quark_se/Kconfig | 17 ++ drivers/gpio/Kconfig | 34 +++ drivers/gpio/Makefile | 2 + drivers/gpio/gpio_qmsi.c | 284 +++++++++++++++++++++ samples/nanokernel/apps/gpio_dw/src/main.c | 12 +- 5 files changed, 347 insertions(+), 2 deletions(-) create mode 100644 drivers/gpio/gpio_qmsi.c diff --git a/arch/x86/soc/quark_se/Kconfig b/arch/x86/soc/quark_se/Kconfig index e8317742dc6..73b63908b70 100644 --- a/arch/x86/soc/quark_se/Kconfig +++ b/arch/x86/soc/quark_se/Kconfig @@ -82,6 +82,7 @@ if GPIO config GPIO_DW def_bool y +if GPIO_DW config GPIO_DW_BOTHEDGES_SUPPORT def_bool y config GPIO_DW_CLOCK_GATE @@ -111,6 +112,22 @@ config GPIO_DW_1_BITS default 6 endif +if QMSI_DRIVERS +config GPIO_QMSI + def_bool n +if GPIO_QMSI +config GPIO_QMSI_0 + def_bool n +config GPIO_QMSI_0_NAME + default "gpio_0" +config GPIO_QMSI_0_IRQ + default 8 +config GPIO_QMSI_0_PRI + default 2 +endif +endif +endif + if I2C config I2C_DW def_bool y diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 7c88052ba05..f7f24de6fd3 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -599,6 +599,40 @@ config GPIO_MMIO_1_OUTPUT help The memory address for output pin level register. +config GPIO_QMSI + bool "QMSI GPIO driver" + depends on GPIO && QMSI_DRIVERS + default n + help + Enable the GPIO driver found on Intel Microcontroller + platforms, using the QMSI library. + +config GPIO_QMSI_0 + bool "QMSI GPIO block 0" + depends on GPIO_QMSI + default n + help + Include support for the GPIO port 0 using QMSI. + +config GPIO_QMSI_0_NAME + string "Driver name" + depends on GPIO_QMSI_0 + default "GPIO_0" + +config GPIO_QMSI_0_IRQ + int "Controller interrupt number" + depends on GPIO_QMSI_0 + default 0 + help + IRQ number for the controller + +config GPIO_QMSI_0_PRI + int "Controller interrupt priority" + depends on GPIO_QMSI_0 + default 2 + help + IRQ priority + config GPIO_SCH bool "SCH GPIO controller" depends on GPIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 7307e9153cf..5dbb626a682 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -1,6 +1,8 @@ +ccflags-$(CONFIG_GPIO_QMSI) +=-I$(CONFIG_QMSI_INSTALL_PATH)/include ccflags-y +=-I$(srctree)/drivers obj-$(CONFIG_GPIO_DW) += gpio_dw.o obj-$(CONFIG_GPIO_PCAL9535A) += gpio_pcal9535a.o obj-$(CONFIG_GPIO_MMIO) += gpio_mmio.o obj-$(CONFIG_GPIO_SCH) += gpio_sch.o +obj-$(CONFIG_GPIO_QMSI) += gpio_qmsi.o diff --git a/drivers/gpio/gpio_qmsi.c b/drivers/gpio/gpio_qmsi.c new file mode 100644 index 00000000000..37ee80b20ea --- /dev/null +++ b/drivers/gpio/gpio_qmsi.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2016 Intel Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include "qm_gpio.h" +#include "qm_scss.h" + +#define INTEN 0x30 +#define INTMASK 0x34 +#define PORTA_EOI 0x4c +#define INT_GPIO_MASK 0x6c + +struct gpio_qmsi_config { + qm_gpio_t gpio; + void *addr; + uint8_t num_pins; +}; + +struct gpio_qmsi_runtime { + gpio_callback_t callback; + uint32_t pin_callbacks; + uint8_t port_callback; +}; +/* + * TODO: Zephyr's API is not clear about the behavior of the this + * application callback. This topic is currently under + * discussion, so this implementation will be fixed as soon as a + * decision is made. + */ +static void gpio_qmsi_0_int_callback(uint32_t status) +{ + struct device *port = device_get_binding(CONFIG_GPIO_QMSI_0_NAME); + struct gpio_qmsi_config *config = port->config->config_info; + struct gpio_qmsi_runtime *context = port->driver_data; + const uint32_t enabled_mask = context->pin_callbacks & status; + int bit; + + if (!context->callback) + return; + + if (context->port_callback) { + context->callback(port, status); + return; + } + + if (!enabled_mask) + return; + + for (bit = 0; bit < config->num_pins; bit++) { + if (enabled_mask & (1 << bit)) { + context->callback(port, bit); + } + } +} + +static void qmsi_write_bit(uint32_t *target, uint8_t bit, uint8_t value) +{ + if (value) { + sys_set_bit((uintptr_t) target, bit); + } else { + sys_clear_bit((uintptr_t) target, bit); + } +} + +static inline void qmsi_pin_config(struct device *port, uint32_t pin, int flags) +{ + struct gpio_qmsi_config *gpio_config = port->config->config_info; + qm_gpio_reg_t *gpio_reg = gpio_config->addr; + qm_gpio_t gpio = gpio_config->gpio; + + /* Save int mask and mask this pin while we configure the port. + * We do this to avoid "spurious interrupts", which is a behavior + * we have observed on QMSI and that still needs investigation. + */ + uint32_t intmask = gpio_reg->gpio_intmask; + qm_gpio_port_config_t cfg = { 0 }; + + sys_set_bit((uint32_t) gpio_reg + INTMASK, pin); + + qm_gpio_get_config(gpio, &cfg); + + qmsi_write_bit(&cfg.direction, pin, (flags & GPIO_DIR_MASK)); + + if (flags & GPIO_INT) { + qmsi_write_bit(&cfg.int_type, pin, (flags & GPIO_INT_EDGE)); + qmsi_write_bit(&cfg.int_polarity, pin, (flags & GPIO_INT_ACTIVE_HIGH)); + qmsi_write_bit(&cfg.int_debounce, pin, (flags & GPIO_INT_DEBOUNCE)); + qmsi_write_bit(&cfg.int_bothedge, pin, (flags & GPIO_INT_DOUBLE_EDGE)); + qmsi_write_bit(&cfg.int_en, pin, 1); + } + + /* FIXME: for multiple GPIO ports */ + cfg.callback = gpio_qmsi_0_int_callback; + qm_gpio_set_config(gpio, &cfg); + + /* Recover the original interrupt mask for this port. */ + sys_write32(intmask, (uint32_t) gpio_reg + INTMASK); +} + +static inline void qmsi_port_config(struct device *port, int flags) +{ + struct gpio_qmsi_config *gpio_config = port->config->config_info; + uint8_t num_pins = gpio_config->num_pins; + int i; + + for (i = 0; i < num_pins; i++) { + qmsi_pin_config(port, i, flags); + } +} + +static inline int gpio_qmsi_config(struct device *port, int access_op, + uint32_t pin, int flags) +{ + if (((flags & GPIO_INT) && (flags & GPIO_DIR_OUT)) || + ((flags & GPIO_DIR_IN) && (flags & GPIO_DIR_OUT))) { + return DEV_INVALID_CONF; + } + + if (access_op == GPIO_ACCESS_BY_PIN) { + qmsi_pin_config(port, pin, flags); + } else { + qmsi_port_config(port, flags); + } + return DEV_OK; +} + +static inline int gpio_qmsi_write(struct device *port, int access_op, + uint32_t pin, uint32_t value) +{ + struct gpio_qmsi_config *gpio_config = port->config->config_info; + qm_gpio_t gpio = gpio_config->gpio; + + if (access_op == GPIO_ACCESS_BY_PIN) { + if (value) { + qm_gpio_set_pin(gpio, pin); + } else { + qm_gpio_clear_pin(gpio, pin); + } + } else { + qm_gpio_write_port(gpio, value); + } + + return DEV_OK; +} + +static inline int gpio_qmsi_read(struct device *port, int access_op, + uint32_t pin, uint32_t *value) +{ + struct gpio_qmsi_config *gpio_config = port->config->config_info; + qm_gpio_t gpio = gpio_config->gpio; + + if (access_op == GPIO_ACCESS_BY_PIN) { + *value = qm_gpio_read_pin(gpio, pin); + } else { + *value = qm_gpio_read_port(gpio); + } + + return DEV_OK; +} + +static inline int gpio_qmsi_set_callback(struct device *port, + gpio_callback_t callback) +{ + struct gpio_qmsi_runtime *context = port->driver_data; + + context->callback = callback; + + return DEV_OK; +} + +static inline int gpio_qmsi_enable_callback(struct device *port, int access_op, + uint32_t pin) +{ + struct gpio_qmsi_config *gpio_config = port->config->config_info; + struct gpio_qmsi_runtime *context = port->driver_data; + uint32_t reg = (uint32_t) gpio_config->addr; + + sys_set_bit(reg + PORTA_EOI, pin); + sys_clear_bit(reg + INTMASK, pin); + + if (access_op == GPIO_ACCESS_BY_PIN) { + context->pin_callbacks |= BIT(pin); + } else { + context->port_callback = 1; + } + + return DEV_OK; +} + +static inline int gpio_qmsi_disable_callback(struct device *port, int access_op, + uint32_t pin) +{ + struct gpio_qmsi_config *gpio_config = port->config->config_info; + struct gpio_qmsi_runtime *context = port->driver_data; + uint32_t reg = (uint32_t) gpio_config->addr; + + sys_set_bit(reg + INTMASK, pin); + + if (access_op == GPIO_ACCESS_BY_PIN) { + context->pin_callbacks &= ~BIT(pin); + } else { + context->port_callback = 0; + } + + return DEV_OK; +} + +static inline int gpio_qmsi_suspend_port(struct device *port) +{ + return DEV_NO_SUPPORT; +} + +static inline int gpio_qmsi_resume_port(struct device *port) +{ + return DEV_NO_SUPPORT; +} + +static struct gpio_driver_api api_funcs = { + .config = gpio_qmsi_config, + .write = gpio_qmsi_write, + .read = gpio_qmsi_read, + .set_callback = gpio_qmsi_set_callback, + .enable_callback = gpio_qmsi_enable_callback, + .disable_callback = gpio_qmsi_disable_callback, + .suspend = gpio_qmsi_suspend_port, + .resume = gpio_qmsi_resume_port +}; + +int gpio_qmsi_init(struct device *port) +{ + struct gpio_qmsi_config *gpio_config = port->config->config_info; + uint32_t reg = (uint32_t) gpio_config->addr; + + clk_periph_enable(CLK_PERIPH_GPIO_REGISTER | + CLK_PERIPH_GPIO_INTERRUPT | CLK_PERIPH_GPIO_DB); + + /* mask and disable interrupts */ + sys_write32(~(0), reg + INTMASK); + sys_write32(0, reg + INTEN); + sys_write32(~(0), reg + PORTA_EOI); + + irq_connect(CONFIG_GPIO_QMSI_0_IRQ, CONFIG_GPIO_QMSI_0_PRI, qm_gpio_isr_0, + 0, IOAPIC_EDGE | IOAPIC_HIGH); + + /* Enable GPIO IRQ and unmask interrupts for Lakemont. */ + sys_clear_bit(QM_SCSS_INT_BASE + INT_GPIO_MASK, 0); + irq_enable(CONFIG_GPIO_QMSI_0_IRQ); + + port->driver_api = &api_funcs; + return DEV_OK; +} + +static struct gpio_qmsi_config gpio_0_config = { + .gpio = QM_GPIO_0, + .addr = &QM_GPIO[0], + .num_pins = QM_NUM_GPIO_PINS, +}; + +static struct gpio_qmsi_runtime gpio_0_runtime; + +DECLARE_DEVICE_INIT_CONFIG(gpio_0, CONFIG_GPIO_QMSI_0_NAME, + &gpio_qmsi_init, &gpio_0_config); + +SYS_DEFINE_DEVICE(gpio_0, &gpio_0_runtime, SECONDARY, + CONFIG_KERNEL_INIT_PRIORITY_DEVICE); diff --git a/samples/nanokernel/apps/gpio_dw/src/main.c b/samples/nanokernel/apps/gpio_dw/src/main.c index fba97f35486..7cefa8875dd 100644 --- a/samples/nanokernel/apps/gpio_dw/src/main.c +++ b/samples/nanokernel/apps/gpio_dw/src/main.c @@ -97,6 +97,14 @@ #define GPIO_NAME "GPIO_" #endif +#ifdef CONFIG_GPIO_DW_0 +#define GPIO_DRV_NAME CONFIG_GPIO_DW_0_NAME +#elif CONFIG_GPIO_QMSI_0 +#define GPIO_DRV_NAME CONFIG_GPIO_QMSI_0_NAME +#else +#error "Unsupported GPIO driver" +#endif + void gpio_callback(struct device *port, uint32_t pin) { PRINT(GPIO_NAME "%d triggered\n", pin); @@ -112,9 +120,9 @@ void main(void) nano_timer_init(&timer, timer_data); - gpio_dev = device_get_binding(CONFIG_GPIO_DW_0_NAME); + gpio_dev = device_get_binding(GPIO_DRV_NAME); if (!gpio_dev) { - PRINT("Cannot find %s!\n", CONFIG_GPIO_DW_0_NAME); + PRINT("Cannot find %s!\n", GPIO_DRV_NAME); } /* Setup GPIO output */