zephyr/drivers/gpio/gpio_cy8c95xx.c
Henrik Brix Andersen 2e8cc9f9b0 drivers: gpio: add combined drive strength flags and mask
Introduce combined GPIO drive strength flags for GPIO controllers only
supporting either default or alternative drive strength regardless if
the pin is driven to a high or a low level.

Fixes: #30329

Signed-off-by: Henrik Brix Andersen <hebad@vestas.com>
2021-11-11 07:20:12 -05:00

318 lines
7.6 KiB
C

/*
* Copyright (c) 2021 Synopsys
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT cypress_cy8c95xx_gpio_port
#include <errno.h>
#include <kernel.h>
#include <device.h>
#include <init.h>
#include <drivers/gpio.h>
#include <drivers/i2c.h>
#include <sys/byteorder.h>
#include <sys/util.h>
#include <logging/log.h>
LOG_MODULE_REGISTER(cy8c95xx, CONFIG_GPIO_LOG_LEVEL);
#include "gpio_utils.h"
/** Cache of the output configuration and data of the pins. */
struct cy8c95xx_pin_state {
uint8_t dir;
uint8_t data_out;
uint8_t pull_up;
uint8_t pull_down;
};
/** Runtime driver data */
struct cy8c95xx_drv_data {
/* gpio_driver_data needs to be first */
struct gpio_driver_data common;
struct cy8c95xx_pin_state pin_state;
struct k_sem *lock;
};
/** Configuration data */
struct cy8c95xx_config {
/* gpio_driver_config needs to be first */
struct gpio_driver_config common;
const struct device *i2c_master;
uint16_t i2c_slave_addr;
uint8_t port_num;
};
#define CY8C95XX_REG_INPUT_DATA0 0x00
#define CY8C95XX_REG_OUTPUT_DATA0 0x08
#define CY8C95XX_REG_PORT_SELECT 0x18
#define CY8C95XX_REG_DIR 0x1C
#define CY8C95XX_REG_PULL_UP 0x1D
#define CY8C95XX_REG_PULL_DOWN 0x1E
#define CY8C95XX_REG_ID 0x2E
static int write_pin_state(const struct cy8c95xx_config *cfg,
struct cy8c95xx_drv_data *drv_data,
struct cy8c95xx_pin_state *pins)
{
int rc;
rc = i2c_reg_write_byte(cfg->i2c_master, cfg->i2c_slave_addr,
CY8C95XX_REG_OUTPUT_DATA0 + cfg->port_num, pins->data_out);
if (rc) {
return rc;
}
rc = i2c_reg_write_byte(cfg->i2c_master, cfg->i2c_slave_addr,
CY8C95XX_REG_PORT_SELECT, cfg->port_num);
if (rc) {
return rc;
}
rc = i2c_reg_write_byte(cfg->i2c_master, cfg->i2c_slave_addr,
CY8C95XX_REG_DIR, pins->dir);
if (rc) {
return rc;
}
rc = i2c_reg_write_byte(cfg->i2c_master, cfg->i2c_slave_addr,
CY8C95XX_REG_PULL_UP, pins->pull_up);
if (rc) {
return rc;
}
rc = i2c_reg_write_byte(cfg->i2c_master, cfg->i2c_slave_addr,
CY8C95XX_REG_PULL_DOWN, pins->pull_down);
return rc;
}
static int cy8c95xx_config(const struct device *dev,
gpio_pin_t pin,
gpio_flags_t flags)
{
const struct cy8c95xx_config *cfg = dev->config;
struct cy8c95xx_drv_data *drv_data = dev->data;
struct cy8c95xx_pin_state *pins = &drv_data->pin_state;
int rc = 0;
uint32_t io_flags;
/* Can't do I2C bus operations from an ISR */
if (k_is_in_isr()) {
return -EWOULDBLOCK;
}
/* Strengths not implemented */
if ((flags & GPIO_DS_ALT) != 0) {
return -ENOTSUP;
}
/* Open-drain not implemented */
if ((flags & GPIO_SINGLE_ENDED) != 0U) {
return -ENOTSUP;
}
WRITE_BIT(pins->pull_up, pin, (flags & GPIO_PULL_UP) != 0U);
WRITE_BIT(pins->pull_down, pin, (flags & GPIO_PULL_DOWN) != 0U);
/* Disconnected pin not implemented*/
io_flags = flags & (GPIO_INPUT | GPIO_OUTPUT);
if (io_flags == GPIO_DISCONNECTED) {
return -ENOTSUP;
}
k_sem_take(drv_data->lock, K_FOREVER);
if ((flags & GPIO_OUTPUT) != 0) {
pins->dir &= ~BIT(pin);
if ((flags & GPIO_OUTPUT_INIT_LOW) != 0) {
pins->data_out &= ~BIT(pin);
} else if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0) {
pins->data_out |= BIT(pin);
}
} else {
pins->dir |= BIT(pin);
}
LOG_DBG("CFG %u %x : DIR %04x ; DAT %04x",
pin, flags, pins->dir, pins->data_out);
rc = write_pin_state(cfg, drv_data, pins);
k_sem_give(drv_data->lock);
return rc;
}
static int port_get(const struct device *dev,
gpio_port_value_t *value)
{
const struct cy8c95xx_config *cfg = dev->config;
struct cy8c95xx_drv_data *drv_data = dev->data;
uint8_t pin_data = 0;
int rc = 0;
/* Can't do I2C bus operations from an ISR */
if (k_is_in_isr()) {
return -EWOULDBLOCK;
}
k_sem_take(drv_data->lock, K_FOREVER);
rc = i2c_reg_read_byte(cfg->i2c_master, cfg->i2c_slave_addr,
CY8C95XX_REG_INPUT_DATA0 + cfg->port_num, &pin_data);
if (rc == 0) {
*value = pin_data;
}
k_sem_give(drv_data->lock);
return rc;
}
static int port_write(const struct device *dev,
gpio_port_pins_t mask,
gpio_port_value_t value,
gpio_port_value_t toggle)
{
const struct cy8c95xx_config *cfg = dev->config;
struct cy8c95xx_drv_data *drv_data = dev->data;
uint8_t *outp = &drv_data->pin_state.data_out;
uint8_t out = ((*outp & ~mask) | (value & mask)) ^ toggle;
/* Can't do I2C bus operations from an ISR */
if (k_is_in_isr()) {
return -EWOULDBLOCK;
}
k_sem_take(drv_data->lock, K_FOREVER);
int rc = i2c_reg_write_byte(cfg->i2c_master, cfg->i2c_slave_addr,
CY8C95XX_REG_OUTPUT_DATA0 + cfg->port_num, out);
if (rc == 0) {
*outp = out;
}
k_sem_give(drv_data->lock);
LOG_DBG("write msk %04x val %04x tog %04x => %04x: %d",
mask, value, toggle, out, rc);
return rc;
}
static int port_set_masked(const struct device *dev,
gpio_port_pins_t mask,
gpio_port_value_t value)
{
return port_write(dev, mask, value, 0);
}
static int port_set_bits(const struct device *dev,
gpio_port_pins_t pins)
{
return port_write(dev, pins, pins, 0);
}
static int port_clear_bits(const struct device *dev,
gpio_port_pins_t pins)
{
return port_write(dev, pins, 0, 0);
}
static int port_toggle_bits(const struct device *dev,
gpio_port_pins_t pins)
{
return port_write(dev, 0, 0, pins);
}
static int pin_interrupt_configure(const struct device *dev,
gpio_pin_t pin,
enum gpio_int_mode mode,
enum gpio_int_trig trig)
{
return -ENOTSUP;
}
/**
* @brief Initialization function of CY8C95XX
*
* @param dev Device struct
* @return 0 if successful, failed otherwise.
*/
static int cy8c95xx_init(const struct device *dev)
{
const struct cy8c95xx_config *cfg = dev->config;
struct cy8c95xx_drv_data *drv_data = dev->data;
int rc;
uint8_t data = 0;
k_sem_take(drv_data->lock, K_FOREVER);
if (!device_is_ready(cfg->i2c_master)) {
LOG_ERR("%s is not ready", cfg->i2c_master->name);
rc = -ENODEV;
goto out;
}
rc = i2c_reg_read_byte(cfg->i2c_master, cfg->i2c_slave_addr,
CY8C95XX_REG_ID, &data);
if (rc) {
goto out;
}
LOG_DBG("cy8c95xx device ID %02X", data & 0xF0);
if ((data & 0xF0) != 0x20) {
LOG_WRN("driver only support [0-2] ports operations");
}
/* Reset state mediated by initial configuration */
drv_data->pin_state = (struct cy8c95xx_pin_state) {
.dir = 0xFF,
.data_out = 0xFF,
.pull_up = 0xFF,
.pull_down = 0x00,
};
rc = write_pin_state(cfg, drv_data, &drv_data->pin_state);
out:
if (rc != 0) {
LOG_ERR("%s init failed: %d", dev->name, rc);
} else {
LOG_DBG("%s init ok", dev->name);
}
k_sem_give(drv_data->lock);
return rc;
}
static const struct gpio_driver_api api_table = {
.pin_configure = cy8c95xx_config,
.port_get_raw = port_get,
.port_set_masked_raw = port_set_masked,
.port_set_bits_raw = port_set_bits,
.port_clear_bits_raw = port_clear_bits,
.port_toggle_bits = port_toggle_bits,
.pin_interrupt_configure = pin_interrupt_configure,
};
static struct k_sem cy8c95xx_lock = Z_SEM_INITIALIZER(cy8c95xx_lock, 1, 1);
#define GPIO_PORT_INIT(idx) \
static const struct cy8c95xx_config cy8c95xx_##idx##_cfg = { \
.common = { \
.port_pin_mask = 0xFF, \
}, \
.i2c_master = DEVICE_DT_GET(DT_BUS(DT_INST(0, cypress_cy8c95xx_gpio))), \
.i2c_slave_addr = DT_REG_ADDR_BY_IDX(DT_INST(0, cypress_cy8c95xx_gpio), 0), \
.port_num = DT_INST_REG_ADDR(idx), \
}; \
static struct cy8c95xx_drv_data cy8c95xx_##idx##_drvdata = { \
.lock = &cy8c95xx_lock, \
}; \
DEVICE_DT_INST_DEFINE(idx, cy8c95xx_init, NULL, \
&cy8c95xx_##idx##_drvdata, &cy8c95xx_##idx##_cfg, \
POST_KERNEL, CONFIG_GPIO_CY8C95XX_INIT_PRIORITY, \
&api_table);
DT_INST_FOREACH_STATUS_OKAY(GPIO_PORT_INIT)