blob: 157212dc2b9dc3396b3f131ed785660da16d9c78 [file] [log] [blame]
/*
* 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 */
};
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;
return i2c_bitbang_configure(&context->bitbang, dev_config);
}
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;
return i2c_bitbang_transfer(&context->bitbang, msgs, num_msgs,
slave_address);
}
static int i2c_gpio_recover_bus(const struct device *dev)
{
struct i2c_gpio_context *context = dev->data;
return i2c_bitbang_recover_bus(&context->bitbang);
}
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 (!device_is_ready(config->scl_gpio.port)) {
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 (!device_is_ready(config->sda_gpio.port)) {
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, config);
bitrate_cfg = i2c_map_dt_bitrate(config->bitrate);
err = i2c_bitbang_configure(&context->bitbang,
I2C_MODE_MASTER | bitrate_cfg);
if (err) {
LOG_ERR("failed to configure I2C bitbang (err %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)