處理器只支持3個i2c通道,常常會不夠用,最近寫了一個gpio模擬i2c的driver,把模擬的i2c通道加入了i2c-core中,作為第 4 通道,調用接口與標准i2c一致,代碼如下:
[cpp]
#define DELAY 2
#define SCL_GPIO GPIO_I2C_SCL
#define SDA_GPIO GPIO_I2C_SDA
static inline void i2c_delay(uint16_t delay)
{
udelay(delay);
}
static inline void set_scl_low(void)
{
gpio_direction_output(SCL_GPIO, 0);
}
static inline void set_scl_high(void)
{
gpio_direction_output(SCL_GPIO, 1);
}
static inline void set_sda_low(void)
{
gpio_direction_output(SDA_GPIO, 0);
}
static inline void set_sda_high(void)
{
gpio_direction_output(SDA_GPIO, 1);
}
static inline void set_sda_in(void)
{
gpio_direction_input(SDA_GPIO);
}
static inline uint8_t get_sda_bit(void)
{
return __gpio_get_value(SDA_GPIO);
}
int i2c_gpio_init(void)
{
int err;
err = gpio_request(SCL_GPIO, NULL);
if (err != 0)
return err;
err = gpio_request(SDA_GPIO, NULL);
set_sda_high();
set_scl_high();
return err;
}
void i2c_gpio_free(void)
{
gpio_free(SDA_GPIO);
gpio_free(SCL_GPIO);
}
static inline void i2c_start(void)
{
set_sda_high();
i2c_delay(DELAY);
set_scl_high();
i2c_delay(DELAY);
set_sda_low();
i2c_delay(DELAY);
set_scl_low();
i2c_delay(DELAY);
}
static inline void i2c_stop(void)
{
set_sda_low();
i2c_delay(DELAY);
set_scl_high();
i2c_delay(4*DELAY);
set_sda_high();
i2c_delay(4*DELAY);
}
/*
* return value:
* 0 --- 收到ACK
* 1 --- 沒收到ACK
*/
uint8_t i2c_send_byte(uint8_t send_byte)
{
uint8_t rc = 0;
uint8_t out_mask = 0x80;
uint8_t count = 8;
uint8_t value;
while(count > 0) {
set_scl_low();
i2c_delay(DELAY);
value = ((send_byte & out_mask) ? 1 : 0);
if(value == 1) {
set_sda_high();
} else {
set_sda_low();
}
send_byte <<= 1;
i2c_delay(DELAY);
set_scl_high();
i2c_delay(DELAY);
count--;
}
set_scl_low();
set_sda_in();
i2c_delay(4*DELAY);
set_scl_high();
i2c_delay(DELAY);
rc = get_sda_bit();
i2c_delay(DELAY);
set_scl_low();
return rc;
}
/*
* ack = 0 發送ACK
* ack = 1 發送非ACK停止讀取
*/
void i2c_read_byte(uint8_t *buffer, uint8_t ack)
{
uint8_t count = 0x08;
uint8_t data = 0x00;
uint8_t temp = 0;
while(count > 0) {
set_scl_low();
i2c_delay(2*DELAY);
if(count == 8)
set_sda_in();
i2c_delay(DELAY);
set_scl_high();
i2c_delay(2*DELAY);
temp = get_sda_bit();
data <<= 1;
if(temp)
data |= 0x01;
i2c_delay(DELAY);
count--;
}
set_scl_low();
i2c_delay(2*DELAY);
if(ack) {
set_sda_high();
} else {
set_sda_low();
}
i2c_delay(DELAY);
set_scl_high();
i2c_delay(2*DELAY);
*buffer = data;
set_scl_low();
}
struct atxx_i2c_gpio {
struct i2c_adapter adap;
struct device *dev;
struct clk *clk;
struct i2c_msg *msg;
spinlock_t lock;
};
static int send_i2c(struct atxx_i2c_gpio *i2c)
{
int i;
uint8_t ack;
spin_lock_irq(&i2c->lock);
i2c_start();
ack = i2c_send_byte((i2c->msg->addr << 1) | 0x00);
if(ack){
goto out;
}
for(i = 0; i < i2c->msg->len; i++) {
ack = i2c_send_byte(i2c->msg->buf[i]);
if(ack){
goto out;
}
}
out:
i2c_stop();
spin_unlock_irq(&i2c->lock);
return ack;
}
static int recv_i2c(struct atxx_i2c_gpio *i2c)
{
int i;
uint8_t ack;
spin_lock_irq(&i2c->lock);
i2c_start();
ack = i2c_send_byte((i2c->msg->addr << 1) | 0x01);
if(ack){
goto out;
}
for(i = 0; i < i2c->msg->len - 1; i++) {
i2c_read_byte(&i2c->msg->buf[i], 0);
}
i2c_read_byte(&i2c->msg->buf[i2c->msg->len - 1], 1);
out:
i2c_stop();
spin_unlock_irq(&i2c->lock);
return ack;
}
static int i2c_atxx_gpio_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
int i, ret;
struct atxx_i2c_gpio *i2c = i2c_get_adapdata(adap);
for (i = 0; i < num; i++) {
i2c->msg = &msgs[i];
i2c->msg->flags = msgs[i].flags;
if (i2c->msg->flags & I2C_M_RD) {
ret = recv_i2c(i2c);
if (ret) {
printk("recv_i2c failed. dev_name(%s).addr = 0x%02x\n",
dev_name(i2c->dev), i2c->msg[0].addr);
return -EAGAIN;
}
} else {
ret = send_i2c(i2c);
if (ret) {
printk("send_i2c failed. dev_name(%s).addr = 0x%02x\n",
dev_name(i2c->dev), i2c->msg[0].addr);
return -EAGAIN;
}
}
}
return num;
}
static uint32_t i2c_atxx_gpio_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
static struct i2c_algorithm i2c_atxx_gpio_algo = {
.master_xfer = i2c_atxx_gpio_xfer,
.functionality = i2c_atxx_gpio_func,
};
static int i2c_atxx_gpio_probe(struct platform_device *pdev)
{
int ret;
struct atxx_i2c_gpio *i2c;
ret = i2c_gpio_init();
if(ret) {
dev_err(&pdev->dev, "couldn't request gpio\n");
return ret;
}
i2c = kzalloc(sizeof(*i2c), GFP_KERNEL);
if (!i2c) {
dev_err(&pdev->dev, "couldn't allocate memory\n");;
ret = -ENOMEM;
goto err_mem;
}
spin_lock_init(&i2c->lock);
i2c->dev = &pdev->dev;
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &i2c_atxx_gpio_algo;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->adap.timeout = I2C_ATXX_TIMEOUT;
i2c->adap.retries = I2C_ATXX_RETRIES;
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
i2c->adap.nr = pdev->id != -1 ? pdev->id : 0;
sprintf(i2c->adap.name, "ATXX i2c gpio adapter");
platform_set_drvdata(pdev, i2c);
i2c_set_adapdata(&i2c->adap, i2c);
ret = i2c_add_numbered_adapter(&i2c->adap);
if (ret) {
dev_err(i2c->dev, "failure adding adapter\n");
goto err_adp;
}
return 0;
err_adp:
kfree(i2c);
err_mem:
i2c_gpio_free();
return ret;
}
static int i2c_atxx_gpio_remove(struct platform_device *pdev)
{
struct atxx_i2c_gpio *i2c = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL);
i2c_del_adapter(&i2c->adap);
kfree(i2c);
i2c_gpio_free();
return 0;
}
static int i2c_atxx_gpio_suspend(struct device *dev)
{
return 0;
}
static int i2c_atxx_gpio_resume(struct device *dev)
{
return 0;
}
static void i2c_atxx_gpio_shutdown(struct platform_device *pdev)
{
/* TODO: */
}
const struct dev_pm_ops i2c_atxx_gpio_pm_ops = {
.suspend = i2c_atxx_gpio_suspend,
.resume = i2c_atxx_gpio_resume,
};
static struct platform_driver i2c_atxx_gpio_driver = {
.driver = {
.name = "i2c-gpio",
.owner = THIS_MODULE,
.pm = &i2c_atxx_gpio_pm_ops,
},
.probe = i2c_atxx_gpio_probe,
.remove = i2c_atxx_gpio_remove,
.shutdown = i2c_atxx_gpio_shutdown,
};
static int __init i2c_adap_atxx_gpio_init(void)
{
return platform_driver_register(&i2c_atxx_gpio_driver);
}
static void __exit i2c_adap_atxx_gpio_exit(void)
{
platform_driver_unregister(&i2c_atxx_gpio_driver);
}
arch_initcall(i2c_adap_atxx_gpio_init);