/*++
drivers/i2c/busses/wmt-i2c-slave-bus.c
Copyright (c) 2008 WonderMedia Technologies, Inc.
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,
either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see .
WonderMedia Technologies, Inc.
10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C.
History:
2010/03/11 First Version
--*/
#include
#define WMT_I2C_SLAVE_C
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "./wmt-i2c-slave-bus.h"
/*#define DEBUG*/
#ifdef DEBUG
#define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __func__ , ## args)
#else
#define DPRINTK(fmt, args...)
#endif
#define DEVICE_NAME "WMT_I2C_SLAVE-0"
#define PMC_ClOCK_ENABLE_LOWER 0xd8130250
#define CTRL_GPIO21 0xD8110055
#define PU_EN_GPIO21 0xD8110495
#define PU_CTRL_GPIO21 0xD81104D5
#define DISABLE_I2C_SLAVE BIT15
struct i2c_slave_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_RD 0x01
__u16 len; /* data length */
__u8 *buf; /* pointer to data */
};
struct slave_i2c_dev_s {
/* module parameters */
char *buf;
/* char dev struct */
struct cdev cdev;
struct class *class_slave_i2c;
};
struct wmt_slave_i2c_s {
struct i2c_regs_s *regs;
int irq_no ;
enum i2c_mode_e i2c_mode ;
int isr_nack ;
int isr_byte_end ;
int isr_timeout ;
int isr_int_pending ;
struct compat_semaphore tx_sem;
struct compat_semaphore rx_sem;
};
static struct i2c_regs_s regs_backup;
extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen);
static unsigned int is_master = 1;/*master:1, slave:0*/
static DEFINE_SPINLOCK(slave_i2c_lock);
static int slave_i2c_dev_major = SLAVE_I2C_MAJOR;
static int slave_i2c_dev_minor;
static int slave_i2c_dev_nr = 1;
static struct slave_i2c_dev_s slave_i2c_dev;
static struct wmt_slave_i2c_s slave_i2c_port;
static unsigned char slave_i2c_addr = 0x31;
#ifdef CONFIG_PROC_FS
static struct proc_dir_entry *slave_i2c_proc;
static int slave_i2c_reg_read(char *buf, char **start, off_t offset, int len)
{
char *p = buf;
p += sprintf(p, "reg : value\n");
p += sprintf(p, "cr : 0x%.4x\n", slave_i2c_port.regs->cr_reg);
p += sprintf(p, "tcr : 0x%.4x\n", slave_i2c_port.regs->tcr_reg);
p += sprintf(p, "csr : 0x%.4x\n", slave_i2c_port.regs->csr_reg);
p += sprintf(p, "isr : 0x%.4x\n", slave_i2c_port.regs->isr_reg);
p += sprintf(p, "imr : 0x%.4x\n", slave_i2c_port.regs->imr_reg);
p += sprintf(p, "cdr : 0x%.4x\n", slave_i2c_port.regs->cdr_reg);
p += sprintf(p, "tr : 0x%.4x\n", slave_i2c_port.regs->tr_reg);
p += sprintf(p, "scr : 0x%.4x\n", slave_i2c_port.regs->scr_reg);
p += sprintf(p, "cssr : 0x%.4x\n", slave_i2c_port.regs->cssr_reg);
p += sprintf(p, "simr : 0x%.4x\n", slave_i2c_port.regs->simr_reg);
p += sprintf(p, "sisr : 0x%.4x\n", slave_i2c_port.regs->sisr_reg);
p += sprintf(p, "csdr : 0x%.4x\n", slave_i2c_port.regs->csdr_reg);
p += sprintf(p, "str : 0x%.4x\n", slave_i2c_port.regs->str_reg);
return p - buf;
}
static int slave_i2c_addr_read(char *buf, char **start, off_t offset, int len)
{
char *p = buf;
p += sprintf(p, "i2c-slave address : 0x%.2x\n", slave_i2c_addr);
return p - buf;
}
#endif
static irqreturn_t slave_i2c_isr(
int irq, /*!<; // IRQ number */
void *dev, /*!<; // Private paramater(i.e. pointer of spi port) */
struct pt_regs *regs /*!<; // interrupt register */
)
{
unsigned short isr_status = slave_i2c_port.regs->sisr_reg;
unsigned short ssr_status = slave_i2c_port.regs->cssr_reg;
DPRINTK("slave_i2c isr sisr = %x\n", isr_status);
DPRINTK("slave_i2c isr cssr = %x\n", ssr_status);
if (isr_status & I2C_SISR_DAT_REQ) {
slave_i2c_port.regs->sisr_reg |= I2C_SISR_DAT_REQ_WRITE_CLEAR;
slave_i2c_port.isr_nack = ssr_status & I2C_SRCV_NACK_MASK;
compat_up(&slave_i2c_port.tx_sem);
} else if (isr_status & I2C_SISR_BYTE_END) {
slave_i2c_port.regs->sisr_reg |= I2C_SISR_BYTE_END_WRITE_CLEAR;
slave_i2c_port.isr_byte_end = 1;
slave_i2c_port.isr_nack = ssr_status & I2C_SRCV_NACK_MASK;
compat_up(&slave_i2c_port.rx_sem);
} else if (isr_status & I2C_SISR_SCL_TIME_OUT) {
slave_i2c_port.regs->sisr_reg |= I2C_SISR_SCL_TIME_OUT_WRITE_CLEAR;
slave_i2c_port.isr_timeout = 1 ;
}
return IRQ_HANDLED;
}
static int wmt_slave_i2c_read_data(unsigned short addr , unsigned char *buf, int size, int flags)
{
int ret = 0;
int xfer_len = 0;
int sleep_count = 1;
DPRINTK("ssr = %x, isr = %x\n", slave_i2c_port.regs->cssr_reg, slave_i2c_port.regs->sisr_reg);
while (xfer_len < size) {
compat_down_interruptible(&slave_i2c_port.rx_sem);
buf[xfer_len] = ((slave_i2c_port.regs->csdr_reg & I2C_SLAVE_READ_DATA_MASK) >> I2C_SLAVE_READ_DATA_SHIFT);
DPRINTK("data = %x\n", buf[xfer_len]);
xfer_len++;
}
ret = xfer_len;
while (slave_i2c_port.regs->cssr_reg & I2C_SLAVE_ACTIVE) {
if (compat_sema_count(&slave_i2c_port.rx_sem) > 0) {/*receive data was longer than request*/
ret = -1;
break;
}
msleep(sleep_count);
if (sleep_count < 16)
sleep_count *= 2;
}
DPRINTK("ssr = %x, isr = %x\n", slave_i2c_port.regs->cssr_reg, slave_i2c_port.regs->sisr_reg);
return ret;
}
static int wmt_slave_i2c_write_data(unsigned short addr, unsigned char *buf, int size, int flags)
{
int ret = 0;
int xfer_len = 0;
int sleep_count = 1;
DPRINTK("tx ssr = %x, isr = %x\n", slave_i2c_port.regs->cssr_reg, slave_i2c_port.regs->sisr_reg);
while (xfer_len < size) {
compat_down_interruptible(&slave_i2c_port.tx_sem);
slave_i2c_port.regs->csdr_reg = buf[xfer_len];
DPRINTK("data = %x\n", buf[xfer_len]);
++xfer_len;
}
ret = xfer_len;
while (slave_i2c_port.regs->cssr_reg & I2C_SLAVE_ACTIVE) {
msleep(sleep_count);
if (sleep_count < 16)
sleep_count *= 2;
}
DPRINTK("2.tx ssr = %x, isr = %x\n", slave_i2c_port.regs->cssr_reg, slave_i2c_port.regs->sisr_reg);
return ret;
}
static int slave_i2c_do_xfer(unsigned short addr, unsigned char *buf, int size, int flags)
{
int ret;
if (flags & I2C_M_RD)
ret = wmt_slave_i2c_read_data(addr , buf, size, flags);
else
ret = wmt_slave_i2c_write_data(addr , buf, size, flags);
return ret;
}
int wmt_i2cslave_transfer0(struct i2c_slave_msg slave_msg)
{
int flags = slave_msg.flags;
unsigned char *buf = slave_msg.buf;
unsigned short addr = slave_msg.addr;
int size = slave_msg.len;
int xfer_len;
if (is_master == 1)
return 0;
spin_lock(&slave_i2c_lock);
xfer_len = slave_i2c_do_xfer(addr, buf, size, flags);
spin_unlock(&slave_i2c_lock);
return xfer_len;
}
EXPORT_SYMBOL(wmt_i2cslave_transfer0);
void wmt_i2cslave_setaddr0(struct i2c_slave_msg msg)
{
if (is_master == 1)
return;
spin_lock(&slave_i2c_lock);
slave_i2c_addr = msg.addr;
if (msg.addr & DISABLE_I2C_SLAVE)
slave_i2c_port.regs->cr_reg &= ~I2C_CR_ENABLE;
else {
slave_i2c_port.regs->cr_reg = 0;
slave_i2c_port.regs->scr_reg = 0;
slave_i2c_port.regs->cr_reg = (I2C_SLAV_MODE_SEL|I2C_CR_ENABLE);
slave_i2c_port.regs->sisr_reg = I2C_SISR_ALL_WRITE_CLEAR;
if (slave_i2c_port.i2c_mode == I2C_STANDARD_MODE)
slave_i2c_port.regs->scr_reg = slave_i2c_addr;
else if (slave_i2c_port.i2c_mode == I2C_FAST_MODE)
slave_i2c_port.regs->scr_reg = slave_i2c_addr;
else
slave_i2c_port.regs->scr_reg = (slave_i2c_addr|I2C_SLAVE_HS_MODE);
slave_i2c_port.regs->simr_reg = I2C_SIMR_ALL_ENABLE;
}
spin_unlock(&slave_i2c_lock);
}
EXPORT_SYMBOL(wmt_i2cslave_setaddr0);
static ssize_t slave_i2c_read(
struct file *filp,
char __user *buf,
size_t count,
loff_t *f_pos
)
{
int ret = 0;
struct i2c_slave_msg slave_msg;
if (is_master == 1)
return ret;
slave_msg.buf = (char *)kmalloc(count * sizeof(unsigned char), GFP_KERNEL);
slave_msg.flags = 0;
slave_msg.flags |= I2C_M_RD;
slave_msg.len = count;
slave_msg.addr = slave_i2c_addr;
ret = wmt_i2cslave_transfer0(slave_msg);
if (copy_to_user(buf, slave_msg.buf, count)) {
kfree(slave_msg.buf);
return -EFAULT;
}
return ret;
}
static ssize_t slave_i2c_write(
struct file *filp,
const char __user *buf,
size_t count,
loff_t *f_pos
)
{
int ret = 0;
struct i2c_slave_msg slave_msg;
if (is_master == 1)
return ret;
slave_msg.buf = (char *)kmalloc(count * sizeof(unsigned char), GFP_KERNEL);
slave_msg.flags = 0;
slave_msg.flags &= ~I2C_M_RD;
slave_msg.len = count;
slave_msg.addr = slave_i2c_addr;
if (copy_from_user(slave_msg.buf, buf, count)) {
kfree(slave_msg.buf);
return -EFAULT;
}
ret = wmt_i2cslave_transfer0(slave_msg);
return ret; /* return Write out data size*/
}
static int slave_i2c_open(
struct inode *inode,
struct file *filp
)
{
struct slave_i2c_dev_s *dev;
char name[40];
int minor_no;
dev = container_of(inode->i_cdev, struct slave_i2c_dev_s, cdev);
filp->private_data = dev;
minor_no = iminor(inode); /* get */
/* Create user name*/
memset(name, 0x0, 8);
sprintf(name, "slave-i2c%d", minor_no);
return 0;
}
static int slave_i2c_release(
struct inode *inode,
struct file *filp
)
{
struct slave_i2c_dev_s *dev;
int minor_no;
dev = container_of(inode->i_cdev, struct slave_i2c_dev_s, cdev);
minor_no = iminor(inode);
return 0;
}
static int slave_i2c_ioctl(
struct inode *inode,
struct file *filp,
unsigned int cmd,
unsigned long arg
)
{
int ret = 0;
struct i2c_slave_msg slave_msg[1];
unsigned char *data_ptr;
unsigned char __user *usr_ptr;
switch (cmd) {
case IOCTL_DO_TRANSFER:
if (copy_from_user(slave_msg, (struct i2c_slave_msg *)arg,
sizeof(struct i2c_slave_msg)))
return -EFAULT;
data_ptr = (unsigned char *)kmalloc(slave_msg->len*sizeof(unsigned char), GFP_KERNEL);
usr_ptr = (unsigned char __user *)slave_msg->buf;
if (copy_from_user(data_ptr, (unsigned char *)slave_msg->buf,
slave_msg->len*sizeof(unsigned char))) {
kfree(data_ptr);
return -EFAULT;
}
slave_msg->buf = data_ptr;
ret = wmt_i2cslave_transfer0((struct i2c_slave_msg)*slave_msg);
if (slave_msg->flags & I2C_M_RD) {
if (copy_to_user(
usr_ptr,
data_ptr,
slave_msg->len))
ret = -EFAULT;
}
kfree(data_ptr);
break;
case IOCTL_SET_ADDR:
if (copy_from_user(slave_msg, (struct i2c_slave_msg *)arg,
sizeof(struct i2c_slave_msg)))
return -EFAULT;
wmt_i2cslave_setaddr0((struct i2c_slave_msg) *slave_msg);
break;
case IOCTL_QUERY_DATA:
break;
case IOCTL_SET_SPEED_MODE:
break;
default:
break;
}
return ret;
}
/*!*************************************************************************
driver file operations struct define
****************************************************************************/
static struct file_operations i2c_slave_fops = {
.owner = THIS_MODULE,
.open = slave_i2c_open,
.read = slave_i2c_read,
.write = slave_i2c_write,
.ioctl = slave_i2c_ioctl,
.release = slave_i2c_release,
};
static int slave_i2c_probe(
struct device *dev
)
{
int ret = 0;
dev_t dev_no;
struct cdev *cdev;
char name[40];
char buf[80];
unsigned int port_num;
int idx = 0;
char varname1[] = "wmt.bus.i2c.slave_port";
int ret_val = 0;
int varlen = 80;
memset(name, 0, 40);
dev_no = MKDEV(slave_i2c_dev_major, slave_i2c_dev_minor);
sprintf(name, "wmt_i2cslave%d",slave_i2c_dev_minor);
cdev = &slave_i2c_dev.cdev;
cdev_init(cdev, &i2c_slave_fops);
ret = cdev_add(cdev, dev_no, 8);
slave_i2c_dev.class_slave_i2c = class_create(THIS_MODULE, "wmt_i2cslave");
device_create(slave_i2c_dev.class_slave_i2c, NULL ,
MKDEV(slave_i2c_dev_major,slave_i2c_dev_minor),
NULL, name);
if (ret) {
printk(KERN_ALERT "*E* register char dev \n");
return ret;
}
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *res;
slave_i2c_proc = proc_mkdir("driver/wmt_i2cslave0", NULL);
/*
slave_i2c_proc->owner = THIS_MODULE;
*/
res = create_proc_entry("registers", 0, slave_i2c_proc);
if (res) {
res->read_proc = slave_i2c_reg_read;
}
res = create_proc_entry("address", 0, slave_i2c_proc);
if (res) {
res->read_proc = slave_i2c_addr_read;
}
#endif
slave_i2c_port.regs = (struct i2c_regs_s *)I2C0_BASE_ADDR;
slave_i2c_port.irq_no = IRQ_I2C0;
slave_i2c_port.i2c_mode = I2C_STANDARD_MODE;
slave_i2c_port.isr_nack = 0;
slave_i2c_port.isr_byte_end = 0;
slave_i2c_port.isr_timeout = 0;
slave_i2c_port.isr_int_pending = 0;
compat_sema_init(&slave_i2c_port.tx_sem, 0);
compat_sema_init(&slave_i2c_port.rx_sem, 0);
ret_val = wmt_getsyspara(varname1, buf, &varlen);
is_master = 1;
if (ret_val == 0) {
ret_val = sscanf(buf, "%x", &port_num);
while (ret_val) {
if (port_num != 0)
is_master = 1;
else {
is_master = 0;
break;
}
idx += ret_val;
ret_val = sscanf(buf + idx, ",%x", &port_num);
}
} else
is_master = 1;
/**/
/* hardware initial*/
/**/
if (is_master == 0) {
*(volatile unsigned int *)PMC_ClOCK_ENABLE_LOWER |= (BIT5);
*(volatile unsigned int *)CTRL_GPIO21 &= ~(BIT0 | BIT1);
*(volatile unsigned int *)PU_EN_GPIO21 |= (BIT0 | BIT1);
*(volatile unsigned int *)PU_CTRL_GPIO21 |= (BIT0 | BIT1);
/*set i2c slave register*/
slave_i2c_port.regs->cr_reg = 0;
slave_i2c_port.regs->scr_reg = 0;
slave_i2c_port.regs->cr_reg = (I2C_SLAV_MODE_SEL|I2C_CR_ENABLE);
slave_i2c_port.regs->sisr_reg = I2C_SISR_ALL_WRITE_CLEAR;
if (slave_i2c_port.i2c_mode == I2C_STANDARD_MODE)
slave_i2c_port.regs->scr_reg = slave_i2c_addr;
else if (slave_i2c_port.i2c_mode == I2C_FAST_MODE)
slave_i2c_port.regs->scr_reg = slave_i2c_addr;
else
slave_i2c_port.regs->scr_reg = (slave_i2c_addr|I2C_SLAVE_HS_MODE);
slave_i2c_port.regs->simr_reg = I2C_SIMR_ALL_ENABLE;
slave_i2c_port.regs->cr_reg &= ~I2C_CR_ENABLE;
if (request_irq(slave_i2c_port.irq_no , &slave_i2c_isr, IRQF_DISABLED, "i2c-slave", 0) < 0) {
DPRINTK(KERN_INFO "I2C-SLAVE: Failed to register I2C-SLAVE irq %i\n", slave_i2c_port.irq_no);
return -ENODEV;
}
}
return ret;
}
static int slave_i2c_remove(
struct device *dev /*!<; // please add parameters description her*/
)
{
struct cdev *cdev;
cdev = &slave_i2c_dev.cdev;
cdev_del(cdev);
return 0;
} /* End of spi_remove() */
static void slave_i2c_backup(void)
{
regs_backup.cr_reg = slave_i2c_port.regs->cr_reg;
regs_backup.tcr_reg = slave_i2c_port.regs->tcr_reg;
regs_backup.scr_reg = slave_i2c_port.regs->scr_reg;
regs_backup.simr_reg = slave_i2c_port.regs->simr_reg;
regs_backup.str_reg = slave_i2c_port.regs->str_reg;
}
static void slave_i2c_restore(void)
{
slave_i2c_port.regs->cr_reg = 0;
slave_i2c_port.regs->cr_reg = regs_backup.cr_reg;
slave_i2c_port.regs->tcr_reg = regs_backup.tcr_reg;
slave_i2c_port.regs->scr_reg = regs_backup.scr_reg;
slave_i2c_port.regs->simr_reg = regs_backup.simr_reg;
slave_i2c_port.regs->str_reg = regs_backup.str_reg;
}
static int slave_i2c_suspend(
struct device *dev, /*!<; // please add parameters description her*/
pm_message_t state /*!<; // please add parameters description her*/
)
{
if (is_master == 1)
return 0;
slave_i2c_backup();
compat_sema_init(&slave_i2c_port.tx_sem, 0);
compat_sema_init(&slave_i2c_port.rx_sem, 0);
return 0;
}
static int slave_i2c_resume(
struct device *dev
)
{
if (is_master == 1)
return 0;
*(volatile unsigned int *)PMC_ClOCK_ENABLE_LOWER |= (BIT5);
*(volatile unsigned int *)CTRL_GPIO21 &= ~(BIT0 | BIT1);
*(volatile unsigned int *)PU_EN_GPIO21 |= (BIT0 | BIT1);
*(volatile unsigned int *)PU_CTRL_GPIO21 |= (BIT0 | BIT1);
slave_i2c_restore();
return 0;
}
/*!*************************************************************************
device driver struct define
****************************************************************************/
static struct device_driver slave_i2c_driver = {
.name = "wmt_i2c_slave_0", /* This name should equal to platform device name.*/
.bus = &platform_bus_type,
.probe = slave_i2c_probe,
.remove = slave_i2c_remove,
.suspend = slave_i2c_suspend,
.resume = slave_i2c_resume
};
static void slave_i2c_platform_release(
struct device *device
)
{
}
/*!*************************************************************************
platform device struct define
****************************************************************************/
static struct platform_device slave_i2c_device = {
.name = "wmt_i2c_slave_0",
.id = 0,
.dev = { .release = slave_i2c_platform_release,
},
.num_resources = 0,
.resource = NULL,
};
static int slave_i2c_init(void)
{
int ret;
dev_t dev_no;
char dev_name[40];
memset(dev_name, 0, 40);
sprintf(dev_name, "wmt_i2c_slave%d", slave_i2c_device.id);
if (slave_i2c_dev_major) {
dev_no = MKDEV(slave_i2c_dev_major, slave_i2c_dev_minor);
ret = register_chrdev_region(dev_no, slave_i2c_dev_nr, dev_name);
} else {
ret = alloc_chrdev_region(&dev_no, slave_i2c_dev_minor, slave_i2c_dev_nr, dev_name);
slave_i2c_dev_major = MAJOR(dev_no);
}
if (ret < 0) {
printk(KERN_ALERT "*E* can't get major %d\n", slave_i2c_dev_major);
return ret;
}
platform_device_register(&slave_i2c_device);
ret = driver_register(&slave_i2c_driver);
return ret;
}
module_init(slave_i2c_init);
static void slave_i2c_exit(void)
{
dev_t dev_no;
driver_unregister(&slave_i2c_driver);
platform_device_unregister(&slave_i2c_device);
dev_no = MKDEV(slave_i2c_dev_major, slave_i2c_dev_minor);
unregister_chrdev_region(dev_no, slave_i2c_dev_nr);
return;
}
module_exit(slave_i2c_exit);
MODULE_AUTHOR("WMT SW Team");
MODULE_DESCRIPTION("WMT_slave_i2c device driver");
MODULE_LICENSE("GPL");
#undef WMT_I2C_SLAVE_C