|  | /* | 
|  | * Copyright (c) 2022 Matthias Freese | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT ti_sn74hc595 | 
|  |  | 
|  | /** | 
|  | * @file Driver for 74 HC shift register | 
|  | */ | 
|  |  | 
|  | #include <zephyr/drivers/gpio.h> | 
|  | #include <zephyr/drivers/spi.h> | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/kernel.h> | 
|  |  | 
|  | #include <zephyr/drivers/gpio/gpio_utils.h> | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(gpio_sn74hc595, CONFIG_GPIO_LOG_LEVEL); | 
|  |  | 
|  | #if CONFIG_SPI_INIT_PRIORITY >= CONFIG_GPIO_SN74HC595_INIT_PRIORITY | 
|  | #error SPI_INIT_PRIORITY must be lower than SN74HC595_INIT_PRIORITY | 
|  | #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 reset_gpio; | 
|  | }; | 
|  |  | 
|  | struct gpio_sn74hc595_drv_data { | 
|  | /* gpio_driver_data needs to be first */ | 
|  | struct gpio_driver_data data; | 
|  |  | 
|  | struct k_mutex lock; | 
|  | uint8_t output; | 
|  | }; | 
|  |  | 
|  | static int sn74hc595_spi_write(const struct device *dev, void *buf, size_t len_bytes) | 
|  | { | 
|  | const struct gpio_sn74hc595_config *config = dev->config; | 
|  |  | 
|  | __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 }; | 
|  |  | 
|  | return spi_write_dt(&config->bus, &tx); | 
|  | } | 
|  |  | 
|  | static int gpio_sn74hc595_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) | 
|  | { | 
|  | ARG_UNUSED(dev); | 
|  | ARG_UNUSED(pin); | 
|  | ARG_UNUSED(flags); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int gpio_sn74hc595_port_get_raw(const struct device *dev, uint32_t *value) | 
|  | { | 
|  | struct gpio_sn74hc595_drv_data *drv_data = dev->data; | 
|  |  | 
|  | k_mutex_lock(&drv_data->lock, K_FOREVER); | 
|  |  | 
|  | *value = drv_data->output; | 
|  |  | 
|  | k_mutex_unlock(&drv_data->lock); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int gpio_sn74hc595_port_set_masked_raw(const struct device *dev, uint32_t mask, | 
|  | uint32_t value) | 
|  | { | 
|  | struct gpio_sn74hc595_drv_data *drv_data = dev->data; | 
|  | int ret = 0; | 
|  | uint8_t output; | 
|  |  | 
|  | k_mutex_lock(&drv_data->lock, K_FOREVER); | 
|  |  | 
|  | /* check if we need to do something at all      */ | 
|  | /* current output differs from new masked value */ | 
|  | 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; | 
|  | } | 
|  |  | 
|  | unlock: | 
|  | k_mutex_unlock(&drv_data->lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int gpio_sn74hc595_port_set_bits_raw(const struct device *dev, uint32_t mask) | 
|  | { | 
|  | return gpio_sn74hc595_port_set_masked_raw(dev, mask, mask); | 
|  | } | 
|  |  | 
|  | static int gpio_sn74hc595_port_clear_bits_raw(const struct device *dev, uint32_t mask) | 
|  | { | 
|  | return gpio_sn74hc595_port_set_masked_raw(dev, mask, 0U); | 
|  | } | 
|  |  | 
|  | static int gpio_sn74hc595_port_toggle_bits(const struct device *dev, uint32_t mask) | 
|  | { | 
|  | struct gpio_sn74hc595_drv_data *drv_data = dev->data; | 
|  | int ret; | 
|  | uint8_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; | 
|  | } | 
|  |  | 
|  | drv_data->output ^= mask; | 
|  |  | 
|  | unlock: | 
|  | k_mutex_unlock(&drv_data->lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static DEVICE_API(gpio, gpio_sn74hc595_drv_api_funcs) = { | 
|  | .pin_configure = gpio_sn74hc595_config, | 
|  | .port_get_raw = gpio_sn74hc595_port_get_raw, | 
|  | .port_set_masked_raw = gpio_sn74hc595_port_set_masked_raw, | 
|  | .port_set_bits_raw = gpio_sn74hc595_port_set_bits_raw, | 
|  | .port_clear_bits_raw = gpio_sn74hc595_port_clear_bits_raw, | 
|  | .port_toggle_bits = gpio_sn74hc595_port_toggle_bits, | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * @brief Initialization function of sn74hc595 | 
|  | * | 
|  | * @param dev Device struct | 
|  | * @return 0 if successful, failed otherwise. | 
|  | */ | 
|  | static int gpio_sn74hc595_init(const struct device *dev) | 
|  | { | 
|  | const struct gpio_sn74hc595_config *config = dev->config; | 
|  | struct gpio_sn74hc595_drv_data *drv_data = dev->data; | 
|  |  | 
|  | 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 (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; | 
|  | } | 
|  |  | 
|  | gpio_pin_set(config->reset_gpio.port, config->reset_gpio.pin, 0); | 
|  |  | 
|  | drv_data->output = 0U; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #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); | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(SN74HC595_INIT) |