| /* |
| * Copyright (c) 2020 Vestas Wind Systems A/S |
| * Copyright (c) 2017 Linaro Ltd. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT gpio_i2c |
| |
| /** |
| * @file |
| * @brief Driver for software driven I2C using GPIO lines |
| * |
| * This driver implements an I2C interface by driving two GPIO lines under |
| * software control. |
| * |
| * The GPIO pins used must be configured (through devicetree and pinmux) with |
| * suitable flags, i.e. the SDA pin as open-collector/open-drain with a pull-up |
| * resistor (possibly as an external component attached to the pin). |
| * |
| * When the SDA pin is read it must return the state of the physical hardware |
| * line, not just the last state written to it for output. |
| * |
| * The SCL pin should be configured in the same manner as SDA, or, if it is |
| * known that the hardware attached to pin doesn't attempt clock stretching, |
| * then the SCL pin may be a push/pull output. |
| */ |
| |
| #include <zephyr/device.h> |
| #include <errno.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/i2c.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(i2c_gpio); |
| |
| #include "i2c-priv.h" |
| #include "i2c_bitbang.h" |
| |
| /* Driver config */ |
| struct i2c_gpio_config { |
| struct gpio_dt_spec scl_gpio; |
| struct gpio_dt_spec sda_gpio; |
| uint32_t bitrate; |
| }; |
| |
| /* Driver instance data */ |
| struct i2c_gpio_context { |
| struct i2c_bitbang bitbang; /* Bit-bang library data */ |
| struct k_mutex mutex; |
| }; |
| |
| static void i2c_gpio_set_scl(void *io_context, int state) |
| { |
| const struct i2c_gpio_config *config = io_context; |
| |
| gpio_pin_set_dt(&config->scl_gpio, state); |
| } |
| |
| static void i2c_gpio_set_sda(void *io_context, int state) |
| { |
| const struct i2c_gpio_config *config = io_context; |
| |
| gpio_pin_set_dt(&config->sda_gpio, state); |
| } |
| |
| static int i2c_gpio_get_sda(void *io_context) |
| { |
| const struct i2c_gpio_config *config = io_context; |
| int rc = gpio_pin_get_dt(&config->sda_gpio); |
| |
| /* Default high as that would be a NACK */ |
| return rc != 0; |
| } |
| |
| static const struct i2c_bitbang_io io_fns = { |
| .set_scl = &i2c_gpio_set_scl, |
| .set_sda = &i2c_gpio_set_sda, |
| .get_sda = &i2c_gpio_get_sda, |
| }; |
| |
| static int i2c_gpio_configure(const struct device *dev, uint32_t dev_config) |
| { |
| struct i2c_gpio_context *context = dev->data; |
| int rc; |
| |
| k_mutex_lock(&context->mutex, K_FOREVER); |
| |
| rc = i2c_bitbang_configure(&context->bitbang, dev_config); |
| |
| k_mutex_unlock(&context->mutex); |
| |
| return rc; |
| } |
| |
| static int i2c_gpio_transfer(const struct device *dev, struct i2c_msg *msgs, |
| uint8_t num_msgs, uint16_t slave_address) |
| { |
| struct i2c_gpio_context *context = dev->data; |
| int rc; |
| |
| k_mutex_lock(&context->mutex, K_FOREVER); |
| |
| rc = i2c_bitbang_transfer(&context->bitbang, msgs, num_msgs, |
| slave_address); |
| |
| k_mutex_unlock(&context->mutex); |
| |
| return rc; |
| } |
| |
| static int i2c_gpio_recover_bus(const struct device *dev) |
| { |
| struct i2c_gpio_context *context = dev->data; |
| int rc; |
| |
| k_mutex_lock(&context->mutex, K_FOREVER); |
| |
| rc = i2c_bitbang_recover_bus(&context->bitbang); |
| |
| k_mutex_unlock(&context->mutex); |
| |
| return rc; |
| } |
| |
| static struct i2c_driver_api api = { |
| .configure = i2c_gpio_configure, |
| .transfer = i2c_gpio_transfer, |
| .recover_bus = i2c_gpio_recover_bus, |
| }; |
| |
| static int i2c_gpio_init(const struct device *dev) |
| { |
| struct i2c_gpio_context *context = dev->data; |
| const struct i2c_gpio_config *config = dev->config; |
| uint32_t bitrate_cfg; |
| int err; |
| |
| if (!gpio_is_ready_dt(&config->scl_gpio)) { |
| LOG_ERR("SCL GPIO device not ready"); |
| return -ENODEV; |
| } |
| |
| err = gpio_pin_configure_dt(&config->scl_gpio, GPIO_OUTPUT_HIGH); |
| if (err) { |
| LOG_ERR("failed to configure SCL GPIO pin (err %d)", err); |
| return err; |
| } |
| |
| if (!gpio_is_ready_dt(&config->sda_gpio)) { |
| LOG_ERR("SDA GPIO device not ready"); |
| return -ENODEV; |
| } |
| |
| err = gpio_pin_configure_dt(&config->sda_gpio, |
| GPIO_INPUT | GPIO_OUTPUT_HIGH); |
| if (err == -ENOTSUP) { |
| err = gpio_pin_configure_dt(&config->sda_gpio, |
| GPIO_OUTPUT_HIGH); |
| } |
| if (err) { |
| LOG_ERR("failed to configure SDA GPIO pin (err %d)", err); |
| return err; |
| } |
| |
| i2c_bitbang_init(&context->bitbang, &io_fns, (void *)config); |
| |
| bitrate_cfg = i2c_map_dt_bitrate(config->bitrate); |
| err = i2c_bitbang_configure(&context->bitbang, |
| I2C_MODE_CONTROLLER | bitrate_cfg); |
| if (err) { |
| LOG_ERR("failed to configure I2C bitbang (err %d)", err); |
| return err; |
| } |
| |
| err = k_mutex_init(&context->mutex); |
| if (err) { |
| LOG_ERR("Failed to create the i2c lock mutex : %d", err); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| #define DEFINE_I2C_GPIO(_num) \ |
| \ |
| static struct i2c_gpio_context i2c_gpio_dev_data_##_num; \ |
| \ |
| static const struct i2c_gpio_config i2c_gpio_dev_cfg_##_num = { \ |
| .scl_gpio = GPIO_DT_SPEC_INST_GET(_num, scl_gpios), \ |
| .sda_gpio = GPIO_DT_SPEC_INST_GET(_num, sda_gpios), \ |
| .bitrate = DT_INST_PROP(_num, clock_frequency), \ |
| }; \ |
| \ |
| I2C_DEVICE_DT_INST_DEFINE(_num, \ |
| i2c_gpio_init, \ |
| NULL, \ |
| &i2c_gpio_dev_data_##_num, \ |
| &i2c_gpio_dev_cfg_##_num, \ |
| POST_KERNEL, CONFIG_I2C_INIT_PRIORITY, &api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(DEFINE_I2C_GPIO) |