[drivers]: gpios: SN74HC595: Extend to allow for chained shift registers
The current driver has a few limitations: 1. The `ngpios` DT property is fixed at eight. Since the SN74HC595 and kin are designed to be easily daisychain-able, the upper bound on `ngpios` should be limited only by the maximum number of pins that Zephyr supports per GPIO port, which is 32. 2. In the case of having no control over the shift register's reset input, the device tree node should accept a default value to shift into the register(s) during init. 3. There seems to be an assumption that the serial clock and load clock are tied together. While this is often the case, the device tree node should be more flexible in allowing the specification of a separate load clock GPIO pin. 4. The device tree node should also be able to accept a GPIO pin to drive the enable input pin of the shift register(s). This commit addresses all of these issues. Signed-off-by: Pete Dietl <petedietl@gmail.com>
This commit is contained in:
committed by
Benjamin Cabé
parent
090130c35b
commit
c407fbcfc9
@@ -15,6 +15,8 @@
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#include <zephyr/drivers/gpio/gpio_utils.h>
|
||||
|
||||
@@ -26,32 +28,45 @@ LOG_MODULE_REGISTER(gpio_sn74hc595, CONFIG_GPIO_LOG_LEVEL);
|
||||
#endif
|
||||
|
||||
struct gpio_sn74hc595_config {
|
||||
/* gpio_driver_config needs to be first */
|
||||
struct gpio_driver_config config;
|
||||
|
||||
struct spi_dt_spec bus;
|
||||
struct gpio_dt_spec enable_gpio;
|
||||
struct gpio_dt_spec reset_gpio;
|
||||
|
||||
uint8_t num_registers;
|
||||
};
|
||||
|
||||
struct gpio_sn74hc595_drv_data {
|
||||
/* gpio_driver_data needs to be first */
|
||||
struct gpio_driver_data data;
|
||||
|
||||
struct k_mutex lock;
|
||||
uint8_t output;
|
||||
uint32_t output;
|
||||
};
|
||||
|
||||
static int sn74hc595_spi_write(const struct device *dev, void *buf, size_t len_bytes)
|
||||
static int sn74hc595_write(const struct device *dev, uint32_t value)
|
||||
{
|
||||
int ret;
|
||||
uint32_t adjusted_value;
|
||||
const struct gpio_sn74hc595_config *config = dev->config;
|
||||
struct gpio_sn74hc595_drv_data *drv_data = dev->data;
|
||||
|
||||
__ASSERT(((buf != NULL) || (len_bytes == 0)), "no valid buffer given");
|
||||
__ASSERT(!k_is_in_isr(), "attempt to access SPI from ISR");
|
||||
|
||||
struct spi_buf tx_buf[] = { { .buf = buf, .len = len_bytes } };
|
||||
const struct spi_buf_set tx = { .buffers = tx_buf, .count = 1 };
|
||||
adjusted_value = sys_cpu_to_be32(
|
||||
value << (BITS_PER_BYTE * (sizeof(uint32_t) - config->num_registers)));
|
||||
|
||||
return spi_write_dt(&config->bus, &tx);
|
||||
struct spi_buf tx_buf[] = {
|
||||
{.buf = (uint8_t *)&adjusted_value, .len = config->num_registers}};
|
||||
const struct spi_buf_set tx = {.buffers = tx_buf, .count = 1};
|
||||
|
||||
ret = spi_write_dt(&config->bus, &tx);
|
||||
|
||||
if (ret == 0) {
|
||||
drv_data->output = value;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int gpio_sn74hc595_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags)
|
||||
@@ -80,7 +95,7 @@ static int gpio_sn74hc595_port_set_masked_raw(const struct device *dev, uint32_t
|
||||
{
|
||||
struct gpio_sn74hc595_drv_data *drv_data = dev->data;
|
||||
int ret = 0;
|
||||
uint8_t output;
|
||||
uint32_t output;
|
||||
|
||||
k_mutex_lock(&drv_data->lock, K_FOREVER);
|
||||
|
||||
@@ -89,16 +104,11 @@ static int gpio_sn74hc595_port_set_masked_raw(const struct device *dev, uint32_t
|
||||
if ((drv_data->output & mask) != (mask & value)) {
|
||||
output = (drv_data->output & ~mask) | (mask & value);
|
||||
|
||||
ret = sn74hc595_spi_write(dev, &output, 1U);
|
||||
if (ret < 0) {
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
drv_data->output = output;
|
||||
ret = sn74hc595_write(dev, output);
|
||||
}
|
||||
|
||||
unlock:
|
||||
k_mutex_unlock(&drv_data->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -116,21 +126,16 @@ static int gpio_sn74hc595_port_toggle_bits(const struct device *dev, uint32_t ma
|
||||
{
|
||||
struct gpio_sn74hc595_drv_data *drv_data = dev->data;
|
||||
int ret;
|
||||
uint8_t toggled_output;
|
||||
uint32_t toggled_output;
|
||||
|
||||
k_mutex_lock(&drv_data->lock, K_FOREVER);
|
||||
|
||||
toggled_output = drv_data->output ^ mask;
|
||||
|
||||
ret = sn74hc595_spi_write(dev, &toggled_output, 1U);
|
||||
if (ret < 0) {
|
||||
goto unlock;
|
||||
}
|
||||
ret = sn74hc595_write(dev, toggled_output);
|
||||
|
||||
drv_data->output ^= mask;
|
||||
|
||||
unlock:
|
||||
k_mutex_unlock(&drv_data->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -151,50 +156,83 @@ static DEVICE_API(gpio, gpio_sn74hc595_drv_api_funcs) = {
|
||||
*/
|
||||
static int gpio_sn74hc595_init(const struct device *dev)
|
||||
{
|
||||
const struct gpio_sn74hc595_config *config = dev->config;
|
||||
int ret;
|
||||
struct gpio_sn74hc595_drv_data *drv_data = dev->data;
|
||||
const struct gpio_sn74hc595_config *config = dev->config;
|
||||
|
||||
if (!spi_is_ready_dt(&config->bus)) {
|
||||
LOG_ERR("SPI bus %s not ready", config->bus.bus->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!gpio_is_ready_dt(&config->reset_gpio)) {
|
||||
LOG_ERR("GPIO port %s not ready", config->reset_gpio.port->name);
|
||||
return -ENODEV;
|
||||
if (config->enable_gpio.port != NULL) {
|
||||
if (!gpio_is_ready_dt(&config->enable_gpio)) {
|
||||
LOG_ERR("GPIO port %s not ready", config->enable_gpio.port->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
if (gpio_pin_configure_dt(&config->enable_gpio, GPIO_OUTPUT_INACTIVE) < 0) {
|
||||
LOG_ERR("Unable to configure ENABLE GPIO pin %u", config->enable_gpio.pin);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_ACTIVE) < 0) {
|
||||
LOG_ERR("Unable to configure RST GPIO pin %u", config->reset_gpio.pin);
|
||||
return -EINVAL;
|
||||
if (config->reset_gpio.port != NULL) {
|
||||
if (!gpio_is_ready_dt(&config->reset_gpio)) {
|
||||
LOG_ERR("GPIO port %s not ready", config->reset_gpio.port->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
if (gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_ACTIVE) < 0) {
|
||||
LOG_ERR("Unable to configure RST GPIO pin %u", config->reset_gpio.pin);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* The reset signal must be stable for at least 120 ns */
|
||||
k_busy_wait(1);
|
||||
|
||||
gpio_pin_set_dt(&config->reset_gpio, 0);
|
||||
|
||||
/* The reset signal must be stable for at least 75 ns before
|
||||
* clocking the SRCLK pin
|
||||
*/
|
||||
k_busy_wait(1);
|
||||
}
|
||||
|
||||
gpio_pin_set(config->reset_gpio.port, config->reset_gpio.pin, 0);
|
||||
k_mutex_lock(&drv_data->lock, K_FOREVER);
|
||||
|
||||
drv_data->output = 0U;
|
||||
return 0;
|
||||
/* `drv_data->output` is initialized with the reset_value property from the device tree */
|
||||
ret = sn74hc595_write(dev, drv_data->output);
|
||||
|
||||
k_mutex_unlock(&drv_data->lock);
|
||||
|
||||
if (config->enable_gpio.port != NULL) {
|
||||
gpio_pin_set_dt(&config->enable_gpio, 1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define SN74HC595_SPI_OPERATION \
|
||||
#define SN74HC595_SPI_OPERATION \
|
||||
((uint16_t)(SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8)))
|
||||
|
||||
#define SN74HC595_INIT(n) \
|
||||
static struct gpio_sn74hc595_drv_data sn74hc595_data_##n = { \
|
||||
.output = 0, \
|
||||
.lock = Z_MUTEX_INITIALIZER(sn74hc595_data_##n.lock), \
|
||||
}; \
|
||||
\
|
||||
static const struct gpio_sn74hc595_config sn74hc595_config_##n = { \
|
||||
.config = { \
|
||||
.port_pin_mask = \
|
||||
GPIO_PORT_PIN_MASK_FROM_DT_INST(n), \
|
||||
}, \
|
||||
.bus = SPI_DT_SPEC_INST_GET(n, SN74HC595_SPI_OPERATION, 0), \
|
||||
.reset_gpio = GPIO_DT_SPEC_INST_GET(n, reset_gpios), \
|
||||
}; \
|
||||
\
|
||||
DEVICE_DT_DEFINE(DT_DRV_INST(n), &gpio_sn74hc595_init, NULL, \
|
||||
&sn74hc595_data_##n, &sn74hc595_config_##n, POST_KERNEL, \
|
||||
CONFIG_GPIO_SN74HC595_INIT_PRIORITY, &gpio_sn74hc595_drv_api_funcs);
|
||||
#define SN74HC595_INIT(n) \
|
||||
static struct gpio_sn74hc595_drv_data sn74hc595_data_##n = { \
|
||||
.output = DT_INST_PROP(n, reset_value), \
|
||||
.lock = Z_MUTEX_INITIALIZER(sn74hc595_data_##n.lock), \
|
||||
}; \
|
||||
\
|
||||
static const struct gpio_sn74hc595_config sn74hc595_config_##n = { \
|
||||
.config = \
|
||||
{ \
|
||||
.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(n), \
|
||||
}, \
|
||||
.bus = SPI_DT_SPEC_INST_GET(n, SN74HC595_SPI_OPERATION, 0), \
|
||||
.reset_gpio = GPIO_DT_SPEC_INST_GET_OR(n, reset_gpio, {0}), \
|
||||
.enable_gpio = GPIO_DT_SPEC_INST_GET_OR(n, enable_gpios, {0}), \
|
||||
.num_registers = DT_INST_PROP(n, ngpios) / BITS_PER_BYTE, \
|
||||
}; \
|
||||
\
|
||||
DEVICE_DT_DEFINE(DT_DRV_INST(n), &gpio_sn74hc595_init, NULL, &sn74hc595_data_##n, \
|
||||
&sn74hc595_config_##n, POST_KERNEL, CONFIG_GPIO_SN74HC595_INIT_PRIORITY, \
|
||||
&gpio_sn74hc595_drv_api_funcs);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(SN74HC595_INIT)
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
# Copyright (c) 2022, Matthias Freese
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: SN74HC595 SPI based GPIO expander
|
||||
description: |
|
||||
SN74HC595 SPI based GPIO expander.
|
||||
The SNx4HC595 is an 8-bit shift register.
|
||||
Note: There are two clock inputs, one for shifting (SRCLK) and one for storing
|
||||
(RCLK). On a rising edge, the shift clock puts a bit into the shift register,
|
||||
but does not make it appear on the output. On a rising edge of the store clock,
|
||||
all shifted in bits appear on the outputs of the shift register. One can use
|
||||
`cs-gpios` property of the shift register's parent SPI controller to work the
|
||||
load clock. If one assigns a GPIO in the GPIO_ACTIVE_LOW configuration, then as
|
||||
the CS pin goes high at the end of the SPI transaction, the shift register will
|
||||
load in all shifted bits.
|
||||
|
||||
compatible: "ti,sn74hc595"
|
||||
|
||||
@@ -10,14 +20,38 @@ include: [gpio-controller.yaml, spi-device.yaml]
|
||||
properties:
|
||||
reset-gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: Reset pin
|
||||
description: |
|
||||
An optional GPIO connected to the reset input of the shift register(s).
|
||||
It will be set to a logical 1 and then held at a logical 0 on init.
|
||||
|
||||
enable-gpios:
|
||||
type: phandle-array
|
||||
description: |
|
||||
An optional GPIO connected to the enable input of the shift register(s).
|
||||
It will be set to a logical 1 on init.
|
||||
|
||||
reset-value:
|
||||
type: int
|
||||
default: 0x0
|
||||
description: |
|
||||
The value to be shifted into the register (or registers) and loaded on
|
||||
init. This is useful when you want to start with the register(s) in a
|
||||
specific state and or you don't have control over the hard reset input of
|
||||
the register(s).
|
||||
|
||||
ngpios:
|
||||
type: int
|
||||
required: true
|
||||
const: 8
|
||||
description: Number of supported gpios
|
||||
enum:
|
||||
- 8
|
||||
- 16
|
||||
- 24
|
||||
- 32
|
||||
description: |
|
||||
The number of supported gpios. Although each shift register has only eight
|
||||
outputs, they can be chained together. Due to Zephyr's idea of a GPIO port
|
||||
only controlling up to 32 pins, the maximum number you can indicate here
|
||||
is 32, equating to four chained shift registers.
|
||||
|
||||
"#gpio-cells":
|
||||
const: 2
|
||||
|
||||
Reference in New Issue
Block a user