373 lines
8.9 KiB
C
373 lines
8.9 KiB
C
/* gpio_sch.c - Driver implementation for Intel SCH GPIO controller */
|
|
|
|
/*
|
|
* Copyright (c) 2015 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 <errno.h>
|
|
|
|
#include <nanokernel.h>
|
|
#include <board.h>
|
|
#include <init.h>
|
|
#include <sys_io.h>
|
|
#include <misc/util.h>
|
|
|
|
#include "gpio_sch.h"
|
|
#include "gpio_utils.h"
|
|
#include "gpio_api_compat.h"
|
|
|
|
#ifndef CONFIG_GPIO_DEBUG
|
|
#define DBG(...)
|
|
#else
|
|
#if defined(CONFIG_STDOUT_CONSOLE)
|
|
#include <stdio.h>
|
|
#define DBG printf
|
|
#else
|
|
#include <misc/printk.h>
|
|
#define DBG printk
|
|
#endif /* CONFIG_STDOUT_CONSOLE */
|
|
#endif /* CONFIG_GPIO_DEBUG */
|
|
|
|
/* Define GPIO_SCH_LEGACY_IO_PORTS_ACCESS
|
|
* inside soc.h if the GPIO controller
|
|
* requires I/O port access instead of
|
|
* memory mapped I/O.
|
|
*/
|
|
#ifdef GPIO_SCH_LEGACY_IO_PORTS_ACCESS
|
|
#define _REG_READ sys_in32
|
|
#define _REG_WRITE sys_out32
|
|
#define _REG_SET_BIT sys_io_set_bit
|
|
#define _REG_CLEAR_BIT sys_io_clear_bit
|
|
#else
|
|
#define _REG_READ sys_read32
|
|
#define _REG_WRITE sys_write32
|
|
#define _REG_SET_BIT sys_set_bit
|
|
#define _REG_CLEAR_BIT sys_clear_bit
|
|
#endif /* GPIO_SCH_LEGACY_IO_PORTS_ACCESS */
|
|
|
|
#define DEFINE_MM_REG_READ(__reg, __off) \
|
|
static inline uint32_t _read_##__reg(uint32_t addr) \
|
|
{ \
|
|
return _REG_READ(addr + __off); \
|
|
}
|
|
#define DEFINE_MM_REG_WRITE(__reg, __off) \
|
|
static inline void _write_##__reg(uint32_t data, uint32_t addr) \
|
|
{ \
|
|
_REG_WRITE(data, addr + __off); \
|
|
}
|
|
|
|
DEFINE_MM_REG_READ(glvl, GPIO_SCH_REG_GLVL)
|
|
DEFINE_MM_REG_WRITE(glvl, GPIO_SCH_REG_GLVL)
|
|
DEFINE_MM_REG_WRITE(gtpe, GPIO_SCH_REG_GTPE)
|
|
DEFINE_MM_REG_WRITE(gtne, GPIO_SCH_REG_GTNE)
|
|
DEFINE_MM_REG_READ(gts, GPIO_SCH_REG_GTS)
|
|
DEFINE_MM_REG_WRITE(gts, GPIO_SCH_REG_GTS)
|
|
|
|
static void _set_bit(uint32_t base_addr,
|
|
uint32_t bit, uint8_t set)
|
|
{
|
|
if (!set) {
|
|
_REG_CLEAR_BIT(base_addr, bit);
|
|
} else {
|
|
_REG_SET_BIT(base_addr, bit);
|
|
}
|
|
}
|
|
|
|
#define DEFINE_MM_REG_SET_BIT(__reg, __off) \
|
|
static inline void _set_bit_##__reg(uint32_t addr, \
|
|
uint32_t bit, uint8_t set) \
|
|
{ \
|
|
_set_bit(addr + __off, bit, set); \
|
|
}
|
|
|
|
DEFINE_MM_REG_SET_BIT(gen, GPIO_SCH_REG_GEN)
|
|
DEFINE_MM_REG_SET_BIT(gio, GPIO_SCH_REG_GIO)
|
|
DEFINE_MM_REG_SET_BIT(glvl, GPIO_SCH_REG_GLVL)
|
|
DEFINE_MM_REG_SET_BIT(gtpe, GPIO_SCH_REG_GTPE)
|
|
DEFINE_MM_REG_SET_BIT(gtne, GPIO_SCH_REG_GTNE)
|
|
|
|
static inline void _set_data_reg(uint32_t *reg, uint8_t pin, uint8_t set)
|
|
{
|
|
*reg &= ~(BIT(pin));
|
|
*reg |= (set << pin) & BIT(pin);
|
|
}
|
|
|
|
static void _gpio_pin_config(struct device *dev, uint32_t pin, int flags)
|
|
{
|
|
struct gpio_sch_config *info = dev->config->config_info;
|
|
struct gpio_sch_data *gpio = dev->driver_data;
|
|
uint8_t active_high = 0;
|
|
uint8_t active_low = 0;
|
|
|
|
_set_bit_gen(info->regs, pin, 1);
|
|
_set_bit_gio(info->regs, pin, !(flags & GPIO_DIR_MASK));
|
|
|
|
if (flags & GPIO_INT) {
|
|
if (flags & GPIO_INT_ACTIVE_HIGH) {
|
|
active_high = 1;
|
|
} else {
|
|
active_low = 1;
|
|
}
|
|
|
|
DBG("Setting up pin %d to active_high %d and active_low %d\n",
|
|
active_high, active_low);
|
|
}
|
|
|
|
/* We store the gtpe/gtne settings. These will be used once
|
|
* we enable the callback for the pin, or the whole port
|
|
*/
|
|
_set_data_reg(&gpio->int_regs.gtpe, pin, active_high);
|
|
_set_data_reg(&gpio->int_regs.gtne, pin, active_low);
|
|
}
|
|
|
|
static inline void _gpio_port_config(struct device *dev, int flags)
|
|
{
|
|
struct gpio_sch_config *info = dev->config->config_info;
|
|
int pin;
|
|
|
|
for (pin = 0; pin < info->bits; pin++) {
|
|
_gpio_pin_config(dev, pin, flags);
|
|
}
|
|
}
|
|
|
|
static int gpio_sch_config(struct device *dev,
|
|
int access_op, uint32_t pin, int flags)
|
|
{
|
|
struct gpio_sch_config *info = dev->config->config_info;
|
|
|
|
if (access_op == GPIO_ACCESS_BY_PIN) {
|
|
if (pin >= info->bits) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
_gpio_pin_config(dev, pin, flags);
|
|
} else {
|
|
_gpio_port_config(dev, flags);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_sch_write(struct device *dev,
|
|
int access_op, uint32_t pin, uint32_t value)
|
|
{
|
|
struct gpio_sch_config *info = dev->config->config_info;
|
|
|
|
if (access_op == GPIO_ACCESS_BY_PIN) {
|
|
if (pin >= info->bits) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
_set_bit_glvl(info->regs, pin, value);
|
|
} else {
|
|
_write_glvl(info->regs, value);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_sch_read(struct device *dev,
|
|
int access_op, uint32_t pin, uint32_t *value)
|
|
{
|
|
struct gpio_sch_config *info = dev->config->config_info;
|
|
|
|
*value = _read_glvl(info->regs);
|
|
|
|
if (access_op == GPIO_ACCESS_BY_PIN) {
|
|
if (pin >= info->bits) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
*value = !!(*value & BIT(pin));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void _gpio_sch_poll_status(int data, int unused)
|
|
{
|
|
struct device *dev = INT_TO_POINTER(data);
|
|
struct gpio_sch_config *info = dev->config->config_info;
|
|
struct gpio_sch_data *gpio = dev->driver_data;
|
|
|
|
ARG_UNUSED(unused);
|
|
|
|
/* Cleaning up GTS first */
|
|
_write_gts(_read_gts(info->regs), info->regs);
|
|
|
|
while (gpio->poll) {
|
|
uint32_t status;
|
|
|
|
status = _read_gts(info->regs);
|
|
if (!status) {
|
|
goto loop;
|
|
}
|
|
|
|
_gpio_fire_callbacks(&gpio->callbacks, dev, status);
|
|
|
|
/* It's not documented but writing the same status value
|
|
* into GTS tells to the controller it got handled.
|
|
*/
|
|
_write_gts(status, info->regs);
|
|
loop:
|
|
nano_fiber_timer_start(&gpio->poll_timer,
|
|
GPIO_SCH_POLLING_TICKS);
|
|
nano_fiber_timer_test(&gpio->poll_timer, TICKS_UNLIMITED);
|
|
}
|
|
}
|
|
|
|
static void _gpio_sch_manage_callback(struct device *dev)
|
|
{
|
|
struct gpio_sch_data *gpio = dev->driver_data;
|
|
|
|
/* Start the fiber only when relevant */
|
|
if (!sys_slist_is_empty(&gpio->callbacks) && gpio->cb_enabled) {
|
|
if (!gpio->poll) {
|
|
DBG("Starting SCH GPIO polling fiber\n");
|
|
gpio->poll = 1;
|
|
fiber_start(gpio->polling_stack,
|
|
GPIO_SCH_POLLING_STACK_SIZE,
|
|
_gpio_sch_poll_status,
|
|
POINTER_TO_INT(dev), 0, 0, 0);
|
|
}
|
|
} else {
|
|
gpio->poll = 0;
|
|
}
|
|
}
|
|
|
|
static int gpio_sch_manage_callback(struct device *dev,
|
|
struct gpio_callback *callback, bool set)
|
|
{
|
|
struct gpio_sch_data *gpio = dev->driver_data;
|
|
|
|
_gpio_manage_callback(&gpio->callbacks, callback, set);
|
|
|
|
_gpio_sch_manage_callback(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_sch_enable_callback(struct device *dev,
|
|
int access_op, uint32_t pin)
|
|
{
|
|
struct gpio_sch_config *info = dev->config->config_info;
|
|
struct gpio_sch_data *gpio = dev->driver_data;
|
|
|
|
if (access_op == GPIO_ACCESS_BY_PIN) {
|
|
uint32_t bits = BIT(pin);
|
|
|
|
if (pin >= info->bits) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
_set_bit_gtpe(info->regs, pin, !!(bits & gpio->int_regs.gtpe));
|
|
_set_bit_gtne(info->regs, pin, !!(bits & gpio->int_regs.gtne));
|
|
|
|
gpio->cb_enabled |= bits;
|
|
} else {
|
|
_write_gtpe(gpio->int_regs.gtpe, info->regs);
|
|
_write_gtne(gpio->int_regs.gtne, info->regs);
|
|
|
|
gpio->cb_enabled = BIT_MASK(info->bits);
|
|
}
|
|
|
|
_gpio_enable_callback(dev, gpio->cb_enabled);
|
|
_gpio_sch_manage_callback(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_sch_disable_callback(struct device *dev,
|
|
int access_op, uint32_t pin)
|
|
{
|
|
struct gpio_sch_config *info = dev->config->config_info;
|
|
struct gpio_sch_data *gpio = dev->driver_data;
|
|
|
|
if (access_op == GPIO_ACCESS_BY_PIN) {
|
|
if (pin >= info->bits) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
_set_bit_gtpe(info->regs, pin, 0);
|
|
_set_bit_gtne(info->regs, pin, 0);
|
|
|
|
gpio->cb_enabled &= ~BIT(pin);
|
|
_gpio_disable_callback(dev, BIT(pin));
|
|
} else {
|
|
_write_gtpe(0, info->regs);
|
|
_write_gtne(0, info->regs);
|
|
|
|
gpio->cb_enabled = 0;
|
|
_gpio_disable_callback(dev, BIT_MASK(info->bits));
|
|
}
|
|
|
|
_gpio_sch_manage_callback(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct gpio_driver_api gpio_sch_api = {
|
|
.config = gpio_sch_config,
|
|
.write = gpio_sch_write,
|
|
.read = gpio_sch_read,
|
|
.manage_callback = gpio_sch_manage_callback,
|
|
.enable_callback = gpio_sch_enable_callback,
|
|
.disable_callback = gpio_sch_disable_callback,
|
|
};
|
|
|
|
int gpio_sch_init(struct device *dev)
|
|
{
|
|
struct gpio_sch_data *gpio = dev->driver_data;
|
|
|
|
dev->driver_api = &gpio_sch_api;
|
|
|
|
nano_timer_init(&gpio->poll_timer, NULL);
|
|
|
|
DBG("SCH GPIO Intel Driver initialized on device: %p\n", dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if CONFIG_GPIO_SCH_0
|
|
|
|
struct gpio_sch_config gpio_sch_0_config = {
|
|
.regs = GPIO_SCH_0_BASE_ADDR,
|
|
.bits = GPIO_SCH_0_BITS,
|
|
};
|
|
|
|
struct gpio_sch_data gpio_data_0;
|
|
|
|
DEVICE_INIT(gpio_0, CONFIG_GPIO_SCH_0_DEV_NAME, gpio_sch_init,
|
|
&gpio_data_0, &gpio_sch_0_config,
|
|
SECONDARY, CONFIG_GPIO_SCH_INIT_PRIORITY);
|
|
GPIO_SETUP_COMPAT_DEV(gpio_0);
|
|
|
|
#endif /* CONFIG_GPIO_SCH_0 */
|
|
#if CONFIG_GPIO_SCH_1
|
|
|
|
struct gpio_sch_config gpio_sch_1_config = {
|
|
.regs = GPIO_SCH_1_BASE_ADDR,
|
|
.bits = GPIO_SCH_1_BITS,
|
|
};
|
|
|
|
struct gpio_sch_data gpio_data_1;
|
|
|
|
DEVICE_INIT(gpio_1, CONFIG_GPIO_SCH_1_DEV_NAME, gpio_sch_init,
|
|
&gpio_data_1, &gpio_sch_1_config,
|
|
SECONDARY, CONFIG_GPIO_SCH_INIT_PRIORITY);
|
|
GPIO_SETUP_COMPAT_DEV(gpio_1);
|
|
|
|
#endif /* CONFIG_GPIO_SCH_1 */
|