首先说明下为什么写这篇文章,网上有许多博客也是介绍I2C驱动在linux上移植的实现,但是笔者认为他们相当一部分没有分清所写的驱动时的驱动模型,是基于device tree, 还是基于传统的Platform模型,有些文章只是把代码移植到平台上调试测试下,并没有理清内部逻辑调用关系,所以觉得有必要把两种驱动模型阐述剖析清楚,本文阅读者必须以在单片机上调试过IIC总线为前提,能够分析从芯片datasheet和其工作原理和总线的基本操作,虽然I2C硬件体系结构比较简单,但是I2C体系结构在Linux中的实现却相当复杂,作为驱动工程师,编写具体的I2C驱动时,主要工作如下:

1)、提供I2C适配器的硬件驱动,探测,初始化I2C适配器(如申请I2C的I/O地址和中断号),驱动CPU控制的I2C适配器从硬件上产生各种信号以及处理I2C中断(I2C总线驱动);

2)、提供I2C控制的algorithm, 用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋给i2c_adapter的algo指针(I2C总线驱动),用于产生I2C访问从设备周期所需要的信号;

3)、实现I2C设备驱动中的i2c_driver接口,用具体yyy的yyy_probe(),yyy_remove(),yyy_suspend(),yyy_resume()函数指针和i2c_device_id设备ID表赋给i2c_driver的probe,remove,suspend,resume和id_table指针(I2C设备驱动);

4)、实现I2C设备所对应类型的具体驱动,i2c_driver只是实现设备与总线的挂接(I2C设备驱动)。

Step1,必须理清platform_device和platform_driver之间的匹配方式

  对比linux2.6.29实现方式见图1,linux3.14.78的实现方式见图2,可以发现,传统的Platform驱动模型只是通过匹配platform_device设备名和驱动的名字来实现,而对于3.0以后的内核,通过一下四种方式实现,首先时基于设备树风格的匹配(设备树是一种描述硬件的数据结构),第二种是基于ACPI风格的匹配,第三种是匹配ID表(platform_device设备名是否出现在platform_driver的ID表内),第四种方式才采用传统的匹配设备与驱动的名字来实现,我们先通过匹配platform_device设备名和驱动的名字来实现IIC在2.6.29上移植,然后再分析通过设备树的方式实现IIC在3.14.78上移植,下面我们开始:

  //linux2.6.29系统中为platform总线定义了一个bus_type的实例platform_bus_type,
struct bus_type platform_bus_type = {
.name = “platform”,
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = PLATFORM_PM_OPS_PTR,
};
EXPORT_SYMBOL_GPL(platform_bus_type); //这里要重点关注其match()成员函数,正是此成员表明了platform_device和platform_driver之间如何匹配。
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) == );
}
//匹配platform_device和platform_driver主要看二者的name字段是否相同。
//对platform_device的定义通常在BSP的板文件中实现,在板文件中,将platform_device归纳为一个数组,最终通过platform_add_devices()函数统一注册。
//platform_add_devices()函数可以将平台设备添加到系统中,这个函数的 原型为:
int platform_add_devices(struct platform_device **devs, int num);
//该函数的第一个参数为平台设备数组的指针,第二个参数为平台设备的数量,它内部调用了platform_device_register()函 数用于注册单个的平台设备。
//linux3.14.78内核
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv); /* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return ; /* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return ; /* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL; /* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == );
}

Step2,添加设备对象硬件信息,对应i2c_client,i2c_client的信息通常在BSP的板文件中通过i2c_board_info填充,在系统启动之处静态地进行i2c设备注册(设备注册分为动态发现注册和静态注册)下面的代码定义了一个I2C设备的ID为“24c08”,地址为0x50的i2c_client,使用i2c_register_board_info将所有注册i2c_board_info对象添加到一个名为__i2c_board_list的链表上,具体实现在mach_s3c2440.c中调用i2c_register_board_info,详见下面代码;

 static struct i2c_board_info i2c_devices[] __initdata = {
{I2C_BOARD_INFO("24c08", 0x50), },
};
static void __init My2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&My2440_fb_info);
s3c_i2c0_set_platdata(NULL);
i2c_register_board_info(, i2c_devices, ARRAY_SIZE(i2c_devices)); s3c_device_spi0.dev.platform_data= &s3c2410_spi0_platdata;
spi_register_board_info(s3c2410_spi0_board, ARRAY_SIZE(s3c2410_spi0_board));
s3c_device_spi1.dev.platform_data= &s3c2410_spi1_platdata;
spi_register_board_info(s3c2410_spi1_board, ARRAY_SIZE(s3c2410_spi1_board));
s3c_device_nand.dev.platform_data = &My2440_nand_info;
s3c_device_sdi.dev.platform_data = &My2440_mmc_cfg; platform_add_devices(My2440_devices, ARRAY_SIZE(My2440_devices));
} MACHINE_START(MY2440, "MY2440")
/* Maintainer: Ben Dooks <ben@fluff.org> */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> ) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100, .init_irq = s3c24xx_init_irq,
.map_io = My2440_map_io,
.init_machine = My2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END 

Step3,概念理清楚,i2c_adapter对应物理上的一个适配器,i2c_client对应真实的物理设备,每个i2c都需要一个i2c_client来描述,i2c_client依附于i2c_adapter,由于一个适配器可以连接多个I2C设备,所以一个i2c_adapter可以被多个i2c_client所依附;而i2c_driver对应与一套驱动方法,他们之间的数据结构之间的关系见下图。在系统启动或者模块加载时,i2c适配器设备驱动i2cdev_driver被添加到系统中,具体实现在drivers/i2c/i2c-dev.c中,现在分析它的实现,在i2c-dev.c中,定义了一个名为i2c_dev_init()的初始化函数,从module_init(i2c_dev_init)可以看出,该函数在系统启动或模块加载时执行,主要完成3种操作

  first. 调用register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops),为I2C适配器注册主设备号为I2C_MAJOR(89)、次设备号为0~255、文件操作集合为i2cdev_fops的字符设备,也就是针对每个I2C适配器生成一个主设备号为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口;

  second. 调用class_create(THIS_MODULE, "i2c-dev")注册名为“i2c-dev”的设备类,然后又注册了一个i2cdev_notifier;

  Third. 调用i2c_for_each_dev(NULL, i2cdev_attach_adapter)遍历i2c_bus_type总线上的设备,对找到的设备执行i2cdev_attach_adapter()函数,它首先调用get_free_i2c_dev()分配并初始化一个struct i2c_dev结构,使i2c_dev->adap指向操作的adapter之后,该i2c_dev会被插入到连边i2c_dev_list中,再创建一个device,即绑定adapter并在/dev/目录下创建字符设备节点;

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;
}
i2c_dev_class->dev_groups = i2c_groups;
/* 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 ;
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;
}

Step4,添加i2c总线驱动(I2C适配器驱动的注册),由于I2C总线控制器通常是在内存上的,所以它本身也连接在platform总线上,要通过platform_driver和platform_device的匹配来执行。尽管I2C适配器给别人提供了总线,它自己也是连接在platform总线上的一个客户。在文件drivers/i2c/busses/i2c-s3c2410.c中,platform_driver的注册通过调用初始化函数i2c_adapter_s3c_init函数来完成,与I2C适配器所对应的platform_driver的probe函数主要完成以下两个工作:

  1、初始化I2C适配器所使用的硬件资源,如申请I/O地址、中断号、时钟等;2、通过i2c_add_numbered_adapter()添加i2c_adapter的数据结构,当然这个i2c_adapter数据结构的成员已经被对应的适配器的相应函数指针所初始化具体实现

i2c_add_numbered_adapter(&i2c->adap)-->i2c_register_adapter(adap)-->i2c_scan_static_board_info(adap)-->list_for_each_entry(devinfo, &__i2c_board_list, list)-->i2c_new_device(adapter,&devinfo->board_info)),以adapter结构体和找到的devinfo结构体中的i2c_board_info结构体为参数在i2c_bus_type总线上添加client设备,即添加i2c_client.
 static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata = NULL;
struct resource *res;
int ret;
if (!pdev->dev.of_node) {
pdata = dev_get_platdata(&pdev->dev);
if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
}
}
i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);
if (!i2c) {
dev_err(&pdev->dev, "no memory for state\n");
return -ENOMEM;
}
i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!i2c->pdata) {
dev_err(&pdev->dev, "no memory for platform data\n");
return -ENOMEM;
}
i2c->quirks = s3c24xx_get_device_quirks(pdev);
if (pdata)
memcpy(i2c->pdata, pdata, sizeof(*pdata));
else
s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c);
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm; //设置适配器的通信方法 32
i2c->adap.retries = ;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = ;
init_waitqueue_head(&i2c->wait);
/* find the clock and enable it */
i2c->dev = &pdev->dev;
i2c->clk = devm_clk_get(&pdev->dev, "i2c");
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
return -ENOENT;
}
dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
/* map the registers */
res = platform_get_resource(pdev, IORESOURCE_MEM, );
i2c->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(i2c->regs))
return PTR_ERR(i2c->regs);
dev_dbg(&pdev->dev, "registers %p (%p)\n",
i2c->regs, res);
/* setup info block for the i2c core ,将s3c24xx_i2c类型的对象I2C作为通信方法的私有数据存放在algo_data中,
      这样master_xfer等方法就能通过适配器的algo_data获得这个对象
     */
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev);
/* inititalise the i2c gpio lines */
if (i2c->pdata->cfg_gpio) {
i2c->pdata->cfg_gpio(to_platform_device(i2c->dev));
} else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c)) {
return -EINVAL;
}
/* initialise the i2c controller*/
clk_prepare_enable(i2c->clk);
ret = s3c24xx_i2c_init(i2c);
clk_disable(i2c->clk);
if (ret != ) {
dev_err(&pdev->dev, "I2C controller init failed\n");
return ret;
}
/* find the IRQ for this unit (note, this relies on the init call to
70 * ensure no current IRQs pending,获取IRQ资源并注册该中断,s3c24xx_i2c_irq是用来后续传输的中断处理函数
71 */
if (!(i2c->quirks & QUIRK_POLL)) {
i2c->irq = ret = platform_get_irq(pdev, );
if (ret <= ) {
dev_err(&pdev->dev, "cannot find IRQ\n");
clk_unprepare(i2c->clk);
return ret;
}
ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq, ,
dev_name(&pdev->dev), i2c);
if (ret != ) {
dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq);
clk_unprepare(i2c->clk);
return ret;
}
}
ret = s3c24xx_i2c_register_cpufreq(i2c);
if (ret < ) {
dev_err(&pdev->dev, "failed to register cpufreq notifier\n");
clk_unprepare(i2c->clk);
return ret;
}
/* Note, previous versions of the driver used i2c_add_adapter()
94 * to add the bus at any number. We now pass the bus number via
95 * the platform data, so if unset it will now default to always
96 * being bus 0.
97 */
i2c->adap.nr = i2c->pdata->bus_num;
i2c->adap.dev.of_node = pdev->dev.of_node;
platform_set_drvdata(pdev, i2c);
pm_runtime_enable(&pdev->dev);
ret = i2c_add_numbered_adapter(&i2c->adap); //静态方式添加适配器
if (ret < ) {
dev_err(&pdev->dev, "failed to add bus to i2c core\n");
pm_runtime_disable(&pdev->dev);
s3c24xx_i2c_deregister_cpufreq(i2c);
clk_unprepare(i2c->clk);
return ret;
}
pm_runtime_enable(&i2c->adap.dev);
dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
return ;
}

Step5,编写i2c设备驱动,这部分工作就完全由驱动工程师来完成了。之前所完成的工作:I2C适配器所在驱动的platform_driver与arch/arm/mach-s3c2440中的platform(或者设备树中的节点)通过platform总线的match()函数导致s3c24xx_i2c_probe的执行,从而完成I2C适配器控制器的注册;而挂载在I2C上面的设备,以陀螺仪MPU6050为对象,它所依附的i2c_driver与arch/arm/mach-s3c2440中的i2c_board_info指向的设备(或设备树中的节点)通过I2C总线的match()函数匹配导致i2c_driver.i2c_probe执行。

  设备树信息

  i2c@138B0000 {
#address-cells = <>;
#size-cells = <>;
samsung,i2c-sda-delay = <>;
samsung,i2c-max-bus-freq = <>;
pinctrl- = <&i2c5_bus>;
pinctrl-names = "default";
status = "okay"; mpu6050@ {
compatible = "fs4412,mpu6050";
reg = <0x68>;
};
};
//i2c_driver.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include "mpu6050.h" #define DEVICE_MAJOR 665
#define DEVICE_MINOR 0
#define DEV_NUM 1
#define DEVICE_NAME "mpu6050" struct mpu6050_device{
struct cdev i2c_cdev;
struct i2c_client *client;
}; struct mpu6050_device* mpu6050_device;
struct class* cls; dev_t devno;
int ret; MODULE_LICENSE("GPL"); static const struct of_device_id i2c_dt_table[]={ {
.compatible="i2c,mpu6050",
},
{
}, };
static const struct i2c_device_id i2c_id_table[]={
{
"mpu6050",
},
{
}, }; static int mpu6050_read_byte(struct i2c_client* client,char reg)
{ char txbuf[]={reg};
char rxbuf[];
struct i2c_msg msg[]={
{
client->addr,,,txbuf },
{
client->addr,,,rxbuf
} };
ret=i2c_transfer(client->adapter,msg,ARRAY_SIZE(msg));
if(ret<)
{
return -EFAULT;
}
return rxbuf[]; } static int mpu6050_write_byte(struct i2c_client* client,char reg,char val)
{ char txbuf[]={reg,val};
struct i2c_msg msg[]={
{
client->addr,,,txbuf },
};
ret=i2c_transfer(client->adapter,msg,ARRAY_SIZE(msg));
if(ret<)
{
printk("i2c_transfer failed!!\n");
return -EFAULT;
}
return ;
} static int i2c_open(struct inode *inode,struct file* file)
{
return ;
}
static int i2c_release(struct inode *inode,struct file* file)
{
return ;
}
static long i2c_ioctl(struct file* file,unsigned int cmd,unsigned long arg)
{
union mpu6050_data data;
struct i2c_client *client=mpu6050_device->client;
switch (cmd)
{
case GET_ACCEL:
data.accel.x=mpu6050_read_byte(client,ACCEL_XOUT_L);
data.accel.x|=mpu6050_read_byte(client,ACCEL_XOUT_H)<<;
data.accel.y=mpu6050_read_byte(client,ACCEL_YOUT_L);
data.accel.y|=mpu6050_read_byte(client,ACCEL_YOUT_H)<<;
data.accel.z=mpu6050_read_byte(client,ACCEL_ZOUT_L);
data.accel.z|=mpu6050_read_byte(client,ACCEL_ZOUT_H)<<;
break;
case GET_GYRO:
data.gyro.x=mpu6050_read_byte(client,GYRO_XOUT_L);
data.gyro.x|=mpu6050_read_byte(client,GYRO_XOUT_H)<<;
data.gyro.y=mpu6050_read_byte(client,GYRO_YOUT_L);
data.gyro.y|=mpu6050_read_byte(client,GYRO_YOUT_H)<<;
data.gyro.z=mpu6050_read_byte(client,GYRO_ZOUT_L);
data.gyro.z|=mpu6050_read_byte(client,GYRO_ZOUT_H)<<;
break;
case GET_TEMP:
data.temp=mpu6050_read_byte(client,TEMP_OUT_L);
data.temp|=mpu6050_read_byte(client,TEMP_OUT_H)<<;
break;
default :
printk("invalid argument\n");
return -EINVAL; } if(copy_to_user((void*)arg,&data,sizeof(data)))
{
return -EFAULT;
}
return sizeof(data);
} const struct file_operations fops={
.owner=THIS_MODULE,
.open=i2c_open,
.release=i2c_release,
.unlocked_ioctl=i2c_ioctl,
}; int i2c_probe(struct i2c_client* client,const struct i2c_device_id* device_id)
{
printk("i2c_mpu6050 probe!!!\n");
devno=MKDEV(DEVICE_MAJOR,DEVICE_MINOR);
mpu6050_device=kzalloc(sizeof(mpu6050_device),GFP_KERNEL);
if(mpu6050_device==NULL)
{
return -ENOMEM;
}
mpu6050_device->client=client; ret=register_chrdev_region(devno,DEV_NUM,DEVICE_NAME);
if(ret<)
{
printk("register_chrdev_region failed!!!\n");
return -;
} cls=class_create(THIS_MODULE,"I2C");
if(IS_ERR(cls))
{
printk("class:I2C create failed!!!\n");
}
printk("class:I2C create succeed!!!\n");
device_create(cls,NULL,devno,NULL,"MPU6050");
cdev_init(&mpu6050_device->i2c_cdev,&fops);
mpu6050_device->i2c_cdev.owner=THIS_MODULE;
printk("i2c_cdev init succeed!!!\n");
cdev_add(&mpu6050_device->i2c_cdev,devno,DEV_NUM);
printk("i2c_cdev add succeed!!!\n"); mpu6050_write_byte(client,SMPLRT_DIV,0X07);
mpu6050_write_byte(client,CONFIG,0X06);
mpu6050_write_byte(client,GYRO_CONFIG,0XF8);
mpu6050_write_byte(client,ACCEL_CONFIG,0X19);
mpu6050_write_byte(client,PWR_MGMT_1,0X00);
mpu6050_write_byte(client,WHO_AM_I,0x68); return ;
}
int i2c_remove(struct i2c_client*client)
{ device_destroy(cls,devno);
class_destroy(cls);
printk("class_destroy succeed!!!\n");
cdev_del(&mpu6050_device->i2c_cdev);
unregister_chrdev_region(devno,DEV_NUM); kfree(mpu6050_device);
return ;
}
static struct i2c_driver i2c_dr ={ .driver={
.name="mpu6050",
.of_match_table=i2c_dt_table,
},
.id_table=i2c_id_table,
.probe=i2c_probe,
.remove=i2c_remove,
}; module_i2c_driver(i2c_dr);

 test.c

 #include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> #include "mpu6050.h"
int main(int argc, const char *argv[])
{
int fd;
float temperature;
// unsigned long data;
union mpu6050_data data;
int ret;
fd=open("/dev/MPU6050",O_RDWR);
if(fd<)
{
perror("fail to open MPU6050");
return -;
}
printf("open /dev/MPU6050 succeed!\n");
while()
{ printf("****************data from mpu6050*************************\n");
printf("\n"); ioctl(fd,GET_ACCEL,&data);
printf("accel:x=%-5d,y=%-5d,z=%-5d\n",data.accel.x,data.accel.y,data.accel.z);
printf("\n"); ioctl(fd,GET_GYRO,&data);
printf("gyro :x=%-5d,y=%-5d,z=%-5d\n",data.gyro.x,data.gyro.y,data.gyro.z);
printf("\n"); ioctl(fd,GET_TEMP,&data);
temperature=((float)data.temp)/+36.53;
printf("temperature=%f\n",temperature);
printf("\n"); sleep(); }

  实际采集到的数据:

参考文献:1.《深入Linux内核架构》 2.《Linux设备驱动开发详解》

IIC驱动移植在linux3.14.78上的实现和在linux2.6.29上实现对比(deep dive)的更多相关文章

  1. DM9000驱动移植在mini2440(linux2.6.29)和FS4412(linux3.14.78)上的实现(deep dive)篇一

    关于dm9000的驱动移植分为两篇,第一篇在mini2440上实现,基于linux2.6.29,也成功在在6410上移植了一遍,和2440非常类似,第二篇在fs4412(Cortex A9)上实现,基 ...

  2. SPI在linux3.14.78 FS_S5PC100(Cortex A8)和S3C2440上驱动移植(deep dive)

    由于工作的原因,对SPI的理解最为深刻,也和SPI最有感情了,之前工作都是基于OSEK操作系统上进行实现,也在US/OS3上实现过SPI驱动的实现和测试,但是都是基于基本的寄存器操作,没有一个系统软件 ...

  3. LCD驱动移植在在mini2440(linux2.6.29)和FS4412(linux3.14.78)上实现对比(deep dive)

    1.Linux帧缓冲子系统 帧缓冲(FrameBuffer)是Linux为显示设备提供的一个接口,用户可以将帧缓冲看成是显示内存的一种映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作 ...

  4. 基于MT6752/32平台 Android L版本驱动移植步骤

    基于MT6752/32平台 Android L版本驱动移植步骤 根据MK官网所述,在Android L 版本上Turnkey ABS 架构将会phase out,而Mediatek Turnkey架构 ...

  5. S3C6410嵌入式应用平台构建(五)——linux-3.14.4移植到OK6410-(Nand分区问题)

    前一篇文章,我们的Linux能后启动了,只是在识别nand时候,没有获取到时钟源,导致后面的分区没哟进行. 我们从启动的log发现: [06/08-11:25:41:371]s3c24xx-nand ...

  6. S3C6410嵌入式应用平台构建(四)——linux-3.14.4移植到OK6410-(初步启动)

    这次,还是把基本的基于我目前最新的Linux源码进行移植到OK6410吧,同时也写下我移植过程中遇到的问题及解决方法,不过有些方法是借鉴网上的,有些是自己加的,会有一些小bug. 一.基本工作 1. ...

  7. S3C6410嵌入式应用平台构建(六)——linux-3.14.4移植到OK6410-(Yaffs2文件制作)

    本文主要讲怎用利用yaffs2工具和busybox制作yaffs2文件系统镜像.大多数都是参照网上的,目的在于记录学习,不做任何用途. 一.制作mkyaffs2image工具 进入yaffs2源码目录 ...

  8. 将移远通信的EC20驱动移植到NUC972上(转)

    源: 将移远通信的EC20驱动移植到NUC972上

  9. AM335x(TQ335x)学习笔记——USB驱动移植

    对于AM335x来讲,TI维护的USB驱动已经非常完善了,本文称之为移植,实际上仅仅是配置内核选项使能USB HOST/OTG功能.废话少说,直接动手开启AM335x的USB驱动配置项. Step1. ...

随机推荐

  1. css3中perspective

    perspective 属性定义 3D 元素距视图的距离,以像素计.该属性允许改变 3D 元素查看 3D 元素的视图.当为元素定义 perspective 属性时,其子元素会获得透视效果,而不是元素本 ...

  2. android 使用Tabhost 发生could not create tab content because could not find view with id 错误

    使用Tabhost的时候经常报:could not create tab content because could not find view with id 错误. 总结一下发生错误的原因,一般的 ...

  3. 前端自动化构建工具gulp记录

    一.安装 1)安装nodejs 通过nodejs的npm安装gulp,插件也可以通过npm安装.windows系统是个.msi工具,只要一直下一步即可,软件会自动在写入环境变量中,这样就能在cmd命令 ...

  4. Java消息队列--ActiveMq 实战

    1.下载安装ActiveMQ ActiveMQ官网下载地址:http://activemq.apache.org/download.html ActiveMQ 提供了Windows 和Linux.Un ...

  5. ios label 自动计算行高详解

    在OC当中自动计算行高主要调用系统的 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ffffff } span ...

  6. Linux系统中用DNW向ARM开发板下载程序

    在Linux下通过dnw来给开发板发送程序.包括驱动程序代码:secbulk.c,应用程序代码:dnw.c.只能运行在32位系统上,在64位系统上提示错误:DNW download Data size ...

  7. could not initialize proxy - no Session

    这是一个精典的问题:因为我们在hibernate里面load一个对象出来时,用到的是代理对象,也就是说当我们在执行load方法时并没有发sql语句,而是返回一个proxy对象.只有当们具体用到哪个ge ...

  8. 数据分布转换:非正态 -> 正态

    来源:丁香园论坛:SPSS上的把非正态分布数据转换为正态分布数据 一楼 可以应用变量变换的方法,将不服从正态分布的资料转化为非正态分布或近似正态分布.常用的变量变换方法有对数变换.平方根变换.倒数变换 ...

  9. 我的MYSQL学习心得(十三) 权限管理

    我的MYSQL学习心得(十三) 权限管理 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) ...

  10. [PHP源码阅读]array_pop和array_shift函数

    上篇文章介绍了PHP添加元素到数组的函数,那么当然有从数组中删除元素.array_pop和array_shift只从数组的头或尾删除一个元素.经过阅读源码,发现这两个函数的实现都是调用了同一个函数-- ...