2014年9月1日 星期一

裝置實例-I2C (3)

    接下來我們就來看看i2c-dev.c吧,它已經是一個完整的字元裝置,在整個I2C架構中就是扮演abstraction layer的角色,其實它已經可以直接拿來使用,不太需要再修改內容。可是了解他在做些什麼事情還是很重要,例如呼叫了哪些i2c-core提供的API,又提供了哪些file operations的方法給user使用...等等,了解它後在之後應用程式的開發上會比較清楚。不過這邊不會詳細說明裡面的內容,會比較針對在file operations中跟user space的操作上,其他部分就只簡單的說明。

static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;
int res;

if (dev->type != &i2c_adapter_type)
return 0;
adap = to_i2c_adapter(dev);

i2c_dev = get_free_i2c_dev(adap);
if (IS_ERR(i2c_dev))
return PTR_ERR(i2c_dev);

/* register this i2c device with the driver core */
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
    MKDEV(I2C_MAJOR, adap->nr), NULL,
    "i2c-%d", adap->nr);
if (IS_ERR(i2c_dev->dev)) {
res = PTR_ERR(i2c_dev->dev);
goto error;
}
res = device_create_file(i2c_dev->dev, &dev_attr_name);
if (res)
goto error_destroy;

pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
adap->name, adap->nr);
return 0;
error_destroy:
device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));
error:
return_i2c_dev(i2c_dev);
return res;
}

static int i2cdev_detach_adapter(struct device *dev, void *dummy)
{
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;

if (dev->type != &i2c_adapter_type)
return 0;
adap = to_i2c_adapter(dev);

i2c_dev = i2c_dev_get_by_minor(adap->nr);
if (!i2c_dev) /* attach_adapter must have failed */
return 0;

device_remove_file(i2c_dev->dev, &dev_attr_name);
return_i2c_dev(i2c_dev);
device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));

pr_debug("i2c-dev: adapter [%s] unregistered\n", adap->name);
return 0;
}

static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action, void *data)
{
struct device *dev = data;

switch (action) {
case BUS_NOTIFY_ADD_DEVICE:
return i2cdev_attach_adapter(dev, NULL);
case BUS_NOTIFY_DEL_DEVICE:
return i2cdev_detach_adapter(dev, NULL);
}

return 0;
}

static struct notifier_block i2cdev_notifier = {
.notifier_call = i2cdev_notifier_call,
};

static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};

static int __init i2c_dev_init(void)
{
int res;

printk(KERN_INFO "i2c /dev entries driver\n");

res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
if (res)
goto out;

i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
if (IS_ERR(i2c_dev_class)) {
res = PTR_ERR(i2c_dev_class);
goto out_unreg_chrdev;
}

/* Keep track of adapters which will be added or removed later */
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
if (res)
goto out_unreg_class;

/* Bind to already existing adapters right away */
i2c_for_each_dev(NULL, i2cdev_attach_adapter);

return 0;

out_unreg_class:
class_destroy(i2c_dev_class);
out_unreg_chrdev:
unregister_chrdev(I2C_MAJOR, "i2c");
out:
printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
return res;
}

static void __exit i2c_dev_exit(void)
{
bus_unregister_notifier(&i2c_bus_type, &i2cdev_notifier);
i2c_for_each_dev(NULL, i2cdev_detach_adapter);
class_destroy(i2c_dev_class);
unregister_chrdev(I2C_MAJOR, "i2c");
}

    就先從載入和卸載函式開始吧,字元裝置的註冊一定是不可少的,這裡也用到了kernel的通知機制,主要工作就是當有i2c_adapter被註冊或卸載時,在/dev資料夾下建立或移除相對應的裝置檔案,和我們之前在test_chrdevˋ中提到的一樣。i2c_for_each_dev()函式其實就是調用bus_for_each_dev()搜尋i2c_bus上已註冊的i2c_adapter,並在/dev資料夾下建立裝置檔案。

static int i2cdev_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct i2c_client *client;
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;

i2c_dev = i2c_dev_get_by_minor(minor);
if (!i2c_dev)
return -ENODEV;

adap = i2c_get_adapter(i2c_dev->adap->nr);
if (!adap)
return -ENODEV;

/* This creates an anonymous i2c_client, which may later be
* pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
*
* This client is ** NEVER REGISTERED ** with the driver model
* or I2C core code!!  It just holds private copies of addressing
* information and maybe a PEC flag.
*/
client = kzalloc(sizeof(*client), GFP_KERNEL);
if (!client) {
i2c_put_adapter(adap);
return -ENOMEM;
}
snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);

client->adapter = adap;
file->private_data = client;

return 0;
}

static int i2cdev_release(struct inode *inode, struct file *file)
{
struct i2c_client *client = file->private_data;

i2c_put_adapter(client->adapter);
kfree(client);
file->private_data = NULL;

return 0;
}

    因為在i2cdev_attach_adapter()中裝置檔案建立的次裝置號採用i2c_adapter中nr欄位,所以在open()方法也可以透過次裝置號找到相對應的i2c_adapter,接下來就看到了之前提過的一個資料結構i2c_client,在這邊它初始化adapter的欄位並加到file的私有資料中,讓file operations其他方法都可以使用。

static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
char *tmp;
int ret;

struct i2c_client *client = file->private_data;

if (count > 8192)
count = 8192;

tmp = kmalloc(count, GFP_KERNEL);
if (tmp == NULL)
return -ENOMEM;

pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n",
iminor(file_inode(file)), count);

ret = i2c_master_recv(client, tmp, count);
if (ret >= 0)
ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret;
kfree(tmp);
return ret;
}

static ssize_t i2cdev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
int ret;
char *tmp;
struct i2c_client *client = file->private_data;

if (count > 8192)
count = 8192;

tmp = memdup_user(buf, count);
if (IS_ERR(tmp))
return PTR_ERR(tmp);

pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n",
iminor(file_inode(file)), count);

ret = i2c_master_send(client, tmp, count);
kfree(tmp);
return ret;
}

    read()和write()方法的行為模式和我們在上一篇引用I2C規格書的Fig 11和Fig 12是匹配的。這邊有一個memdup_user()函式,分配記憶體並從user space複製資料都在裡面做完了,可以讓程式碼更簡潔,並且減少需要自行錯誤處理工作。最後分別調用i2c-core.c提供的i2c_master_recv()i2c_master_send()函式把資料網下層送。

int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
{
struct i2c_adapter *adap = client->adapter;
struct i2c_msg msg;
int ret;

msg.addr = client->addr;
msg.flags = client->flags & I2C_M_TEN;
msg.flags |= I2C_M_RD;
msg.len = count;
msg.buf = buf;

ret = i2c_transfer(adap, &msg, 1);

/*
* If everything went ok (i.e. 1 msg received), return #bytes received,
* else error code.
*/
return (ret == 1) ? count : ret;
}

int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
{
int ret;
struct i2c_adapter *adap = client->adapter;
struct i2c_msg msg;

msg.addr = client->addr;
msg.flags = client->flags & I2C_M_TEN;
msg.len = count;
msg.buf = (char *)buf;

ret = i2c_transfer(adap, &msg, 1);

/*
* If everything went ok (i.e. 1 msg transmitted), return #bytes
* transmitted, else error code.
*/
return (ret == 1) ? count : ret;
}

int  i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
unsigned long orig_jiffies;
int ret, try;

/* Retry automatically on arbitration loss */
orig_jiffies = jiffies;
for (ret = 0, try = 0; try <= adap->retries; try++) {
ret = adap->algo->master_xfer(adap, msgs, num);
if (ret != -EAGAIN)
break;
if (time_after(jiffies, orig_jiffies + adap->timeout))
break;
}

return ret;
}

可以看到他們的主要工作就是建構好一個i2c_msg結構,最後調用i2c_transfer()。i2c_transfer()則呼叫在XXX_i2c.c中曾經提到過的master_xfer()函式,這裡還採用了重試和time out的機制。還記得上一篇曾經說過這個read的行為模式在很多週邊都是不被支援的,所以read()方法幾乎不戶被使用,因此相對應的write()方法也很少被使用,所以在"裝置實例-I2C (1)"中的架構圖中沒有列出i2c_master_recv()i2c_master_send(),反而使用較多的是ioctl()方法。

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct i2c_client *client = file->private_data;
unsigned long funcs;

dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
cmd, arg);

switch (cmd) {
case I2C_SLAVE:
case I2C_SLAVE_FORCE:
/* NOTE:  devices set up to work with "new style" drivers
* can't use I2C_SLAVE, even when the device node is not
* bound to a driver.  Only I2C_SLAVE_FORCE will work.
*
* Setting the PEC flag here won't affect kernel drivers,
* which will be using the i2c_client node registered with
* the driver model core.  Likewise, when that client has
* the PEC flag already set, the i2c-dev driver won't see
* (or use) this setting.
*/
if ((arg > 0x3ff) ||
   (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
return -EINVAL;
if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
return -EBUSY;
/* REVISIT: address could become busy later */
client->addr = arg;
return 0;
case I2C_TENBIT:
if (arg)
client->flags |= I2C_M_TEN;
else
client->flags &= ~I2C_M_TEN;
return 0;
case I2C_PEC:
if (arg)
client->flags |= I2C_CLIENT_PEC;
else
client->flags &= ~I2C_CLIENT_PEC;
return 0;
case I2C_FUNCS:
funcs = i2c_get_functionality(client->adapter);
return put_user(funcs, (unsigned long __user *)arg);

case I2C_RDWR:
return i2cdev_ioctl_rdrw(client, arg);

case I2C_SMBUS:
return i2cdev_ioctl_smbus(client, arg);

case I2C_RETRIES:
client->adapter->retries = arg;
break;
case I2C_TIMEOUT:
/* For historical reasons, user-space sets the timeout
* value in units of 10 ms.
*/
client->adapter->timeout = msecs_to_jiffies(arg * 10);
break;
default:
/* NOTE:  returning a fault code here could cause trouble
* in buggy userspace code.  Some old kernel bugs returned
* zero in this case, and userspace code might accidentally
* have depended on that bug.
*/
return -ENOTTY;
}
return 0;
}

struct i2c_rdwr_ioctl_data {
struct i2c_msg __user *msgs; /* pointers to i2c_msgs */
__u32 nmsgs; /* number of i2c_msgs */
};

static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client, 
unsigned long arg)
{
struct i2c_rdwr_ioctl_data rdwr_arg;
struct i2c_msg *rdwr_pa;
u8 __user **data_ptrs;
int i, res;

if (copy_from_user(&rdwr_arg,
  (struct i2c_rdwr_ioctl_data __user *)arg,
  sizeof(rdwr_arg)))
return -EFAULT;

/* Put an arbitrary limit on the number of messages that can
* be sent at once */
if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)
return -EINVAL;

rdwr_pa = memdup_user(rdwr_arg.msgs,
     rdwr_arg.nmsgs * sizeof(struct i2c_msg));
if (IS_ERR(rdwr_pa))
return PTR_ERR(rdwr_pa);

data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL);
if (data_ptrs == NULL) {
kfree(rdwr_pa);
return -ENOMEM;
}

res = 0;
for (i = 0; i < rdwr_arg.nmsgs; i++) {
/* Limit the size of the message to a sane amount */
if (rdwr_pa[i].len > 8192) {
res = -EINVAL;
break;
}

data_ptrs[i] = (u8 __user *)rdwr_pa[i].buf;
rdwr_pa[i].buf = memdup_user(data_ptrs[i], rdwr_pa[i].len);
if (IS_ERR(rdwr_pa[i].buf)) {
res = PTR_ERR(rdwr_pa[i].buf);
break;
}

/*
* If the message length is received from the slave (similar
* to SMBus block read), we must ensure that the buffer will
* be large enough to cope with a message length of
* I2C_SMBUS_BLOCK_MAX as this is the maximum underlying bus
* drivers allow. The first byte in the buffer must be
* pre-filled with the number of extra bytes, which must be
* at least one to hold the message length, but can be
* greater (for example to account for a checksum byte at
* the end of the message.)
*/
if (rdwr_pa[i].flags & I2C_M_RECV_LEN) {
if (!(rdwr_pa[i].flags & I2C_M_RD) ||
   rdwr_pa[i].buf[0] < 1 ||
   rdwr_pa[i].len < rdwr_pa[i].buf[0] +
    I2C_SMBUS_BLOCK_MAX) {
res = -EINVAL;
break;
}

rdwr_pa[i].len = rdwr_pa[i].buf[0];
}
}
if (res < 0) {
int j;
for (j = 0; j < i; ++j)
kfree(rdwr_pa[j].buf);
kfree(data_ptrs);
kfree(rdwr_pa);
return res;
}

res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs);
while (i-- > 0) {
if (res >= 0 && (rdwr_pa[i].flags & I2C_M_RD)) {
if (copy_to_user(data_ptrs[i], rdwr_pa[i].buf,
rdwr_pa[i].len))
res = -EFAULT;
}
kfree(rdwr_pa[i].buf);
}
kfree(data_ptrs);
kfree(rdwr_pa);
return res;
}

    ioctl()方法裡定義了很多目前不會用到的命令,目前比較需要關心的是I2C_RDWR這個命令和i2cdev_ioctl_rdwr()這個函式。user space的process呼叫ioctl這個system call的時候還需要把i2c_rdwr_ioctl_data這個資料結構當成一個參數傳過來,這個結構裡面有i2c_msg指標的欄位和msg的數量,也就是說在user space裡面就要先把i2c_msg依照周邊的要求建構好。在i2cdev_ioctl_rdwr()函式裡就是分別把i2c_rdwr_ioctl_data結構、i2c_msg結構和資料(i2c_msg中的buf欄位)都複製一份到kernel裡,接下來就是調用i2c_transfer()把資料往下層送,最後如果是讀的msg,就把讀到的資料複製到user space去。所以在應用程式中要完成的工作流程大概是open() -> 依需求建構i2c_msg陣列 -> 建構i2c_rdwr_ioctl_data結構 -> ioctl() -> close()

    到這邊為止,整個I2C的架構算是介紹完了,我認為這個架構算是一個通用的架構,如果是比較舊的kernel的話,對於EEPROM類型的裝置還有另一種架構,不過新的kernel好像取消了。除了一些增加的資料結構,多了i2c-core.c把一些API包裝起來外,有沒有發現跟我們自己創立的架構很類似呢? 

2014年8月28日 星期四

裝置實例-I2C (2)

    接下來就說明如何實現I2C控制器的驅動,當然也要讓這個裝置符合Linux的裝置架構,所以在說明它之前,要先知道它應該掛在哪個bus下。在嵌入式的系統中,這一類的裝置都是獨立屬於SoC記憶體空間的控制器,不依附像是PCI、USB...之類的匯流排,基於這個背景,Linux於是定義了一個虛擬的匯流排,platform_bus,相對於這個匯流排的裝置為platform_device,而驅動程式為platform_driver。因此在XXX_i2c.c中首要工作就是分別建立platform_device和platform_driver,並且註冊它們。

struct resource XXX_i2c_resource[]= { [0]={ .start = CMIC_I2C_CONFIG_BASE, .end = CMIC_I2C_CONFIG_END, .flags = IORESOURCE_MEM, }, [1]={ .start = CMIC_I2C_COMMAND_BASE, .end = CMIC_I2C_COMMAND_END, .flags = IORESOURCE_MEM, }, }

static struct platform_driver XXX_i2c_drv = { .driver = { .owner = THIS_MODULE, .name = "XXX-i2c", }, .probe = XXX_i2c_probe, .remove = XXX_i2c_remove, }

struct platform_device *XXX_i2c_dev; static int __init XXX_i2c_init(void) { int err = 0; XXX_i2c_dev = platform_device_alloc("XXX-i2c", -1); if (XXX_i2c_dev == NULL) { err = -ENOMEM; goto exit; } err = platform_device_add_resources(XXX_i2c_dev,XXX_i2c_resource, ARRAY_SIZE(XXX_i2c_resource)); if (err) goto exit_put; err = platform_device_add(XXX_i2c_dev); if(err) goto exit_put; err = platform_driver_register(&XXX_i2c_drv); if(err) goto exit_unregister; return 0; exit_unregister: platform_device_unregister(XXX_i2c_dev); exit_put: platform_device_put(XXX_i2c_dev); exit: return err; } static void __exit XXX_i2c_exit(void) { platform_driver_unregister(&XXX_i2c_drv); platform_device_unregister(XXX_i2c_dev); platform_device_put(XXX_i2c_dev); }

看完上面的程式碼,只有宣告platform_driver卻沒有發現platform_device,沒錯! 因為這一個驅動是編寫為一個module的形式,所以在載入函式中,platform_device採用動態分配的方式產生,platform_device裡面有一個resource的欄位,主要是用來描述這個控制器會用到的暫存器位址、io-port位址和中斷編號,建立好platform_device後便調用platform_device_add()來註冊這個platform_device。

    一般來說,platfrom_device都是在BSP套件中實現的,以一個arm架構的平台為例,它會被放在arch/arm/mach-XXX/XXX-mach.c中,以靜態的方式宣告如下,XXX可能是平台的名稱。

struce platform_device XXX_i2c_dev = { .name = "XXX-i2c", .id = -1, .num_resources = ARRAY_SIZE(XXX_i2c_resource), .resource = XXX_i2c_resource, };

一個SoC上通常會有很多個platform_device,所以它們會被歸納成一個陣列的形式,之後再調用platform_add_devices()函式統一註冊。

    接著看到platform_driver,完全就跟我們之前自己定義的test_driver一模一樣,一樣都有probe()和remove()方法,不過probe()方法在這裡除了分配記憶體外,還要請求資源並且把實體位址映射到虛擬的位址,之後才能用映射後的惠只存取各個暫存器,另外就是實現i2c_adapter這個資料結構了。卸載函式除了把裝置和驅動卸載外,不要忘了也要釋放資源!

    裝置和驅動都註冊之後不是一定就可以正常工作了喔,不要忘了bus_type的match()方法,match成功後才會調用驅動的probe()方法,那platform_bus會如何判斷驅動支援的裝置呢?讓我們來看看

static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev;

pdev = container_of(dev, struct platform_device, dev);
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}

是的,跟test_bus_match()做法是一樣的,只比較platform_device和platform_driver兩者的名稱。

struct XXX_i2c_adap { struct i2c_adapter adap; struct i2c_msg *msg; unsigned int msg_num; unsigned int msg_idx; unsigned int buf_len; struct resource *ioarea_cfg; struct resource *ioarea_cmd; void __iomem *regs_cfg; void __iomem *regs_cmd; unsigned int delay_time; };

static struct i2c_algorithm XXX_i2c_algo = { .master_xfer = XXX_i2c_xfer, .functionality = XXX_i2c_func, };

static ssize_t i2c_speed_get(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_adapter *adap = container_of(dev, struct i2c_adapter, dev); struct XXX_i2c_adap *i2c = container_of(adap, struct XXX_i2c_adap, adap); return sprintf(buf,"%s speed: 400KHz\n", i2c->adap.name); } static ssize_t i2c_speed_set(struct device *dev, struct device_attribute *attr, char *buf, size_t count) { struct i2c_adapter *adap = container_of(dev, struct i2c_adapter, dev); struct XXX_i2c_adap *i2c = container_of(adap, struct XXX_i2c_adap, adap); unsigned long tmp; strict_strtoul(buf, 0, &tmp); if (tmp == 400) __raw_writel(HIGH_SPEED, i2c->regs_cfg+XXX_I2C_TIMING); else if (tmp == 100) __raw_writel(NORMAL_SPEED, i2c->regs_cfg+XXX_I2C_TIMING); else { printk("invalid value\n"); return -EINVAL; } return count; } static DEVICE_ATTR(speed, S_IRUGO|S_IWUSR, i2c_speed_get, i2c_speed_set);

static void __inline i2c_adapter_setup(struct XXX_i2c_adap *i2c) { sprintf(i2c->adap.name, "XXX-i2c"); i2c->adap.owner = THIS_MODULE; i2c->adap.algo = &XXX_i2c_algo; i2c->adap.retries = 3; i2c->adap.class = I2C_CLASS_HWMON; i2c->adap.algo_data = i2c; i2c->delay_time = 50; } static int XXX_i2c_probe(struct platform_device *pdev) { int ret=0; unsigned long i2c_cmd = 0x00000000; struct XXX_i2c_adap *i2c; struct resource *res; i2c = kzalloc(sizeof(struct XXX_i2c_adap), GFP_KERNEL); if(i2c == NULL) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { ret = -ENOENT; goto free_i2c; } i2c->ioarea_cfg = request_mem_region(res->start, resource_size(res), pdev->name); if (i2c->ioarea_cfg == NULL) { ret = -ENXIO; goto free_i2c; } i2c->regs_cfg = ioremap(res->start, resource_size(res)); if (i2c->regs_cfg == NULL) { ret = -ENXIO; goto release_cfg; } res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (res == NULL) { ret = -ENOENT; goto unmap_cfg; } i2c->ioarea_cmd = request_mem_region(res->start, resource_size(res), pdev->name); if(i2c->ioarea_cmd == NULL) { printk(KERN_ERR"request mem failed\n"); ret = -ENXIO; goto unmap_cfg; } i2c->regs_cmd = ioremap(res->start, resource_size(res)); if (i2c->regs_cmd == NULL) { ret = -ENXIO; goto release_cmd; } i2c_adapter_setup(i2c); i2c->adap.dev.parent = &pdev->dev; ret = i2c_add_adapter(&i2c->adap); if(ret < 0) goto unmap_cmd; ret = device_create_file(&i2c->adap.dev, &dev_attr_speed); if (ret) goto out; platform_set_drvdata(pdev, i2c); addr = addr << 1; i2c_cmd |= I2C_START; return 0; out: i2c_del_adapter(&i2c->adap); unmap_cmd: iounmap(i2c->regs_cmd); release_cmd: release_mem_region(i2c->ioarea_cmd->start, (i2c->ioarea_cmd->end - i2c->ioarea_cmd->start));
unmap_cfg: iounmap(i2c->regs_cfg); release_cfg: release_mem_region(i2c->ioarea_cfg->start, (i2c->ioarea_cfg->end - i2c->ioarea_cfg->start)); free_i2c: kfree(i2c); return ret; } static int XXX_i2c_remove(struct platform_device *pdev) { struct XXX_i2c_adap *i2c; i2c = platform_get_drvdata(pdev);
device_remove_file(&i2c->adap.dev, &dev_attr_speed); iounmap(i2c->regs_cmd); release_mem_region(i2c->ioarea_cmd->start, (i2c->ioarea_cmd->end - i2c->ioarea_cmd->start)); iounmap(i2c->regs_cfg); release_mem_region(i2c->ioarea_cfg->start, (i2c->ioarea_cfg->end - i2c->ioarea_cfg->start)); i2c_del_adapter(&i2c->adap); kfree(i2c); return 0; }

    上述程式碼中,我們也自己定義的一個資料結構XXX_i2c_adap,當然包含了i2c_adapter結構。i2c_msg結構則是i2c-dev和XXX_i2c這兩層溝通需要傳遞的訊息,結構如下

struct i2c_msg {
__u16 addr;         /* slave address         */
__u16 flags;
__u16 len; /* msg data length */
__u8 *buf; /* pointer to msg data */
};

註解已經解釋的很清楚了,而flag的部分標示了這個msg是讀或是寫,所以讀和寫會是兩個分別的msg,另外還有其他作用,有需要可參考include/linux/uapi/linux/i2c.h檔案。在XXX_i2c_adap還有兩個記錄暫存器base address的欄位,regs_cfg和regs_cmd,之後可以透過這個base address加上每個暫存器的偏移量就可以存去取相對的暫存器了。

    因為我們在resource中宣告了兩塊的記憶體位址,所以在XXX_i2c_probe()函式裡就要分別請求他們的資源並且映射到虛擬的記憶體空間,regs_cfg和regs_cmd就記錄了這兩塊虛擬記憶體的base address,接下來就是建立i2c_adapter結構了,其中比較重要的部分是XXX_i2c_algo結構,其中XXX_i2c_xfer()主要是一些暫存器讀寫的方式和流程,讓實體的SCL和SDA線路上能產生相對應的訊號,master_xfer()這個callback function會在i2c-core.c的i2c_transfer()被呼叫。XXX_i2c_func()記錄這個驅動具備怎樣的功能。完成i2c_adapter結構的建立後就調用i2c_add_adapter()註冊,如果SoC上有多個I2C控制器,那就要依照順序初始化i2c_adapter中的nr欄位,並且改調用i2c_add_numbered_adapter()來註冊。另外還提供了一個speed的屬性檔讓使用者可以設定或是讀取I2C bus的速度,最後把我們自己定義的結構放到platform_device的私有資料欄位去,其實也是調用dev_set_drvdata()

static int __inline is_lastmsg(struct XXX_i2c_adap *i2c) { return i2c->msg_idx == (i2c->msg_num-1); } static int __inline is_lastdata(struct XXX_i2c_adap *i2c) { return i2c->buf_len == 1; } static void __inline clear_event_status(struct XXX_i2c_adap *i2c) { unsigned int event_tmp; event_tmp = __raw_readl(i2c->regs_cmd+XXX_I2C_EVENT_STAT); __raw_writel(event_tmp, i2c->regs_cmd+XXX_I2C_EVENT_STAT); } static int XXX_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) { int i, ret=0; unsigned long i2c_cmd, i2c_data, i2c_state, i2c_event, tmp; struct i2c_msg *msg = msgs; unsigned short addr; unsigned char *buffer; struct XXX_i2c_adap *i2c = adap->algo_data; clear_event_status(i2c); i2c_cmd = 0x00000000; i2c->msg_num = num; for (i2c->msg_idx=0; i2c->msg_idx < num; i2c->msg_idx++, msg++) { i2c->msg = msg; addr = msg->addr << 1; i2c->buf_len = msg->len; buffer = msg->buf; if (msg->len == 0) { addr |= (msg->flags == I2C_M_RD)?1:0; __raw_writel(addr, i2c->regs_cmd+XXX_I2C_MST_DATA_WR); break; } if (msg->flags & I2C_M_RD) { addr |= 1; i2c_cmd = BLOCK_READ|i2c->buf_len; __raw_writel(addr, i2c->regs_cmd+XXX_I2C_MST_DATA_WR); } else { __raw_writel(addr, i2c->regs_cmd+XXX_I2C_MST_DATA_WR); for (; i2c->buf_len > 0; i2c->buf_len--, buffer++) { if (is_lastmsg(i2c)) { if (is_lastdata(i2c)) { i2c_data = LAST_DATA|*buffer; __raw_writel(i2c_data, i2c->regs_cmd+XXX_I2C_MST_DATA_WR); i2c_cmd = BLOCK_WRITE; } } i2c_data = *buffer; __raw_writel(i2c_data, i2c->regs_cmd+XXX_I2C_MST_DATA_WR); } } } i2c_cmd |= I2C_START; __raw_writel(i2c_cmd, i2c->regs_cmd+XXX_I2C_MST_CMD); i2c_event = __raw_readl(i2c->regs_cmd+XXX_I2C_EVENT_STAT); while (!(i2c_event & START_BUSY_EVENT)) i2c_event = __raw_readl(i2c->regs_cmd+XXX_I2C_EVENT_STAT); __raw_writel(i2c_event, i2c->regs_cmd+XXX_I2C_EVENT_STAT); i2c_state = __raw_readl(i2c->regs_cmd+XXX_I2C_MST_CMD); if (!(i2c_state & I2C_BUSY)) { if (i2c_stat & I2C_LOST_ARB) { printk(KERN_ERR"Lost arbitration\n"); ret = -EAGAIN; } else if (i2c_state & I2C_DEV_NACK) { printk(KERN_ERR"No device acked\n"); ret = -ENODEV; } else if (i2c_state & I2C_REG_NACK) { printk(KERN_ERR"Register not found\n"); ret = -ENODEV; } else { if ((i2c->msg->flags & I2C_M_RD) && msg->len > 0) { for (i=0; i<i2c->msg->len; i++) { tmp = __raw_readl(i2c->regs_cmd+XXX_I2C_MST_DATA_RD); i2c->msg->buf[i] = tmp & 0xFF; } } ret = i2c->msg_idx; } } else { printk(KERN_ERR"Reset I2C controller\n"); __raw_writel(I2C_RESET, i2c->regs_cfg+XXX_I2C_CONFIG); mdelay(100); __raw_writel(I2C_ENABLE, i2c->regs_cfg+XXX_I2C_CONFIG); ret = -EFAULT; } udelay(i2c->delay_time); return ret; } static u32 XXX_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_I2C|I2C_FUNC_SMBUS_EMUL; }

    先來看XXX_i2c_func()這個函式,它只是回傳了這個I2C控制器有什麼樣的功能,這邊當然一定有基本的I2C功能,另外還可以模擬SMBUS,SMBUS跟I2C類似,差別是送出資料的格式不同,在訊號上時序的要求也有一點差異。

    XXX_i2c_xfer()函式就是主要I2C控制器的核心部分,這邊是透過對一些暫存器的讀寫讓資料能按照I2C要求的規格和時序傳送到每一個周邊裝置,其實每個SoC的做法都不一樣,下次遇到的時候可能全部要砍掉重練,不過還是記錄一下這次遇到的做法。這裡有一個需要注意的地方,slave address是7個bits(系統沒有需要10個bits周邊,所以沒有實做10個bits的傳輸),可是每次資料傳送都是送8個bits,所以會把msg的addr欄位往左shift一個bit,最後一個bit代表的是讀(1)或寫(0)的動作。



    要讓資料按照正確的格式送到I2C bus上,重點是要依照一定的流程對暫存器讀寫。我們可以把XXX_i2c_xfer()分成兩部分來看,第一部分從所有的i2c_msg中取得slave address、周邊設備的暫存器位址和要寫的資料並把他們都寫入I2C控制器的XXX_I2C_MST_WR暫存器,在XXX_I2C_MST_WR暫存器中的資料最後會被放到一個FIFO中,等待start被觸發後會依照順序送到I2C bus上,流程圖如下。



第一部分的程式碼就是依照這張流程圖寫出來的,還是有可以最佳化的空間。一開始判斷是否有資料要收送,資料長度為0的msg是SMBUS的QUICK command的部分,有一些工具程式會利用這個功能快速的探測整個I2C bus上所有的周邊。再講讀和寫的部份之前,先來看看I2C規格書裡的三張圖


Fig 11是寫資料到周邊,非常直覺應該一看就懂了。Fig 12是從周邊讀資料,可是目前很多週邊並不採用這種格式,反而是採用Fig 13的格式,一開始會先把周邊的slave address和要讀的暫存器位址送出去,這是一個寫的動作,接下來才是從周邊把資料讀回來,所以需要兩個i2c_msg來完成整的讀的流程。

    第一個部分在觸發start後就把資料都送出去了,接下來進到第二個部分,流程圖如下


首先會先等待整個傳輸的過程完成,這裡是一個busy waiting,也可以採用中斷的方式,當資料送出後就把這個task掛到wait queue上,等待中斷被觸發,中斷產生後喚醒task然後處理接下來的工作。接下來就是判斷I2C 控制器是不是仍然busy,會busy通常都是第一部分的流程出錯了,處理方式就是直接reset控制器。至於其他錯誤發生的情況,lost arbitration最常遇到的是在傳輸過程中時序的錯誤造成周邊誤判,導致雖然整個流程結束,可是SDA仍然被周邊佔用,Linux有提供一個機制來修復這個情形,方法是送出9個clock,為什麼是9個clock? 看一看規格書裡面的時序圖就知道囉! i2c_adapter的i2c_bus_recovery_info欄位就是做為這個用途,不過這需要硬體能夠配合,因為要單獨對SCL做high和low的控制。另外兩個錯誤就是找不到周邊或周邊的暫存器了。之前如果是讀動作,資料會被放在FIFO裡,再透過暫存器讀走就可以了。

    整個XXX_i2c.c的內容就寫到這裡,寫的有點多...幾乎大部分嵌入式系統I2C架構都差不多,會不一樣的地方應該主要都是跟硬體相關的部分,如果想深入了解I2C運作的情形,可以參考I2C規格書,如果想看看其他驅動的做法,在kernel原始碼drivers/i2c/資料夾下有其他平台的範例,不過還是要有其他平台的datasheet會比較知道他在做些什麼。下一次就來看看i2c-dev.c啦。

2014年8月26日 星期二

裝置實例-I2C (1)

    之前寫了這麼多,長篇大論的,可是看來都是自己弄出來的東西,一個虛擬的裝置和一些自己定義的資料結構,還說kernel裡面差不多都是這樣的架構,好像單純自爽而已吧。好吧!那我們就來看看一個"I2C"裝置的實例吧!在開始之前還事先來看看他在Linux下的架構是怎樣吧,順便也可以來跟之前我們自創的架構比較一下。


    如果有google過的話,類似的圖應該有看過,不過這邊就照我自己的理解重新描述一次。這就是從user space一直到硬體層的架構了,先不看硬體層的話,有沒有跟之前的架構很像。XXX_i2c.c是我們真正要實現的部分,裡面有一個很重要的資料結構i2c_adapter,他會提供一個方法來操作跟硬體相關的暫存器,並提供給上層一個callback function,master_xfer()。在上來一層就是i2c-core.c這層,它提供了下層註冊的方法和上層存取下層的方法i2c_transfer(),當然裡面還有很多kernel給的API。接下來再往上到i2c-dev.c,它就是一個字元裝置,提供給user space一個管道,並且之後再調用下層給的方法,裡面也有一個重要的資料結構i2c_client。如果和之前我們自己建立的架構比較起來,這邊其實只增加了i2c-core.c這一層。

    而有關硬體層的部分,主要就是I2C bus的線路,SCL上是clock,SDA上是data。每一個I2C裝置都掛在這個bus上,它們有各自的slave address,透過一些溝通的機制,可以知道哪些資料是要傳送給他們的,詳細的資料可以參考I2C的Spec。

    看到這邊,之前的長篇大論還是有點用處的啦,不是單純自爽,之後會先從我們要自己實現的XXX_i2c.c看起,就不會說明整個bus到device和driver的所有細節,不過遇到重點部分還是會提一下。最後就先列出剛才兩個重要的資料結構結束這篇吧! 暫時看不懂沒關係,之後會慢慢講囉,其實註解已經寫的滿清楚的了。

struct i2c_adapter {
struct module *owner;
unsigned int class;  /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;

/* data fields that are valid for all devices */
struct rt_mutex bus_lock;

int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */

int nr;
char name[48];
struct completion dev_released;

struct mutex userspace_clients_lock;
struct list_head userspace_clients;

struct i2c_bus_recovery_info *bus_recovery_info;
}

struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
                /* addresses are stored in the */
                /* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct i2c_driver *driver; /* and our access routines */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;

}