/* * Multicore I2C Adapter Driver * * Copyright (C) 2012 Ilya Tushkov (the-mail-box-1@yandex.ru) * 2013 Elvees (support@elvees.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * I2C driver for the Multicore CPU * * smbus_xfer and master_xfer implemented, smbus_xfer supports 7-bit addresses * only * */ //#define DEBUG #include #include #include #include #include #include #include #include #include #include "mc_i2c.h" //#define I2C_BUS_DELAY 10 #define BYTE_ONLY -1 #define SIMPLE_TRANSFER -2 extern u32 mips_hpt_frequency; #define TIMEOUT_CYCLES (mips_hpt_frequency / 30) struct mc_i2c_controller { struct i2c_adapter adapter; void __iomem *regs; }; #define to_i2cc(_adap) container_of(_adap, struct mc_i2c_controller, adapter) static inline u32 _R(struct mc_i2c_controller *i2cc, unsigned idx) { return __raw_readl(i2cc->regs + idx); } static inline void _W(struct mc_i2c_controller *i2cc, unsigned idx, u32 val) { __raw_writel(val, i2cc->regs + idx); } static int mc_i2c_wait(struct mc_i2c_controller *i2cc) { u8 status; int ret = 0; long timeout = TIMEOUT_CYCLES; while (1) { status = _R(i2cc, RG_SR); if (!(status & F_TIP)) break; if (--timeout < 0) return -ENXIO; } if (status & F_AL) { pr_debug("AL!\n"); ret = -EAGAIN; } //else if (status & F_RX_NACK) // ret = -ENXIO; if (ret) _W(i2cc, RG_CR, F_STO); return ret; } static int mc_i2c_write_byte(struct mc_i2c_controller *i2cc, u8 data, u8 flags) { _W(i2cc, RG_TXR, data); _W(i2cc, RG_CR, F_SND | flags); return mc_i2c_wait(i2cc); } static int mc_i2c_read_byte(struct mc_i2c_controller *i2cc, u8 *data, u8 flags) { int ret; _W(i2cc, RG_CR, F_RCV | flags); ret = mc_i2c_wait(i2cc); if (ret) return ret; *data = _R(i2cc, RG_RXR) & 0xFF; return 0; } static int mc_i2c_smbus_write(struct mc_i2c_controller *i2cc, u16 addr, int comm, u8 *data, u8 size) { int ret; int i; int flags = 0; if (size == 0) return -EINVAL; ret = mc_i2c_write_byte(i2cc, addr << 1, F_STA); if (ret) return ret; if (comm >= 0) { ret = mc_i2c_write_byte(i2cc, (u8)comm, 0); if (ret) return ret; } /* ret = mc_i2c_write_byte(i2cc, size, 0); if (ret) return ret; */ for (i = 0; i < size; ++i) { if (i == size - 1) flags = F_STO; ret = mc_i2c_write_byte(i2cc, data[i], flags); if (ret) return ret; } return 0; } static int mc_i2c_smbus_read(struct mc_i2c_controller *i2cc, u16 addr, int comm, u8 *data, u8 size) { int ret; int i; int flags = 0; if (size == 0) return -EINVAL; if (comm != SIMPLE_TRANSFER) { ret = mc_i2c_write_byte(i2cc, addr << 1, F_STA); if (ret) return ret; if (comm >= 0) { ret = mc_i2c_write_byte(i2cc, (u8)comm, 0); if (ret) return ret; } } ret = mc_i2c_write_byte(i2cc, (addr << 1) | 1, F_STA); if (ret) return ret; /* ret = mc_i2c_read_byte(i2cc, size, 0); if (ret) return ret; */ for (i = 0; i < size; ++i) { if (i == size - 1) flags = F_NACK | F_STO; ret = mc_i2c_read_byte(i2cc, &data[i], flags); if (ret) return ret; } return 0; } static s32 mc_i2c_access(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data) { int ret = 0; struct mc_i2c_controller *i2cc = to_i2cc(adap); long timeout = TIMEOUT_CYCLES; dev_dbg(&adap->dev, "i2c: addr = %.4x flags = %.4x cmd = %d size = %d %s\n", addr, flags, command, size, read_write == I2C_SMBUS_WRITE ? "write" : "read"); switch (size) { case I2C_SMBUS_QUICK: dev_dbg(&adap->dev, "size = I2C_SMBUS_QUICK\n"); ret = mc_i2c_write_byte(i2cc, (addr << 1) | (read_write != 0), F_STA | F_STO); break; case I2C_SMBUS_BYTE: dev_dbg(&adap->dev, "size = I2C_SMBUS_BYTE\n"); if (read_write == I2C_SMBUS_WRITE) { ret = mc_i2c_smbus_write(i2cc, addr, BYTE_ONLY, &command, 1); dev_dbg(&adap->dev, "written byte = 0x%02x\n", command); } else { ret = mc_i2c_smbus_read(i2cc, addr, BYTE_ONLY, &data->byte, 1); dev_dbg(&adap->dev, "read byte = 0x%02x\n", data->byte); } break; case I2C_SMBUS_BYTE_DATA: dev_dbg(&adap->dev, "size = I2C_SMBUS_BYTE_DATA\n"); if (read_write == I2C_SMBUS_WRITE) { ret = mc_i2c_smbus_write(i2cc, addr, command, &data->byte, 1); dev_dbg(&adap->dev, "written data = 0x%02x\n", data->byte); } else { ret = mc_i2c_smbus_read(i2cc, addr, command, &data->byte, 1); dev_dbg(&adap->dev, "read data = 0x%02x\n", data->byte); } break; case I2C_SMBUS_WORD_DATA: dev_dbg(&adap->dev, "size = I2C_SMBUS_WORD_DATA\n"); if (read_write == I2C_SMBUS_WRITE) { ret = mc_i2c_smbus_write(i2cc, addr, command, &data->byte, 2); dev_dbg(&adap->dev, "written data = 0x%04x\n", data->word); } else { ret = mc_i2c_smbus_read(i2cc, addr, command, &data->byte, 2); dev_dbg(&adap->dev, "read data = 0x%04x\n", data->word); } break; case I2C_SMBUS_I2C_BLOCK_DATA: dev_dbg(&adap->dev, "size = I2C_SMBUS_BLOCK_DATA\n"); if (read_write == I2C_SMBUS_WRITE) { ret = mc_i2c_smbus_write(i2cc, addr, command, &data->block[1], data->block[0]); } else { ret = mc_i2c_smbus_read(i2cc, addr, command, &data->block[1], data->block[0]); } break; } /* wait for the hw side to finish whatever it's doing */ //udelay(I2C_BUS_DELAY); while (_R(i2cc, RG_SR) & F_BUSY) if (--timeout < 0) return -ENXIO; dev_dbg(&adap->dev, "Result: %d\n", ret); return ret; } static int mc_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *pmsg, int num) { int i, ret; struct mc_i2c_controller *i2cc = to_i2cc(adap); long timeout = TIMEOUT_CYCLES; dev_dbg(&adap->dev, "%s: processing %d messages\n", __func__, num); for (i = 0; i < num; i++) { dev_dbg(&adap->dev, " #%d: %sing %d byte%s %s 0x%02x\n", i, pmsg->flags & I2C_M_RD ? "read" : "writ", pmsg->len, pmsg->len > 1 ? "s" : "", pmsg->flags & I2C_M_RD ? "from" : "to", pmsg->addr); if (pmsg->len && pmsg->buf) { if (pmsg->flags & I2C_M_RD) ret = mc_i2c_smbus_read(i2cc, pmsg->addr, SIMPLE_TRANSFER, pmsg->buf, pmsg->len); else ret = mc_i2c_smbus_write(i2cc, pmsg->addr, SIMPLE_TRANSFER, pmsg->buf, pmsg->len); if (ret) { dev_err(&adap->dev, "i2c xfer failed\n"); return ret; } //udelay(I2C_BUS_DELAY); while (_R(i2cc, RG_SR) & F_BUSY) if (--timeout < 0) return -ENXIO; } pmsg += 1; } return i; } static u32 mc_i2c_func(struct i2c_adapter *adapter) { return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_I2C_BLOCK; } static struct i2c_algorithm mc_i2c_algorithm = { .master_xfer = mc_i2c_xfer, .smbus_xfer = mc_i2c_access, .functionality = mc_i2c_func, }; /*--------------------------------------------------------------------*/ static __devinit int mc_i2c_probe(struct platform_device *pdev) { int ret; struct resource *r; struct mc_i2c_controller *i2cc; struct i2c_adapter *adap; i2cc = devm_kzalloc(&pdev->dev, sizeof(struct mc_i2c_controller), GFP_KERNEL); if (!i2cc) { ret = -ENOMEM; goto err_return; } adap = &i2cc->adapter; adap->owner = THIS_MODULE; adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; adap->algo = &mc_i2c_algorithm; adap->nr = pdev->id; adap->retries = 3; memcpy(adap->name, "multicore i2c adapter", 22); r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (r == NULL) { ret = -ENODEV; goto err_return; } i2cc->regs = devm_request_and_ioremap(&pdev->dev, r); if (!i2cc->regs) { ret = -ENOMEM; goto err_return; } dev_dbg(&adap->dev, "regs @ %p\n", i2cc->regs); platform_set_drvdata(pdev, i2cc); ret = i2c_add_numbered_adapter(adap); if (ret) goto err_return; _W(i2cc, RG_CTR, F_PRST); _W(i2cc, RG_CTR, F_EN); _W(i2cc, RG_PRER, mips_hpt_frequency / (5 * CONFIG_MULTICORE_I2C_SPEED * 1000) - 1); printk("mc_i2c: found controller #%d\n", pdev->id); return 0; err_return: return ret; } static __devexit int mc_i2c_remove(struct platform_device *pdev) { return 0; } static struct platform_driver mc_i2c_driver = { .driver = { .name = "mc_i2c", .owner = THIS_MODULE, }, .probe = mc_i2c_probe, .remove = __devexit_p(mc_i2c_remove), }; module_platform_driver(mc_i2c_driver); MODULE_DESCRIPTION("Multicore I2C Controller driver"); MODULE_AUTHOR("Dmitry Podkhvatilin"); MODULE_LICENSE("GPL");