|
/*
|
|
* i2c.c
|
|
*
|
|
* Created on: Apr 20, 2010
|
|
* Author: Mike Williamson
|
|
*/
|
|
|
|
#include "common/fpga/fpga_i2c.h"
|
|
#include "common/fpga/core_ids.h"
|
|
#include "fpga.h"
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/device.h>
|
|
#include <linux/i2c.h>
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Mike Williamson <michael.williamson@criticallink.com");
|
|
MODULE_DESCRIPTION("Driver for MityDSP-L138 FPGA Based I2C Interface");
|
|
|
|
/**
|
|
* this is the device driver specific parameters tied to each i2c device in the system
|
|
* (the object data)
|
|
*/
|
|
struct fpga_i2c {
|
|
struct i2c_adapter adapter;
|
|
};
|
|
|
|
/*
|
|
* Low level master read/write transaction. This function is called
|
|
* from i2c_xfer.
|
|
*
|
|
* \param[in] adap pointer to our adaptor device
|
|
* \param[in] msg message to send
|
|
* \param[in] stop true if this is the last message in a set
|
|
*
|
|
* \return number of bytes transmitted, or < 0 error condition
|
|
*/
|
|
static int
|
|
fpga_i2c_xfer_msg(struct i2c_adapter *adap, struct i2c_msg *msg, int stop)
|
|
{
|
|
int i, slp_cnt;
|
|
tuI2cCSR CSR;
|
|
tuI2cSAR SAR;
|
|
unsigned short *lpBaseReg;
|
|
struct device *dev;
|
|
struct fpga_i2c *driver_data;
|
|
struct fpga_device* fpgadev;
|
|
|
|
dev = i2c_get_adapdata(adap);
|
|
fpgadev = to_fpga_device(dev);
|
|
driver_data = dev_get_drvdata(dev);
|
|
lpBaseReg = (unsigned short*)fpgadev->baseaddr;
|
|
|
|
dev_dbg(dev, "Msg Transfer - R/W = %d, len = %d\n", msg->flags & I2C_M_RD ? 1 : 0, msg->len);
|
|
|
|
/* Can't handle 10 bit addresses yet */
|
|
if (msg->flags & I2C_M_TEN)
|
|
{
|
|
dev_err(dev, "Msg Transfer 10 Bit Address Requested\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (msg->len > FPGA_I2C_FIFO_DEPTH)
|
|
{
|
|
dev_err(dev, "Msg Transfer Message Length too big, %d\n", msg->len);
|
|
return -EIO;
|
|
}
|
|
|
|
SAR.mnWord = 0;
|
|
SAR.msBits.mnSlaveAddr = msg->addr;
|
|
|
|
lpBaseReg[gnSAR_OFFSET] = SAR.mnWord;
|
|
|
|
lpBaseReg[gnCNT_OFFSET] = msg->len;
|
|
|
|
slp_cnt = 0;
|
|
|
|
/** TODO - if transfer length is > 32 we need to
|
|
* loop in multiples of 32 (without setting stop)
|
|
* until we're done...
|
|
*/
|
|
if (msg->flags & I2C_M_RD)
|
|
{
|
|
/* Handle Bus Read */
|
|
CSR.mnWord = 0;
|
|
CSR.msBits.mbFifoReset = 1;
|
|
lpBaseReg[gnCSR_OFFSET] = CSR.mnWord;
|
|
wmb();
|
|
|
|
CSR.mnWord = 0;
|
|
CSR.msBits.mb10Bit = 0;
|
|
CSR.msBits.mbRnW = 1;
|
|
CSR.msBits.mbStop = stop;
|
|
CSR.msBits.mbDone = 1;
|
|
CSR.msBits.mbGo = 1;
|
|
lpBaseReg[gnCSR_OFFSET] = CSR.mnWord;
|
|
wmb();
|
|
|
|
/* wait until done */
|
|
CSR.mnWord = lpBaseReg[gnCSR_OFFSET];
|
|
|
|
while (!CSR.msBits.mbDone) {
|
|
msleep(1);
|
|
rmb();
|
|
CSR.mnWord = lpBaseReg[gnCSR_OFFSET];
|
|
if (++slp_cnt > 250)
|
|
break;
|
|
};
|
|
|
|
if (slp_cnt > 250) {
|
|
dev_dbg(dev, "Timeout on write request\n");
|
|
return -EIO;
|
|
}
|
|
|
|
/* go fetch the data */
|
|
for (i = 0; i < msg->len; i++)
|
|
{
|
|
tuI2cFDR DataReg;
|
|
DataReg.mnWord = lpBaseReg[gnFDR_OFFSET];
|
|
msg->buf[i] = DataReg.msBits.mnData;
|
|
rmb();
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
/* clear the buffers */
|
|
dev_dbg(dev, "Loading buffer with %d bytes\n", msg->len);
|
|
|
|
CSR.mnWord = 0;
|
|
CSR.msBits.mbFifoReset = 1;
|
|
lpBaseReg[gnCSR_OFFSET] = CSR.mnWord;
|
|
wmb();
|
|
|
|
CSR.msBits.mbFifoReset = 0;
|
|
lpBaseReg[gnCSR_OFFSET] = CSR.mnWord;
|
|
wmb();
|
|
|
|
/* load the buffer */
|
|
for (i = 0; i < msg->len; i++)
|
|
{
|
|
tuI2cFDR DataReg;
|
|
DataReg.mnWord = 0;
|
|
DataReg.msBits.mnData = msg->buf[i];
|
|
lpBaseReg[gnFDR_OFFSET] = DataReg.mnWord;
|
|
wmb();
|
|
}
|
|
|
|
/* Handle Bus Write */
|
|
CSR.mnWord = 0;
|
|
CSR.msBits.mb10Bit = 0;
|
|
CSR.msBits.mbRnW = 0;
|
|
CSR.msBits.mbStop = stop;
|
|
CSR.msBits.mbDone = 1;
|
|
CSR.msBits.mbGo = 1;
|
|
lpBaseReg[gnCSR_OFFSET] = CSR.mnWord;
|
|
wmb();
|
|
|
|
/* wait until done */
|
|
CSR.mnWord = lpBaseReg[gnCSR_OFFSET];
|
|
|
|
while (!CSR.msBits.mbDone) {
|
|
msleep(1);
|
|
rmb();
|
|
CSR.mnWord = lpBaseReg[gnCSR_OFFSET];
|
|
if (++slp_cnt > 250)
|
|
break;
|
|
};
|
|
|
|
if (slp_cnt > 250) {
|
|
dev_dbg(dev, "Timeout on write request\n");
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
/** check status for ack errors, etc. */
|
|
CSR.mnWord = lpBaseReg[gnCSR_OFFSET];
|
|
if (CSR.msBits.mbAckErr) {
|
|
dev_dbg(dev, "Ack Error\n");
|
|
return -EIO;
|
|
}
|
|
|
|
dev_dbg(dev, "transfer returning %d\n", msg->len);
|
|
return msg->len;
|
|
}
|
|
|
|
/*
|
|
* Prepare controller for a transaction and call i2c_davinci_xfer_msg
|
|
*
|
|
* \param[in] adap pointer to our adaptor device
|
|
* \param[in] msgs pointer to a list of messages to send
|
|
* \param[in] num number of messages to transfer
|
|
*/
|
|
static int
|
|
fpga_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
|
|
{
|
|
struct device *dev = i2c_get_adapdata(adap);
|
|
int i;
|
|
int ret;
|
|
|
|
dev_dbg(dev, "%s: msgs: %d\n", __func__, num);
|
|
|
|
/*
|
|
ret = i2c_wait_bus_not_busy(dev, 1);
|
|
if (ret < 0) {
|
|
dev_warn(dev->dev, "timeout waiting for bus ready\n");
|
|
return ret;
|
|
}
|
|
*/
|
|
|
|
for (i = 0; i < num; i++) {
|
|
ret = fpga_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1)));
|
|
dev_dbg(dev, "%s [%d/%d] ret: %d\n", __func__, i + 1, num, ret);
|
|
if (ret < 0)
|
|
{
|
|
dev_dbg(dev, "Failed to transfer message : %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
static u32 fpga_i2c_func(struct i2c_adapter *adap)
|
|
{
|
|
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
|
|
}
|
|
|
|
static struct i2c_algorithm i2c_algo = {
|
|
.master_xfer = fpga_i2c_xfer,
|
|
.functionality = fpga_i2c_func,
|
|
};
|
|
|
|
static struct attribute *fpga_i2c_attributes[] = {
|
|
/* &dev_attr_pen_down.attr, */
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group fpga_i2c_attr_group = {
|
|
.attrs = fpga_i2c_attributes,
|
|
};
|
|
|
|
/**
|
|
* IRQ handler called when an ADS7843 core is asserting an interrupt
|
|
* condition. This method is called within the context of an ISR, and
|
|
* must be atomic.
|
|
*
|
|
* \param[in] dev the device issuing the interrupt.
|
|
* \return 0
|
|
*/
|
|
static int fpga_i2c_IrqHandler(struct fpga_device* dev)
|
|
{
|
|
struct i2c *driver_data;
|
|
unsigned short *lpBaseReg;
|
|
|
|
driver_data = dev_get_drvdata(&dev->dev);
|
|
lpBaseReg = (unsigned short*)dev->baseaddr;
|
|
|
|
/* TODO - interrupts not yet designed into core */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* This routine is called when a device is removed from the FPGA bus.
|
|
*
|
|
* \param[in] dev pointer to the device being removed.
|
|
*/
|
|
static int fpga_i2c_remove(struct device *dev)
|
|
{
|
|
int rv = 0;
|
|
struct fpga_i2c *driver_data;
|
|
struct fpga_device* fpgadev = to_fpga_device(dev);
|
|
driver_data = dev_get_drvdata(dev);
|
|
|
|
dev_dbg(dev, "i2c_remove entered\n");
|
|
|
|
i2c_del_adapter(&driver_data->adapter);
|
|
|
|
sysfs_remove_group(&dev->kobj, &fpga_i2c_attr_group);
|
|
|
|
/* make sure IRQ is disabled */
|
|
enable_irq_vec(fpgadev->coreversion.ver0.bits.level,
|
|
fpgadev->coreversion.ver0.bits.vector,
|
|
0);
|
|
|
|
kfree(driver_data);
|
|
|
|
dev_dbg(dev, "i2c_remove completed\n");
|
|
return rv;
|
|
}
|
|
|
|
static int fpga_i2c_probe(struct device *dev);
|
|
|
|
/**
|
|
* The driver object. The information here must be common for
|
|
* all of the potential drivers in the system.
|
|
*/
|
|
static struct fpga_driver fpga_i2c_driver = {
|
|
.id = CORE_ID_I2C, /** must match value in core_ids.h */
|
|
.version = "I2C Driver 1.0",
|
|
.IrqHandler = fpga_i2c_IrqHandler,
|
|
.probe = fpga_i2c_probe,
|
|
.remove = fpga_i2c_remove,
|
|
.driver = {
|
|
.name = "fpga_i2c",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
int adapter_number = 2;
|
|
module_param (adapter_number, int, S_IRUSR | S_IRGRP | S_IROTH);
|
|
MODULE_PARM_DESC (adapter_number, "The base adaptor number for "
|
|
"new I2C busses (default 2)");
|
|
|
|
/**
|
|
* The i2c_probe routine is called after the i2c driver is successfully
|
|
* matched to an FPGA core with the same core ID.
|
|
*
|
|
* \param[in] dev device within an fpga_device structure.
|
|
* return 0 on successful probe / initialization.
|
|
*/
|
|
static int fpga_i2c_probe(struct device *dev)
|
|
{
|
|
int rv = 0;
|
|
struct i2c_adapter* adap;
|
|
struct fpga_i2c *driver_data = NULL;
|
|
unsigned short *lpBaseReg = NULL;
|
|
|
|
struct fpga_device* fpgadev = to_fpga_device(dev);
|
|
|
|
lpBaseReg = (unsigned short*)fpgadev->baseaddr;
|
|
|
|
dev_dbg(dev, "i2c_probe() entered\n");
|
|
|
|
driver_data = kzalloc(sizeof(struct fpga_i2c), GFP_KERNEL);
|
|
if (!driver_data)
|
|
{
|
|
rv = -ENOMEM;
|
|
goto probe_bail;
|
|
}
|
|
|
|
rv = sysfs_create_group(&dev->kobj, &fpga_i2c_attr_group);
|
|
if (rv)
|
|
{
|
|
dev_err(dev, "i2c_probe() failed to add attributes group - %d\n", rv);
|
|
goto probe_bail_free_driver;
|
|
}
|
|
|
|
dev_set_drvdata(dev, driver_data);
|
|
|
|
adap = &driver_data->adapter;
|
|
i2c_set_adapdata(adap, dev);
|
|
adap->owner = THIS_MODULE;
|
|
adap->class = I2C_CLASS_HWMON;
|
|
strlcpy(adap->name, "MityDSP FPGA I2C adapter", sizeof(adap->name));
|
|
adap->algo = &i2c_algo;
|
|
adap->dev.parent = dev;
|
|
adap->timeout = FPGA_I2C_TIMEOUT;
|
|
|
|
adap->nr = adapter_number++;
|
|
rv = i2c_add_numbered_adapter(adap);
|
|
if (rv) {
|
|
dev_err(dev, "failure adding adapter\n");
|
|
goto probe_bail_free_adapt;
|
|
}
|
|
|
|
/* Set the frequency */
|
|
lpBaseReg[gnDIV_OFFSET] = 0xFF; /* TODO! */
|
|
wmb();
|
|
|
|
enable_irq_vec(fpgadev->coreversion.ver0.bits.level,
|
|
fpgadev->coreversion.ver0.bits.vector,
|
|
1);
|
|
|
|
return rv;
|
|
|
|
probe_bail_free_adapt:
|
|
|
|
|
|
probe_bail_free_driver:
|
|
kfree(driver_data);
|
|
|
|
probe_bail:
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* Called when the module containing this driver is loaded.
|
|
*/
|
|
static int __init fpga_i2c_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = register_fpga_driver(&fpga_i2c_driver);
|
|
if (ret)
|
|
{
|
|
printk(KERN_ALERT "Unable to register i2c_driver\n");
|
|
goto exit_bail;
|
|
}
|
|
|
|
return 0;
|
|
|
|
exit_bail: /* Uh-Oh */
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Called when the module containing this driver is unloaded from kernel.
|
|
*/
|
|
static void fpga_i2c_exit(void)
|
|
{
|
|
driver_unregister(&fpga_i2c_driver.driver);
|
|
}
|
|
|
|
module_init(fpga_i2c_init);
|
|
module_exit(fpga_i2c_exit);
|
|
|
|
|