链表的知识:

struct list_head {struct list_head *next, *prev;
};

API函数

函数 功能
LIST_HEAD 声明并初始化双向链表。
INIT_LIST_HEAD 初始化双向链表。
list_add 在链表头@head节点后面插入一个新的节点@new。
list_add_tail 在链表末尾@tail节点后面插入一个新的节点@new。
list_del 删除链表中指定的节点,并对删除节点成员置NULL。
list_del_init 删除链表中指定的节点,并对删除节点重新初始化。
list_for_each 正向遍历链表中每个成员。
list_for_each_prev 反向遍历链表中每个成员。
list_entry 通过type结构体成员member的地址ptr得到member所在type结构体的首地址。
list_for_each_entry 通过遍历链表得到member所在的type结构体的首地址。
list_replace_init 将链表中@old节点替换为@new节点,并对@old节点重新初始化。
list_move 将一个节点从一个链表中移动到另一个链表头@head节点后面。
list_move_tail 将一个节点从一个链表中移动到另一个链表末尾@tail节点后面
list_empty 测试某个节点所在的链表是否为空。

list_add:

static inline void list_add(struct list_head *new, struct list_head *head)
{__list_add(new, head, head->next);
}
static inline void __list_add(struct list_head *new,struct list_head *prev,struct list_head *next)
{next->prev = new;new->next = next;new->prev = prev;prev->next = new;
}

如此插入意思是:
1.新的节点的prev指向head,next指向上一次的节点
2.head的next指向新的节点
3.上一次的节点prev指向新节点。

单向链表寄宿法

#include <stdio.h>
#include <stdlib.h>
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({              \
const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
(type *)( (char *)__mptr - offsetof(type,member) );})#define LIST_HEAD_INIT(name) { &(name) } //将prev和next都设置为name,说明只有一个节点
#define LIST_HEAD(name) \struct list_head name = LIST_HEAD_INIT(name)typedef struct list_head{struct list_head *next;
}LIST;typedef struct {LIST list;char name[128];int  age;
}STUDENT;
LIST student_list;
void list_add(LIST *new,LIST *head,LIST *next)
{head->next=new;new->next=next;
}
void list_add_tail(LIST *new,LIST *head)
{LIST *list_itertor=head;while(list_itertor->next){list_itertor=list_itertor->next;}list_itertor->next=new;new->next=NULL;
}int main(void) {printf("Hello World\n");for(int i=0;i<10;i++){STUDENT *student=(STUDENT*)malloc(sizeof(STUDENT));sprintf(student->name,"student%d",i);student->age=i;//list_add(&student->list,&student_list,student_list.next);list_add_tail(&student->list,&student_list);}LIST *list_itertor=student_list.next;while(list_itertor){STUDENT *student=container_of(list_itertor,STUDENT,list);printf("name=%s\n",student->name);list_itertor=list_itertor->next;}return 0;
}

多层链表寄宿

#include <stdio.h>
#include <stdlib.h>
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({              \
const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
(type *)( (char *)__mptr - offsetof(type,member) );})#define LIST_HEAD_INIT(name) { &(name) } //将prev和next都设置为name,说明只有一个节点
#define LIST_HEAD(name) \struct list_head name = LIST_HEAD_INIT(name)typedef struct list_head{struct list_head *next;
}LIST;typedef struct {LIST list;char name[128];int  age;
}STUDENT;typedef struct {LIST stu_list;LIST class_list;char name[128];
}CLASS;
typedef struct {LIST list;char name[128];
}SCHOOL;CLASS class;
SCHOOL school;
void list_add(LIST *new,LIST *head,LIST *next)
{head->next=new;new->next=next;
}
void list_add_tail(LIST *new,LIST *head)
{LIST *list_itertor=head;while(list_itertor->next){list_itertor=list_itertor->next;}list_itertor->next=new;new->next=NULL;
}int main(void) {printf("Hello World\n");for(int i=0;i<10;i++){CLASS *class=(CLASS*)malloc(sizeof(CLASS));sprintf(class->name,"class%d",i);list_add(&class->class_list,&school.list,school.list.next);for(int i=0;i<100;i++){STUDENT *student=(STUDENT*)malloc(sizeof(STUDENT));sprintf(student->name,"student%d",i);list_add(&student->list,&class->stu_list,class->stu_list.next);}}LIST *list_itertor=school.list.next;while(list_itertor){CLASS *class=container_of(list_itertor,CLASS,class_list);printf("name=%s \n",class->name);LIST *s_ist_itertor=class->stu_list.next;while(s_ist_itertor){STUDENT *sdudent =container_of(s_ist_itertor,STUDENT,list);printf("name=%s \n",sdudent->name);s_ist_itertor=s_ist_itertor->next;}list_itertor=list_itertor->next;}return 0;
}

内核线程的知识

#define kthread_run(threadfn, data, namefmt, ...) \
({ \
struct task_struct *__k \
= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
if (!IS_ERR(__k)) \
wake_up_process(__k); \
__k; \
})
#define kthread_create(threadfn, data, namefmt, arg...) \kthread_create_on_node(threadfn, data, NUMA_NO_NODE, namefmt, ##arg)
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
void *data, int node,
const char namefmt[],
...)
{struct task_struct *task;
va_list args;va_start(args, namefmt);
task = __kthread_create_on_node(threadfn, data, node, namefmt, args);
va_end(args);return task;
}
struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),
void *data, int node,
const char namefmt[],
va_list args)
{DECLARE_COMPLETION_ONSTACK(done);
struct task_struct *task;
struct kthread_create_info *create = kmalloc(sizeof(*create),
GFP_KERNEL);if (!create)
return ERR_PTR(-ENOMEM);
create->threadfn = threadfn;
create->data = data;
create->node = node;
create->done = &done;spin_lock(&kthread_create_lock);
list_add_tail(&create->list, &kthread_create_list);
spin_unlock(&kthread_create_lock);wake_up_process(kthreadd_task);
/*
* Wait for completion in killable state, for I might be chosen by
* the OOM killer while kthreadd is trying to allocate memory for
* new kernel thread.
*/
if (unlikely(wait_for_completion_killable(&done))) {/*
* If I was SIGKILLed before kthreadd (or new kernel thread)
* calls complete(), leave the cleanup of this structure to
* that thread.
*/
if (xchg(&create->done, NULL))
return ERR_PTR(-EINTR);
/*
* kthreadd (or new kernel thread) will call complete()
* shortly.
*/
wait_for_completion(&done);
}
task = create->result;
if (!IS_ERR(task)) {static const struct sched_param param = { .sched_priority = 0 };vsnprintf(task->comm, sizeof(task->comm), namefmt, args);
/*
* root may have changed our (kthreadd's) priority or CPU mask.
* The kernel thread should not inherit these properties.
*/
sched_setscheduler_nocheck(task, SCHED_NORMAL, &param);
set_cpus_allowed_ptr(task, cpu_all_mask);
}
kfree(create);
return task;
}

重要宏定义container_of(ptr, type, member)得到type的地址

#define container_of(ptr, type, member) ({            \const typeof(((type *)0)->member) *__mptr = (ptr);    \(type *)((char *)__mptr - offsetof(type, member));    \
})

SPI驱动详解

spi_imx_probe

static int spi_imx_probe(struct platform_device *pdev)
{struct device_node *np = pdev->dev.of_node;const struct of_device_id *of_id =of_match_device(spi_imx_dt_ids, &pdev->dev);struct spi_imx_master *mxc_platform_info =dev_get_platdata(&pdev->dev);struct spi_master *master;struct spi_imx_data *spi_imx;struct resource *res;int i, ret, irq, spi_drctl;const struct spi_imx_devtype_data *devtype_data = of_id ? of_id->data :(struct spi_imx_devtype_data *)pdev->id_entry->driver_data;bool slave_mode;if (!np && !mxc_platform_info) {dev_err(&pdev->dev, "can't get the platform data\n");return -EINVAL;}slave_mode = devtype_data->has_slavemode &&of_property_read_bool(np, "spi-slave");if (slave_mode)master = spi_alloc_slave(&pdev->dev,sizeof(struct spi_imx_data));elsemaster = spi_alloc_master(&pdev->dev,sizeof(struct spi_imx_data));if (!master)return -ENOMEM;ret = of_property_read_u32(np, "fsl,spi-rdy-drctl", &spi_drctl);if ((ret < 0) || (spi_drctl >= 0x3)) {/* '11' is reserved */spi_drctl = 0;}platform_set_drvdata(pdev, master);master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);master->bus_num = np ? -1 : pdev->id;spi_imx = spi_master_get_devdata(master);spi_imx->bitbang.master = master;spi_imx->dev = &pdev->dev;spi_imx->slave_mode = slave_mode;spi_imx->devtype_data = devtype_data;/* Get number of chip selects, either platform data or OF */if (mxc_platform_info) {master->num_chipselect = mxc_platform_info->num_chipselect;if (mxc_platform_info->chipselect) {master->cs_gpios = devm_kzalloc(&master->dev,sizeof(int) * master->num_chipselect, GFP_KERNEL);if (!master->cs_gpios)return -ENOMEM;for (i = 0; i < master->num_chipselect; i++)master->cs_gpios[i] = mxc_platform_info->chipselect[i];}} else {u32 num_cs;if (!of_property_read_u32(np, "num-cs", &num_cs))master->num_chipselect = num_cs;/* If not preset, default value of 1 is used */}spi_imx->bitbang.chipselect = spi_imx_chipselect;spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;spi_imx->bitbang.txrx_bufs = spi_imx_transfer;spi_imx->bitbang.master->setup = spi_imx_setup;spi_imx->bitbang.master->cleanup = spi_imx_cleanup;spi_imx->bitbang.master->prepare_message = spi_imx_prepare_message;spi_imx->bitbang.master->unprepare_message = spi_imx_unprepare_message;spi_imx->bitbang.master->slave_abort = spi_imx_slave_abort;spi_imx->bitbang.master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH \| SPI_NO_CS;if (is_imx35_cspi(spi_imx) || is_imx51_ecspi(spi_imx) ||is_imx53_ecspi(spi_imx))spi_imx->bitbang.master->mode_bits |= SPI_LOOP | SPI_READY;spi_imx->spi_drctl = spi_drctl;init_completion(&spi_imx->xfer_done);res = platform_get_resource(pdev, IORESOURCE_MEM, 0);spi_imx->base = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(spi_imx->base)) {ret = PTR_ERR(spi_imx->base);goto out_master_put;}spi_imx->base_phys = res->start;irq = platform_get_irq(pdev, 0);if (irq < 0) {ret = irq;goto out_master_put;}ret = devm_request_irq(&pdev->dev, irq, spi_imx_isr, 0,dev_name(&pdev->dev), spi_imx);if (ret) {dev_err(&pdev->dev, "can't get irq%d: %d\n", irq, ret);goto out_master_put;}spi_imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg");if (IS_ERR(spi_imx->clk_ipg)) {ret = PTR_ERR(spi_imx->clk_ipg);goto out_master_put;}spi_imx->clk_per = devm_clk_get(&pdev->dev, "per");if (IS_ERR(spi_imx->clk_per)) {ret = PTR_ERR(spi_imx->clk_per);goto out_master_put;}ret = clk_prepare_enable(spi_imx->clk_per);if (ret)goto out_master_put;ret = clk_prepare_enable(spi_imx->clk_ipg);if (ret)goto out_put_per;spi_imx->spi_clk = clk_get_rate(spi_imx->clk_per);/** Only validated on i.mx35 and i.mx6 now, can remove the constraint* if validated on other chips.*/if (spi_imx->devtype_data->has_dmamode) {ret = spi_imx_sdma_init(&pdev->dev, spi_imx, master);if (ret == -EPROBE_DEFER)goto out_clk_put;if (ret < 0)dev_err(&pdev->dev, "dma setup error %d, use pio\n",ret);}spi_imx->devtype_data->reset(spi_imx);spi_imx->devtype_data->intctrl(spi_imx, 0);master->dev.of_node = pdev->dev.of_node;ret = spi_bitbang_start(&spi_imx->bitbang);启动spi_bitbangif (ret) {dev_err(&pdev->dev, "bitbang start failed with %d\n", ret);goto out_clk_put;}/* Request GPIO CS lines, if any */if (!spi_imx->slave_mode && master->cs_gpios) {for (i = 0; i < master->num_chipselect; i++) {if (!gpio_is_valid(master->cs_gpios[i]))continue;ret = devm_gpio_request(&pdev->dev,master->cs_gpios[i],DRIVER_NAME);if (ret) {dev_err(&pdev->dev, "Can't get CS GPIO %i\n",master->cs_gpios[i]);goto out_spi_bitbang;}}}dev_info(&pdev->dev, "probed\n");clk_disable(spi_imx->clk_ipg);clk_disable(spi_imx->clk_per);return ret;out_spi_bitbang:spi_bitbang_stop(&spi_imx->bitbang);
out_clk_put:clk_disable_unprepare(spi_imx->clk_ipg);
out_put_per:clk_disable_unprepare(spi_imx->clk_per);
out_master_put:spi_master_put(master);return ret;
}

spi_bitbang_start

初始化master的函数指针
master->prepare_transfer_hardware = spi_bitbang_prepare_hardware;
master->unprepare_transfer_hardware = spi_bitbang_unprepare_hardware;
master->transfer_one = spi_bitbang_transfer_one; //初始化transfer_one
master->set_cs = spi_bitbang_set_cs;
调用ret = spi_register_master(spi_master_get(master));创建master驱动器

int spi_bitbang_start(struct spi_bitbang *bitbang)
{struct spi_master *master = bitbang->master;int ret;if (!master || !bitbang->chipselect)return -EINVAL;mutex_init(&bitbang->lock);if (!master->mode_bits)master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags;if (master->transfer || master->transfer_one_message)return -EINVAL;master->prepare_transfer_hardware = spi_bitbang_prepare_hardware;master->unprepare_transfer_hardware = spi_bitbang_unprepare_hardware;master->transfer_one = spi_bitbang_transfer_one; //初始化transfer_onemaster->set_cs = spi_bitbang_set_cs;if (!bitbang->txrx_bufs) {bitbang->use_dma = 0;bitbang->txrx_bufs = spi_bitbang_bufs;if (!master->setup) {if (!bitbang->setup_transfer)bitbang->setup_transfer =spi_bitbang_setup_transfer;master->setup = spi_bitbang_setup;master->cleanup = spi_bitbang_cleanup;}}/* driver may get busy before register() returns, especially* if someone registered boardinfo for devices*/ret = spi_register_master(spi_master_get(master));if (ret)spi_master_put(master);return 0;
}

spi_register_controller

注册master控制器
调用 status = spi_controller_initialize_queue(ctlr);//在这里创建了内核线程worker机制

int spi_register_controller(struct spi_controller *ctlr)
{struct device        *dev = ctlr->dev.parent;struct boardinfo    *bi;int            status = -ENODEV;int            id, first_dynamic;if (!dev)return -ENODEV;if (!spi_controller_is_slave(ctlr)) {status = of_spi_register_master(ctlr);if (status)return status;}/* even if it's just one always-selected device, there must* be at least one chipselect*/if (ctlr->num_chipselect == 0)return -EINVAL;/* allocate dynamic bus number using Linux idr */if ((ctlr->bus_num < 0) && ctlr->dev.of_node) {id = of_alias_get_id(ctlr->dev.of_node, "spi");if (id >= 0) {ctlr->bus_num = id;mutex_lock(&board_lock);id = idr_alloc(&spi_master_idr, ctlr, ctlr->bus_num,ctlr->bus_num + 1, GFP_KERNEL);mutex_unlock(&board_lock);if (WARN(id < 0, "couldn't get idr"))return id == -ENOSPC ? -EBUSY : id;}}if (ctlr->bus_num < 0) {first_dynamic = of_alias_get_highest_id("spi");if (first_dynamic < 0)first_dynamic = 0;elsefirst_dynamic++;mutex_lock(&board_lock);id = idr_alloc(&spi_master_idr, ctlr, first_dynamic,0, GFP_KERNEL);mutex_unlock(&board_lock);if (WARN(id < 0, "couldn't get idr"))return id;ctlr->bus_num = id;}INIT_LIST_HEAD(&ctlr->queue);spin_lock_init(&ctlr->queue_lock);spin_lock_init(&ctlr->bus_lock_spinlock);mutex_init(&ctlr->bus_lock_mutex);mutex_init(&ctlr->io_mutex);ctlr->bus_lock_flag = 0;init_completion(&ctlr->xfer_completion);if (!ctlr->max_dma_len)ctlr->max_dma_len = INT_MAX;/* register the device, then userspace will see it.* registration fails if the bus ID is in use.*/dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);status = device_add(&ctlr->dev);if (status < 0) {/* free bus id */mutex_lock(&board_lock);idr_remove(&spi_master_idr, ctlr->bus_num);mutex_unlock(&board_lock);goto done;}dev_dbg(dev, "registered %s %s\n",spi_controller_is_slave(ctlr) ? "slave" : "master",dev_name(&ctlr->dev));/* If we're using a queued driver, start the queue */if (ctlr->transfer)dev_info(dev, "controller is unqueued, this is deprecated\n");else {status = spi_controller_initialize_queue(ctlr);//在这里创建了内核线程worker机制if (status) {device_del(&ctlr->dev);/* free bus id */mutex_lock(&board_lock);idr_remove(&spi_master_idr, ctlr->bus_num);mutex_unlock(&board_lock);goto done;}}/* add statistics */spin_lock_init(&ctlr->statistics.lock);mutex_lock(&board_lock);list_add_tail(&ctlr->list, &spi_controller_list);list_for_each_entry(bi, &board_list, list)spi_match_controller_to_boardinfo(ctlr, &bi->board_info);mutex_unlock(&board_lock);/* Register devices from the device tree and ACPI */of_register_spi_devices(ctlr);acpi_register_spi_devices(ctlr);
done:return status;
}

spi_controller_initialize_queue

初始化ctlr->transfer = spi_queued_transfer;
初始化ctlr->transfer_one_message = spi_transfer_one_message;
ret = spi_init_queue(ctlr);初始化队列,创建work机制
ret = spi_start_queue(ctlr);启动队列

static int spi_controller_initialize_queue(struct spi_controller *ctlr)
{int ret;ctlr->transfer = spi_queued_transfer;if (!ctlr->transfer_one_message)ctlr->transfer_one_message = spi_transfer_one_message;/* Initialize and start queue */ret = spi_init_queue(ctlr);初始化队列if (ret) {dev_err(&ctlr->dev, "problem initializing queue\n");goto err_init_queue;}ctlr->queued = true;ret = spi_start_queue(ctlr);启动队列if (ret) {dev_err(&ctlr->dev, "problem starting queue\n");goto err_start_queue;}return 0;err_start_queue:spi_destroy_queue(ctlr);
err_init_queue:return ret;
}

spi_init_queue

初始化队列
创建内核线程的worker机制
初始化work的函数,spi_pump_messages

static int spi_init_queue(struct spi_controller *ctlr)
{struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };ctlr->running = false;ctlr->busy = false;kthread_init_worker(&ctlr->kworker);ctlr->kworker_task = kthread_run(kthread_worker_fn, &ctlr->kworker,"%s", dev_name(&ctlr->dev));if (IS_ERR(ctlr->kworker_task)) {dev_err(&ctlr->dev, "failed to create message pump task\n");return PTR_ERR(ctlr->kworker_task);}kthread_init_work(&ctlr->pump_messages, spi_pump_messages);//初始化发消息work->fn的函数/** Controller config will indicate if this controller should run the* message pump with high (realtime) priority to reduce the transfer* latency on the bus by minimising the delay between a transfer* request and the scheduling of the message pump thread. Without this* setting the message pump thread will remain at default priority.*/if (ctlr->rt) {dev_info(&ctlr->dev,"will run message pump with realtime priority\n");sched_setscheduler(ctlr->kworker_task, SCHED_FIFO, &param);}return 0;
}

kthread_worker_fn

如果worker->work_list不为空,遍历list链表,拿到当前的work
work->func(work);执行work的func函数

int kthread_worker_fn(void *worker_ptr)
{struct kthread_worker *worker = worker_ptr;struct kthread_work *work;/** FIXME: Update the check and remove the assignment when all kthread* worker users are created using kthread_create_worker*() functions.*/WARN_ON(worker->task && worker->task != current);worker->task = current;if (worker->flags & KTW_FREEZABLE)set_freezable();repeat:set_current_state(TASK_INTERRUPTIBLE);    /* mb paired w/ kthread_stop */if (kthread_should_stop()) {__set_current_state(TASK_RUNNING);spin_lock_irq(&worker->lock);worker->task = NULL;spin_unlock_irq(&worker->lock);return 0;}work = NULL;spin_lock_irq(&worker->lock);if (!list_empty(&worker->work_list)) {work = list_first_entry(&worker->work_list,struct kthread_work, node);/********************如果worker->work_list不为空,遍历list链表,拿到当前的work******************/list_del_init(&work->node);}worker->current_work = work;spin_unlock_irq(&worker->lock);if (work) {__set_current_state(TASK_RUNNING);work->func(work);执行work的func函数} else if (!freezing(current))schedule();try_to_freeze();cond_resched();goto repeat;
}

三个重要的子函数:__spi_queued_transfe和__spi_pump_messages和kthread_queue_work

__spi_queued_transfer

把spi_message插入到ctlr->queue消息队列
然后调用kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);
推送work(pump_messages)插入到ctlr->kworker的list里面,然后启动线程

static int __spi_queued_transfer(struct spi_device *spi,struct spi_message *msg,bool need_pump)
{struct spi_controller *ctlr = spi->controller;unsigned long flags;spin_lock_irqsave(&ctlr->queue_lock, flags);if (!ctlr->running) {spin_unlock_irqrestore(&ctlr->queue_lock, flags);return -ESHUTDOWN;}msg->actual_length = 0;msg->status = -EINPROGRESS;list_add_tail(&msg->queue, &ctlr->queue);if (!ctlr->busy && need_pump)kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);spin_unlock_irqrestore(&ctlr->queue_lock, flags);return 0;
}

__spi_pump_messages

遍历list得到cur message
ctlr->cur_msg =
list_first_entry(&ctlr->queue, struct spi_message, queue);
然后调用硬件传输。
ret = ctlr->transfer_one_message(ctlr, ctlr->cur_msg);//

static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread)
{unsigned long flags;bool was_busy = false;int ret;/* Lock queue */spin_lock_irqsave(&ctlr->queue_lock, flags);/* Make sure we are not already running a message */if (ctlr->cur_msg) {spin_unlock_irqrestore(&ctlr->queue_lock, flags);return;}/* If another context is idling the device then defer */if (ctlr->idling) {kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);spin_unlock_irqrestore(&ctlr->queue_lock, flags);return;}/* Check if the queue is idle */if (list_empty(&ctlr->queue) || !ctlr->running) {if (!ctlr->busy) {spin_unlock_irqrestore(&ctlr->queue_lock, flags);return;}/* Only do teardown in the thread */if (!in_kthread) {kthread_queue_work(&ctlr->kworker,&ctlr->pump_messages);spin_unlock_irqrestore(&ctlr->queue_lock, flags);return;}ctlr->busy = false;ctlr->idling = true;spin_unlock_irqrestore(&ctlr->queue_lock, flags);kfree(ctlr->dummy_rx);ctlr->dummy_rx = NULL;kfree(ctlr->dummy_tx);ctlr->dummy_tx = NULL;if (ctlr->unprepare_transfer_hardware &&ctlr->unprepare_transfer_hardware(ctlr))dev_err(&ctlr->dev,"failed to unprepare transfer hardware\n");if (ctlr->auto_runtime_pm) {pm_runtime_mark_last_busy(ctlr->dev.parent);pm_runtime_put_autosuspend(ctlr->dev.parent);}trace_spi_controller_idle(ctlr);spin_lock_irqsave(&ctlr->queue_lock, flags);ctlr->idling = false;spin_unlock_irqrestore(&ctlr->queue_lock, flags);return;}/* Extract head of queue */ctlr->cur_msg =list_first_entry(&ctlr->queue, struct spi_message, queue);list_del_init(&ctlr->cur_msg->queue);if (ctlr->busy)was_busy = true;elsectlr->busy = true;spin_unlock_irqrestore(&ctlr->queue_lock, flags);mutex_lock(&ctlr->io_mutex);if (!was_busy && ctlr->auto_runtime_pm) {ret = pm_runtime_get_sync(ctlr->dev.parent);if (ret < 0) {dev_err(&ctlr->dev, "Failed to power device: %d\n",ret);mutex_unlock(&ctlr->io_mutex);return;}}if (!was_busy)trace_spi_controller_busy(ctlr);if (!was_busy && ctlr->prepare_transfer_hardware) {ret = ctlr->prepare_transfer_hardware(ctlr);if (ret) {dev_err(&ctlr->dev,"failed to prepare transfer hardware\n");if (ctlr->auto_runtime_pm)pm_runtime_put(ctlr->dev.parent);mutex_unlock(&ctlr->io_mutex);return;}}trace_spi_message_start(ctlr->cur_msg);if (ctlr->prepare_message) {ret = ctlr->prepare_message(ctlr, ctlr->cur_msg);if (ret) {dev_err(&ctlr->dev, "failed to prepare message: %d\n",ret);ctlr->cur_msg->status = ret;spi_finalize_current_message(ctlr);goto out;}ctlr->cur_msg_prepared = true;}ret = spi_map_msg(ctlr, ctlr->cur_msg);if (ret) {ctlr->cur_msg->status = ret;spi_finalize_current_message(ctlr);goto out;}ret = ctlr->transfer_one_message(ctlr, ctlr->cur_msg);//*[HTML]: 注释:
ctlr->transfer_one_message = spi_transfer_one_message
spi_transfer_one_message调用ctlr->transfer_one(ctlr, msg->spi, xfer);
master->transfer_one = spi_bitbang_transfer_one;
spi_bitbang_transfer_one最终调用 bitbang->txrx_bufs(spi, transfer);
spi_imx->bitbang.txrx_bufs = spi_imx_transfer;硬件接口if (ret) {dev_err(&ctlr->dev,"failed to transfer one message from queue\n");goto out;}out:mutex_unlock(&ctlr->io_mutex);/* Prod the scheduler in case transfer_one() was busy waiting */if (!ret)cond_resched();
}

kthread_queue_work

把work插入到worker->work_list里面,然后启动线程。

bool kthread_queue_work(struct kthread_worker *worker,struct kthread_work *work)
{bool ret = false;unsigned long flags;spin_lock_irqsave(&worker->lock, flags);if (!queuing_blocked(worker, work)) {kthread_insert_work(worker, work, &worker->work_list);把work插入到worker->work_list里面,然后启动线程。ret = true;}spin_unlock_irqrestore(&worker->lock, flags);return ret;
}

spi_sync

传输消息的主要函数
先把spi_message推送到master队列
然后推送work到workerlist
启动硬件传输

static int __spi_sync(struct spi_device *spi, struct spi_message *message)
{DECLARE_COMPLETION_ONSTACK(done);int status;struct spi_controller *ctlr = spi->controller;unsigned long flags;status = __spi_validate(spi, message);if (status != 0)return status;message->complete = spi_complete;message->context = &done;message->spi = spi;SPI_STATISTICS_INCREMENT_FIELD(&ctlr->statistics, spi_sync);SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync);/* If we're not using the legacy transfer method then we will* try to transfer in the calling context so special case.* This code would be less tricky if we could remove the* support for driver implemented message queues.*/if (ctlr->transfer == spi_queued_transfer) {spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags);trace_spi_message_submit(message);status = __spi_queued_transfer(spi, message, false)//先把work推送到worker,然后启动线程执行work->func(work); pump_messages()spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);} else {status = spi_async_locked(spi, message);}if (status == 0) {/* Push out the messages in the calling context if we* can.*/if (ctlr->transfer == spi_queued_transfer) {SPI_STATISTICS_INCREMENT_FIELD(&ctlr->statistics,spi_sync_immediate);SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics,spi_sync_immediate);__spi_pump_messages(ctlr, false);推送消息         }wait_for_completion(&done);status = message->status;}message->context = NULL;return status;
}

驱动例程


#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>#include <linux/platform_device.h>#include <linux/spi/spi.h>#include "ecspi_oled.h"/*------------------字符设备内容----------------------*/
#define DEV_NAME "ecspi_oled"
#define DEV_CNT (1)static dev_t oled_devno;      //定义字符设备的设备号
static struct cdev oled_chr_dev; //定义字符设备结构体chr_dev
struct class *class_oled;        //保存创建的类
struct device *device_oled;      // 保存创建的设备/*------------------IIC设备内容----------------------*/
struct spi_device *oled_spi_device = NULL; //保存oled设备对应的spi_device结构体,匹配成功后由.prob函数带回。
struct device_node *oled_device_node;      //ecspi_oled的设备树节点结构体
int oled_control_pin_number;               // 保存oled D/C控制引脚的引脚号(从对应设备树节点中获取)
u8 oled_init_data[] = {0xae, 0xae, 0x00, 0x10, 0x40,0x81, 0xcf, 0xa1, 0xc8, 0xa6,0xa8, 0x3f, 0xd3, 0x00, 0xd5,0x80, 0xd9, 0xf1, 0xda, 0x12,0xdb, 0x40, 0x20, 0x02, 0x8d,0x14, 0xa4, 0xa6, 0xaf};/*
*函数功能:向oled发送一个命令
*spi_device oled设备驱动对应的spi_device结构体。
*command  要发送的数据。
*返回值:成功,返回0 失败返回负数。
*/
static int oled_send_command(struct spi_device *spi_device, u8 command)
{int error = 0;u8 tx_data = command;struct spi_message *message;   //定义发送的消息struct spi_transfer *transfer; //定义传输结构体/*设置 D/C引脚为低电平*/gpio_direction_output(oled_control_pin_number, 0);/*申请空间*/message = kzalloc(sizeof(struct spi_message), GFP_KERNEL);transfer = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);/*填充message和transfer结构体*/transfer->tx_buf = &tx_data;transfer->len = 1;spi_message_init(message);spi_message_add_tail(transfer, message);error = spi_sync(spi_device, message);kfree(message);kfree(transfer);if (error != 0){printk("spi_sync error! \n");return -1;}gpio_direction_output(oled_control_pin_number, 1);return 0;
}/*
*函数功能:向oled发送一组命令
*spi_device oled设备驱动对应的spi_device结构体。
*commands  要发送的数据。
*返回值:成功,返回0 失败返回负数。
*/
static int oled_send_commands(struct spi_device *spi_device, u8 *commands, u16 lenght)
{int error = 0;struct spi_message *message;   //定义发送的消息struct spi_transfer *transfer; //定义传输结构体/*申请空间*/message = kzalloc(sizeof(struct spi_message), GFP_KERNEL);transfer = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);/*设置 D/C引脚为低电平*/gpio_direction_output(oled_control_pin_number, 0);/*填充message和transfer结构体*/transfer->tx_buf = commands;transfer->len = lenght;spi_message_init(message);spi_message_add_tail(transfer, message);error = spi_sync(spi_device, message);kfree(message);kfree(transfer);if (error != 0){printk("spi_sync error! \n");return -1;}return error;
}/*
*向 oled 发送一个字节
*spi_device,指定oled 设备驱动的spi 结构体
*data, 要发送数据
*/
static int oled_send_one_u8(struct spi_device *spi_device, u8 data)
{int error = 0;u8 tx_data = data;struct spi_message *message;   //定义发送的消息struct spi_transfer *transfer; //定义传输结构体/*设置 D/C引脚为高电平*/gpio_direction_output(oled_control_pin_number, 1);/*申请空间*/message = kzalloc(sizeof(struct spi_message), GFP_KERNEL);transfer = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);/*填充message和transfer结构体*/transfer->tx_buf = &tx_data;transfer->len = 1;spi_message_init(message);spi_message_add_tail(transfer, message);error = spi_sync(spi_device, message);kfree(message);kfree(transfer);if (error != 0){printk("spi_sync error! \n");return -1;}return 0;
}/*
*向 oled 发送数据
*spi_device,指定oled 设备驱动的spi 结构体
*data, 要发送数据的地址
*lenght,发送的数据长度
*/
static int oled_send_data(struct spi_device *spi_device, u8 *data, u16 lenght)
{int error = 0;int index = 0;struct spi_message *message;   //定义发送的消息struct spi_transfer *transfer; //定义传输结构体/*设置 D/C引脚为高电平*/gpio_direction_output(oled_control_pin_number, 1);/*申请空间*/message = kzalloc(sizeof(struct spi_message), GFP_KERNEL);transfer = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);/*每次发送 30字节,循环发送*/do{if (lenght > 30){transfer->tx_buf = data + index;transfer->len = 30;spi_message_init(message);spi_message_add_tail(transfer, message);index += 30;lenght -= 30;}else{transfer->tx_buf = data + index;transfer->len = lenght;spi_message_init(message);spi_message_add_tail(transfer, message);index += lenght;lenght = 0;}error = spi_sync(spi_device, message);if (error != 0){printk("spi_sync error! %d \n", error);return -1;}} while (lenght > 0);kfree(message);kfree(transfer);return 0;
}/*
*回环测试函数
*spi_device,指定oled 设备驱动的spi 结构体
*/
void loop_back_test(struct spi_device *spi_device)
{u8 tx_buffer[2] = {0x66, 0x77};u8 rx_buffer[2];struct spi_message *message;   //定义发送的消息struct spi_transfer *transfer; //定义传输结构体message = kzalloc(sizeof(struct spi_message), GFP_KERNEL);transfer = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);printk("message size=%d,  transfer=%d \n", sizeof(struct spi_message), sizeof(struct spi_transfer));transfer->tx_buf = tx_buffer;transfer->rx_buf = rx_buffer;transfer->len = 2;spi_message_init(message);              /* 初始化spi_message */spi_message_add_tail(transfer, message); /* 将spi_transfer添加到spi_message队列 */spi_sync(spi_device, message);          /* 同步发送 */printk("tx_buffer=%02X, %02X \n", tx_buffer[0], tx_buffer[1]);printk("rx_buffer=%02X, %02X \n", rx_buffer[0], rx_buffer[1]);kfree(message);kfree(transfer);
}/*
* 填充整个OLED 显示屏
* bmp_dat, 填充的值
*/
void oled_fill(unsigned char bmp_dat)
{u8 y, x;for (y = 0; y < 8; y++){oled_send_command(oled_spi_device, 0xb0 + y);oled_send_command(oled_spi_device, 0x01);oled_send_command(oled_spi_device, 0x10);// msleep(100);for (x = 0; x < 128; x++){oled_send_one_u8(oled_spi_device, bmp_dat);}}
}/*
*向 oled 发送要显示的数据,x, y 指定显示的起始位置,支持自动换行
*spi_device,指定oled 设备驱动的spi 结构体
*display_buffer, 数据地址
*x, y,起始坐标。
*length, 发送长度
*/
static int oled_display_buffer(u8 *display_buffer, u8 x, u8 y, u16 length)
{u16 index = 0;int error = 0;do{/*设置写入的起始坐标*/error += oled_send_command(oled_spi_device, 0xb0 + y);error += oled_send_command(oled_spi_device, ((x & 0xf0) >> 4) | 0x10);error += oled_send_command(oled_spi_device, (x & 0x0f) | 0x01);if (length > (X_WIDTH - x)){error += oled_send_data(oled_spi_device, display_buffer + index, X_WIDTH - x);length -= (X_WIDTH - x);index += (X_WIDTH - x);x = 0;y++;}else{error += oled_send_data(oled_spi_device, display_buffer + index, length);index += length;// x += length;length = 0;}} while (length > 0);if (error != 0){/*发送错误*/printk("oled_display_buffer error! %d \n",error);return -1;}return index;
}/*oled 初始化函数*/
void spi_oled_init(void)
{/*初始化oled*/oled_send_commands(oled_spi_device, oled_init_data, sizeof(oled_init_data));/*清屏*/oled_fill(0x00);
}/*字符设备操作函数集,open函数实现*/
static int oled_open(struct inode *inode, struct file *filp)
{spi_oled_init(); //初始化显示屏return 0;
}/*字符设备操作函数集,.write函数实现*/
static int oled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *off)
{int copy_number=0;/*申请内存*/oled_display_struct *write_data;write_data = (oled_display_struct*)kzalloc(cnt, GFP_KERNEL);copy_number = copy_from_user(write_data, buf,cnt);oled_display_buffer(write_data->display_buffer, write_data->x, write_data->y, write_data->length);/*释放内存*/kfree(write_data);return 0;
}/*字符设备操作函数集,.release函数实现*/
static int oled_release(struct inode *inode, struct file *filp)
{oled_send_command(oled_spi_device, 0xae);//关闭显示return 0;
}/*字符设备操作函数集*/
static struct file_operations oled_chr_dev_fops = {.owner = THIS_MODULE,.open = oled_open,.write = oled_write,.release = oled_release};/*----------------平台驱动函数集-----------------*/
static int oled_probe(struct spi_device *spi)
{int ret = -1; //保存错误状态码printk(KERN_EMERG "\t  match successed  \n");/*获取 ecspi_oled 设备树节点*/oled_device_node = of_find_node_by_path("/soc/aips-bus@2000000/spba-bus@2000000/ecspi@2008000/ecspi_oled@0");if (oled_device_node == NULL){printk(KERN_EMERG "\t  get ecspi_oled@0 failed!  \n");}/*获取 oled 的 D/C 控制引脚并设置为输出,默认高电平*/oled_control_pin_number = of_get_named_gpio(oled_device_node, "d_c_control_pin", 0);printk("oled_control_pin_number = %d,\n ", oled_control_pin_number);gpio_direction_output(oled_control_pin_number, 1);/*初始化spi*/oled_spi_device = spi;oled_spi_device->mode = SPI_MODE_0;oled_spi_device->max_speed_hz = 2000000;spi_setup(oled_spi_device);/*---------------------注册 字符设备部分-----------------*///采用动态分配的方式,获取设备编号,次设备号为0,//设备名称为rgb-leds,可通过命令cat  /proc/devices查看//DEV_CNT为1,当前只申请一个设备编号ret = alloc_chrdev_region(&oled_devno, 0, DEV_CNT, DEV_NAME);if (ret < 0){printk("fail to alloc oled_devno\n");goto alloc_err;}//关联字符设备结构体cdev与文件操作结构体file_operationsoled_chr_dev.owner = THIS_MODULE;cdev_init(&oled_chr_dev, &oled_chr_dev_fops);// 添加设备至cdev_map散列表中ret = cdev_add(&oled_chr_dev, oled_devno, DEV_CNT);if (ret < 0){printk("fail to add cdev\n");goto add_err;}/*创建类 */class_oled = class_create(THIS_MODULE, DEV_NAME);/*创建设备 DEV_NAME 指定设备名,*/device_oled = device_create(class_oled, NULL, oled_devno, NULL, DEV_NAME);/*打印oled_spi_device 部分内容*/printk("max_speed_hz = %d\n", oled_spi_device->max_speed_hz);printk("chip_select = %d\n", (int)oled_spi_device->chip_select);printk("bits_per_word = %d\n", (int)oled_spi_device->bits_per_word);printk("mode = %02X\n", oled_spi_device->mode);printk("cs_gpio = %02X\n", oled_spi_device->cs_gpio);return 0;add_err:// 添加设备失败时,需要注销设备号unregister_chrdev_region(oled_devno, DEV_CNT);printk("\n error! \n");
alloc_err:return -1;
}static int oled_remove(struct spi_device *spi)
{/*删除设备*/device_destroy(class_oled, oled_devno);           //清除设备class_destroy(class_oled);                    //清除类cdev_del(&oled_chr_dev);                       //清除设备号unregister_chrdev_region(oled_devno, DEV_CNT); //取消注册字符设备return 0;
}/*指定 ID 匹配表*/
static const struct spi_device_id oled_device_id[] = {{"fire,ecspi_oled", 0},{}};/*指定设备树匹配表*/
static const struct of_device_id oled_of_match_table[] = {{.compatible = "fire,ecspi_oled"},{}};/*spi 总线设备结构体*/
struct spi_driver oled_driver = {.probe = oled_probe,.remove = oled_remove,.id_table = oled_device_id,.driver = {.name = "ecspi_oled",.owner = THIS_MODULE,.of_match_table = oled_of_match_table,},
};/*
*驱动初始化函数
*/
static int __init oled_driver_init(void)
{int error;pr_info("oled_driver_init\n");error = spi_register_driver(&oled_driver);return error;
}/*
*驱动注销函数
*/
static void __exit oled_driver_exit(void)
{pr_info("oled_driver_exit\n");spi_unregister_driver(&oled_driver);
}module_init(oled_driver_init);
module_exit(oled_driver_exit);MODULE_LICENSE("GPL");//MISO----CSI_DATA07  MX6UL_PAD_CSI_DATA07__ECSPI1_MISO  0x10b0
//MOSI----CSI_DATA06  MX6UL_PAD_CSI_DATA06__ECSPI1_MOSI  0x10b0
//SS0-----CSI_DATA05  MX6UL_PAD_CSI_DATA05__ECSPI1_SS0   0x10b0
//SCLK----CSI_DATA04  MX6UL_PAD_CSI_DATA04__ECSPI1_SCLK  0x10b0
//D/C-----CSI_DATA03  MX6UL_PAD_CSI_DATA03__GPIO4_IO24   0x10b0

i2c驱动详解

i2c adapter字符设备的创建

static int __init i2c_dev_init(void)
{int res;printk(KERN_INFO "i2c /dev entries driver\n");res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");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);//在此创建了adapter的字符设备return 0;out_unreg_class:class_destroy(i2c_dev_class);
out_unreg_chrdev:unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);return res;
}

imx-i2c注册设备

主要创建adapter适配器
初始化了adapter的algo等

static int i2c_imx_probe(struct platform_device *pdev)
{const struct of_device_id *of_id = of_match_device(i2c_imx_dt_ids,&pdev->dev);struct imx_i2c_struct *i2c_imx;struct resource *res;struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev);void __iomem *base;int irq, ret;dma_addr_t phy_addr;dev_dbg(&pdev->dev, "<%s>\n", __func__);irq = platform_get_irq(pdev, 0);if (irq < 0) {dev_err(&pdev->dev, "can't get irq number\n");return irq;}res = platform_get_resource(pdev, IORESOURCE_MEM, 0);base = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(base))return PTR_ERR(base);phy_addr = (dma_addr_t)res->start;i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL);if (!i2c_imx)return -ENOMEM;if (of_id)i2c_imx->hwdata = of_id->data;elsei2c_imx->hwdata = (struct imx_i2c_hwdata *)platform_get_device_id(pdev)->driver_data;/* Setup i2c_imx driver structure */strlcpy(i2c_imx->adapter.name, pdev->name, sizeof(i2c_imx->adapter.name));i2c_imx->adapter.owner        = THIS_MODULE;i2c_imx->adapter.algo        = &i2c_imx_algo;i2c_imx->adapter.dev.parent    = &pdev->dev;i2c_imx->adapter.nr        = pdev->id;i2c_imx->adapter.dev.of_node    = pdev->dev.of_node;i2c_imx->base            = base;/* Get I2C clock */i2c_imx->clk = devm_clk_get(&pdev->dev, NULL);if (IS_ERR(i2c_imx->clk)) {dev_err(&pdev->dev, "can't get I2C clock\n");return PTR_ERR(i2c_imx->clk);}ret = clk_prepare_enable(i2c_imx->clk);if (ret) {dev_err(&pdev->dev, "can't enable I2C clock, ret=%d\n", ret);return ret;}/* Request IRQ */ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr, IRQF_SHARED,pdev->name, i2c_imx);if (ret) {dev_err(&pdev->dev, "can't claim irq %d\n", irq);goto clk_disable;}/* Init queue */init_waitqueue_head(&i2c_imx->queue);/* Set up adapter data */i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);/* Set up platform driver data */platform_set_drvdata(pdev, i2c_imx);pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_TIMEOUT);pm_runtime_use_autosuspend(&pdev->dev);pm_runtime_set_active(&pdev->dev);pm_runtime_enable(&pdev->dev);ret = pm_runtime_get_sync(&pdev->dev);if (ret < 0)goto rpm_disable;/* Set up clock divider */i2c_imx->bitrate = IMX_I2C_BIT_RATE;ret = of_property_read_u32(pdev->dev.of_node,"clock-frequency", &i2c_imx->bitrate);if (ret < 0 && pdata && pdata->bitrate)i2c_imx->bitrate = pdata->bitrate;/* Set up chip registers to defaults */imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,i2c_imx, IMX_I2C_I2CR);imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR);/* Init optional bus recovery function */ret = i2c_imx_init_recovery_info(i2c_imx, pdev);/* Give it another chance if pinctrl used is not ready yet */if (ret == -EPROBE_DEFER)goto rpm_disable;/* Add I2C adapter */ret = i2c_add_numbered_adapter(&i2c_imx->adapter);//最重要的创建了adapterif (ret < 0)goto rpm_disable;pm_runtime_mark_last_busy(&pdev->dev);pm_runtime_put_autosuspend(&pdev->dev);dev_dbg(&i2c_imx->adapter.dev, "claimed irq %d\n", irq);dev_dbg(&i2c_imx->adapter.dev, "device resources: %pR\n", res);dev_dbg(&i2c_imx->adapter.dev, "adapter name: \"%s\"\n",i2c_imx->adapter.name);dev_info(&i2c_imx->adapter.dev, "IMX I2C adapter registered\n");/* Init DMA config if supported */i2c_imx_dma_request(i2c_imx, phy_addr);return 0;   /* Return OK */rpm_disable:pm_runtime_put_noidle(&pdev->dev);pm_runtime_disable(&pdev->dev);pm_runtime_set_suspended(&pdev->dev);pm_runtime_dont_use_autosuspend(&pdev->dev);clk_disable:clk_disable_unprepare(i2c_imx->clk);return ret;
}

i2c_register_adapter

创建了一个挂载在i2c_bus_type的dev,类型为i2c_adapter_type

static int i2c_register_adapter(struct i2c_adapter *adap)
{int res = -EINVAL;/* Can't register until after driver model init */if (WARN_ON(!is_registered)) {res = -EAGAIN;goto out_list;}/* Sanity checks */if (WARN(!adap->name[0], "i2c adapter has no name"))goto out_list;if (!adap->algo) {pr_err("adapter '%s': no algo supplied!\n", adap->name);goto out_list;}if (!adap->lock_ops)adap->lock_ops = &i2c_adapter_lock_ops;rt_mutex_init(&adap->bus_lock);rt_mutex_init(&adap->mux_lock);mutex_init(&adap->userspace_clients_lock);INIT_LIST_HEAD(&adap->userspace_clients);/* Set default timeout to 1 second if not already set */if (adap->timeout == 0)adap->timeout = HZ;/* register soft irqs for Host Notify */res = i2c_setup_host_notify_irq_domain(adap);if (res) {pr_err("adapter '%s': can't create Host Notify IRQs (%d)\n",adap->name, res);goto out_list;}dev_set_name(&adap->dev, "i2c-%d", adap->nr);adap->dev.bus = &i2c_bus_type;adap->dev.type = &i2c_adapter_type;res = device_register(&adap->dev);if (res) {pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);goto out_list;}res = of_i2c_setup_smbus_alert(adap);if (res)goto out_reg;dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);pm_runtime_no_callbacks(&adap->dev);pm_suspend_ignore_children(&adap->dev, true);pm_runtime_enable(&adap->dev);#ifdef CONFIG_I2C_COMPATres = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,adap->dev.parent);if (res)dev_warn(&adap->dev,"Failed to create compatibility class link\n");
#endifi2c_init_recovery(adap);/* create pre-declared device nodes */of_i2c_register_devices(adap);i2c_acpi_register_devices(adap);i2c_acpi_install_space_handler(adap);if (adap->nr < __i2c_first_dynamic_bus_num)i2c_scan_static_board_info(adap);/* Notify drivers */mutex_lock(&core_lock);bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);mutex_unlock(&core_lock);return 0;out_reg:init_completion(&adap->dev_released);device_unregister(&adap->dev);wait_for_completion(&adap->dev_released);
out_list:mutex_lock(&core_lock);idr_remove(&i2c_adapter_idr, adap->nr);mutex_unlock(&core_lock);return res;
}

如何使用i2c adapter

第一步:/定义设备树匹配表/
static const struct of_device_id mpu6050_of_match_table[] = {
{.compatible = “fire,i2c_mpu6050”},
{/* sentinel */}};
第二步:/定义i2c总线设备结构体/
struct i2c_driver mpu6050_driver = {
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.id_table = gtp_device_id,
.driver = {
.name = “fire,i2c_mpu6050”,
.owner = THIS_MODULE,
.of_match_table = mpu6050_of_match_table,
},
};
第三步:调用接口注册驱动
ret = i2c_add_driver(&mpu6050_driver);

i2c_add_driver使用原理

i2c_register_driver 创建dev的client,挂载到i2c_bus_type,并且绑定apapter
然后再注册驱动的时候会调用i2c_bus_type的i2c_device_probe函数,通过dev拿到i2c_client和driver,把client传入到driver->probe,就可以使用adapter了。

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{int res;/* Can't register until after driver model init */if (WARN_ON(!is_registered))return -EAGAIN;/* add the driver to the list of i2c drivers in the driver core */driver->driver.owner = owner;driver->driver.bus = &i2c_bus_type;INIT_LIST_HEAD(&driver->clients);/* When registration returns, the driver core* will have called probe() for all matching-but-unbound devices.*/res = driver_register(&driver->driver);if (res)return res;pr_debug("driver [%s] registered\n", driver->driver.name);/* Walk the adapters that are already present */i2c_for_each_dev(driver, __process_new_driver);遍历挂载在i2c_bus_type上的dev,然后执行__process_new_driverreturn 0;
}
int bus_for_each_dev(struct bus_type *bus, struct device *start,void *data, int (*fn)(struct device *, void *))
{struct klist_iter i;struct device *dev;int error = 0;if (!bus || !bus->p)return -EINVAL;klist_iter_init_node(&bus->p->klist_devices, &i,(start ? &start->p->knode_bus : NULL));while (!error && (dev = next_device(&i)))error = fn(dev, data);klist_iter_exit(&i);return error;
}
static int __process_new_driver(struct device *dev, void *data)
{if (dev->type != &i2c_adapter_type)return 0;//在这里找到i2c_adapter_type的设备,to_i2c_adapter(dev)找到adapterreturn i2c_do_add_adapter(data, to_i2c_adapter(dev));
}
创建设备并且赋值adapter
static int i2c_do_add_adapter(struct i2c_driver *driver,struct i2c_adapter *adap)
{/* Detect supported devices on that bus, and instantiate them */i2c_detect(adap, driver);/* Let legacy drivers scan this bus for matching devices */if (driver->attach_adapter) {dev_warn(&adap->dev, "%s: attach_adapter method is deprecated\n",driver->driver.name);dev_warn(&adap->dev,"Please use another way to instantiate your i2c_client\n");/* We ignore the return code; if it fails, too bad */driver->attach_adapter(adap);}return 0;
}
创建temp_client,赋值adapter
static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{const unsigned short *address_list;struct i2c_client *temp_client;int i, err = 0;int adap_id = i2c_adapter_id(adapter);address_list = driver->address_list;if (!driver->detect || !address_list)return 0;/* Warn that the adapter lost class based instantiation */if (adapter->class == I2C_CLASS_DEPRECATED) {dev_dbg(&adapter->dev,"This adapter dropped support for I2C classes and won't auto-detect %s devices anymore. ""If you need it, check 'Documentation/i2c/instantiating-devices' for alternatives.\n",driver->driver.name);return 0;}/* Stop here if the classes do not match */if (!(adapter->class & driver->class))return 0;/* Set up a temporary client to help detect callback */temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);if (!temp_client)return -ENOMEM;temp_client->adapter = adapter;for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {dev_dbg(&adapter->dev,"found normal entry for adapter %d, addr 0x%02x\n",adap_id, address_list[i]);temp_client->addr = address_list[i];err = i2c_detect_address(temp_client, driver);if (unlikely(err))break;}kfree(temp_client);return err;
}
//通过client = i2c_new_device(adapter, &info);创建i2c_bus_type设备,类型为i2c_client_type
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{struct i2c_client  *client;int         status;client = kzalloc(sizeof *client, GFP_KERNEL);if (!client)return NULL;client->adapter = adap;client->dev.platform_data = info->platform_data;if (info->archdata)client->dev.archdata = *info->archdata;client->flags = info->flags;client->addr = info->addr;client->irq = info->irq;if (!client->irq)client->irq = i2c_dev_irq_from_resources(info->resources,info->num_resources);strlcpy(client->name, info->type, sizeof(client->name));status = i2c_check_addr_validity(client->addr, client->flags);if (status) {dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);goto out_err_silent;}/* Check for address business */status = i2c_check_addr_busy(adap, i2c_encode_flags_to_addr(client));if (status)goto out_err;client->dev.parent = &client->adapter->dev;client->dev.bus = &i2c_bus_type;client->dev.type = &i2c_client_type;client->dev.of_node = info->of_node;client->dev.fwnode = info->fwnode;i2c_dev_set_name(adap, client, info);if (info->properties) {status = device_add_properties(&client->dev, info->properties);if (status) {dev_err(&adap->dev,"Failed to add properties to client %s: %d\n",client->name, status);goto out_err;}}status = device_register(&client->dev);if (status)goto out_free_props;dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",client->name, dev_name(&client->dev));return client;out_free_props:if (info->properties)device_remove_properties(&client->dev);
out_err:dev_err(&adap->dev,"Failed to register i2c client %s at 0x%02x (%d)\n",client->name, client->addr, status);
out_err_silent:kfree(client);return NULL;
}

如何找到client

通过i2c_bus_type的prob函数,拿到client和driver,然后执行driver的probe函数,把client带入,此时client的dev的adapater可以使用了。

struct bus_type i2c_bus_type = {.name       = "i2c",.match       = i2c_device_match,.probe      = i2c_device_probe,.remove     = i2c_device_remove,.shutdown  = i2c_device_shutdown,
};
static int i2c_device_probe(struct device *dev)
{struct i2c_client  *client = i2c_verify_client(dev);struct i2c_driver *driver;int status;if (!client)return 0;driver = to_i2c_driver(dev->driver);if (!client->irq && !driver->disable_i2c_core_irq_mapping) {int irq = -ENOENT;if (client->flags & I2C_CLIENT_HOST_NOTIFY) {dev_dbg(dev, "Using Host Notify IRQ\n");irq = i2c_smbus_host_notify_to_irq(client);} else if (dev->of_node) {irq = of_irq_get_byname(dev->of_node, "irq");if (irq == -EINVAL || irq == -ENODATA)irq = of_irq_get(dev->of_node, 0);} else if (ACPI_COMPANION(dev)) {irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 0);}if (irq == -EPROBE_DEFER)return irq;if (irq < 0)irq = 0;client->irq = irq;}/** An I2C ID table is not mandatory, if and only if, a suitable OF* or ACPI ID table is supplied for the probing device.*/if (!driver->id_table &&!i2c_acpi_match_device(dev->driver->acpi_match_table, client) &&!i2c_of_match_device(dev->driver->of_match_table, client))return -ENODEV;if (client->flags & I2C_CLIENT_WAKE) {int wakeirq = -ENOENT;if (dev->of_node) {wakeirq = of_irq_get_byname(dev->of_node, "wakeup");if (wakeirq == -EPROBE_DEFER)return wakeirq;}device_init_wakeup(&client->dev, true);if (wakeirq > 0 && wakeirq != client->irq)status = dev_pm_set_dedicated_wake_irq(dev, wakeirq);else if (client->irq > 0)status = dev_pm_set_wake_irq(dev, client->irq);elsestatus = 0;if (status)dev_warn(&client->dev, "failed to set up wakeup irq\n");}dev_dbg(dev, "probe\n");status = of_clk_set_defaults(dev->of_node, false);if (status < 0)goto err_clear_wakeup_irq;status = dev_pm_domain_attach(&client->dev, true);if (status == -EPROBE_DEFER)goto err_clear_wakeup_irq;/** When there are no more users of probe(),* rename probe_new to probe.*/if (driver->probe_new)status = driver->probe_new(client);else if (driver->probe)status = driver->probe(client,i2c_match_id(driver->id_table, client));elsestatus = -EINVAL;if (status)goto err_detach_pm_domain;return 0;err_detach_pm_domain:dev_pm_domain_detach(&client->dev, true);
err_clear_wakeup_irq:dev_pm_clear_wake_irq(&client->dev);device_init_wakeup(&client->dev, false);return status;
}

i2c驱动实例


#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/i2c.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>#include <linux/platform_device.h>#include "i2c_mpu6050.h"/*------------------字符设备内容----------------------*/
#define DEV_NAME "I2C1_mpu6050"
#define DEV_CNT (1)/*定义 led 资源结构体,保存获取得到的节点信息以及转换后的虚拟寄存器地址*/
static dev_t mpu6050_devno;              //定义字符设备的设备号
static struct cdev mpu6050_chr_dev;      //定义字符设备结构体chr_dev
struct class *class_mpu6050;             //保存创建的类
struct device *device_mpu6050;           // 保存创建的设备
struct device_node *mpu6050_device_node; //rgb_led的设备树节点结构体/*------------------IIC设备内容----------------------*/
struct i2c_client *mpu6050_client = NULL; //保存mpu6050设备对应的i2c_client结构体,匹配成功后由.prob函数带回。/*通过i2c 向mpu6050写入数据
*mpu6050_client:mpu6050的i2c_client结构体。
*address, 数据要写入的地址,
*data, 要写入的数据
*返回值,错误,-1。成功,0
*/
static int i2c_write_mpu6050(struct i2c_client *mpu6050_client, u8 address, u8 data)
{int error = 0;u8 write_data[2];struct i2c_msg send_msg; //要发送的数据结构体/*设置要发送的数据*/write_data[0] = address;write_data[1] = data;/*发送 iic要写入的地址 reg*/send_msg.addr = mpu6050_client->addr; //mpu6050在 iic 总线上的地址send_msg.flags = 0;                   //标记为发送数据send_msg.buf = write_data;            //写入的首地址send_msg.len = 2;                      //reg长度/*执行发送*/error = i2c_transfer(mpu6050_client->adapter, &send_msg, 1);if (error != 1){printk(KERN_DEBUG "\n i2c_transfer error \n");return -1;}return 0;
}/*通过i2c 向mpu6050写入数据
*mpu6050_client:mpu6050的i2c_client结构体。
*address, 要读取的地址,
*data,保存读取得到的数据
*length,读长度
*返回值,错误,-1。成功,0
*/
static int i2c_read_mpu6050(struct i2c_client *mpu6050_client, u8 address, void *data, u32 length)
{int error = 0;u8 address_data = address;struct i2c_msg mpu6050_msg[2];/*设置读取位置msg*/mpu6050_msg[0].addr = mpu6050_client->addr; //mpu6050在 iic 总线上的地址mpu6050_msg[0].flags = 0;                   //标记为发送数据mpu6050_msg[0].buf = &address_data;           //写入的首地址mpu6050_msg[0].len = 1;                        //写入长度/*设置读取位置msg*/mpu6050_msg[1].addr = mpu6050_client->addr; //mpu6050在 iic 总线上的地址mpu6050_msg[1].flags = I2C_M_RD;           //标记为读取数据mpu6050_msg[1].buf = data;                    //读取得到的数据保存位置mpu6050_msg[1].len = length;              //读取长度error = i2c_transfer(mpu6050_client->adapter, mpu6050_msg, 2);if (error != 2){printk(KERN_DEBUG "\n i2c_read_mpu6050 error \n");return -1;}return 0;
}/*初始化i2c
*返回值,成功,返回0。失败,返回 -1
*/
static int mpu6050_init(void)
{int error = 0;/*配置mpu6050*/error += i2c_write_mpu6050(mpu6050_client, PWR_MGMT_1, 0X00);error += i2c_write_mpu6050(mpu6050_client, SMPLRT_DIV, 0X07);error += i2c_write_mpu6050(mpu6050_client, CONFIG, 0X06);error += i2c_write_mpu6050(mpu6050_client, ACCEL_CONFIG, 0X01);if (error < 0){/*初始化错误*/printk(KERN_DEBUG "\n mpu6050_init error \n");return -1;}return 0;
}/*字符设备操作函数集,open函数实现*/
static int mpu6050_open(struct inode *inode, struct file *filp)
{// printk("\n mpu6050_open \n");/*向 mpu6050 发送配置数据,让mpu6050处于正常工作状态*/mpu6050_init();return 0;
}/*字符设备操作函数集,.read函数实现*/
static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{char data_H;char data_L;int error;short mpu6050_result[6]; //保存mpu6050转换得到的原始数据// printk("\n mpu6050_read \n");i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_L, &data_L, 1);mpu6050_result[0] = data_H << 8;mpu6050_result[0] += data_L;i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_L, &data_L, 1);mpu6050_result[1] = data_H << 8;mpu6050_result[1] += data_L;i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_L, &data_L, 1);mpu6050_result[2] = data_H << 8;mpu6050_result[2] += data_L;i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_L, &data_L, 1);mpu6050_result[3] = data_H << 8;mpu6050_result[3] += data_L;i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_L, &data_L, 1);mpu6050_result[4] = data_H << 8;mpu6050_result[4] += data_L;i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_L, &data_L, 1);mpu6050_result[5] = data_H << 8;mpu6050_result[5] += data_L;// printk("AX=%d, AY=%d, AZ=%d \n",(int)mpu6050_result[0],(int)mpu6050_result[1],(int)mpu6050_result[2]);// printk("GX=%d, GY=%d, GZ=%d \n \n",(int)mpu6050_result[3],(int)mpu6050_result[4],(int)mpu6050_result[5]);/*将读取得到的数据拷贝到用户空间*/error = copy_to_user(buf, mpu6050_result, cnt);if(error != 0){printk("copy_to_user error!");return -1;}return 0;
}/*字符设备操作函数集,.release函数实现*/
static int mpu6050_release(struct inode *inode, struct file *filp)
{// printk("\n mpu6050_release \n");/*向mpu6050发送命令,使mpu6050进入关机状态*/return 0;
}/*字符设备操作函数集*/
static struct file_operations mpu6050_chr_dev_fops ={.owner = THIS_MODULE,.open = mpu6050_open,.read = mpu6050_read,.release = mpu6050_release,
};/*----------------平台驱动函数集-----------------*/
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{int ret = -1; //保存错误状态码printk(KERN_EMERG "\t  match successed  \n");/*---------------------注册 字符设备部分-----------------*///采用动态分配的方式,获取设备编号,次设备号为0,//设备名称为rgb-leds,可通过命令cat  /proc/devices查看//DEV_CNT为1,当前只申请一个设备编号ret = alloc_chrdev_region(&mpu6050_devno, 0, DEV_CNT, DEV_NAME);if (ret < 0){printk("fail to alloc mpu6050_devno\n");goto alloc_err;}//关联字符设备结构体cdev与文件操作结构体file_operationsmpu6050_chr_dev.owner = THIS_MODULE;cdev_init(&mpu6050_chr_dev, &mpu6050_chr_dev_fops);// 添加设备至cdev_map散列表中ret = cdev_add(&mpu6050_chr_dev, mpu6050_devno, DEV_CNT);if (ret < 0){printk("fail to add cdev\n");goto add_err;}/*创建类 */class_mpu6050 = class_create(THIS_MODULE, DEV_NAME);/*创建设备 DEV_NAME 指定设备名,*/device_mpu6050 = device_create(class_mpu6050, NULL, mpu6050_devno, NULL, DEV_NAME);mpu6050_client = client;return 0;add_err:// 添加设备失败时,需要注销设备号unregister_chrdev_region(mpu6050_devno, DEV_CNT);printk("\n error! \n");
alloc_err:return -1;
}static int mpu6050_remove(struct i2c_client *client)
{/*删除设备*/device_destroy(class_mpu6050, mpu6050_devno);    //清除设备class_destroy(class_mpu6050);                     //清除类cdev_del(&mpu6050_chr_dev);                        //清除设备号unregister_chrdev_region(mpu6050_devno, DEV_CNT); //取消注册字符设备return 0;
}/*定义ID 匹配表*/
static const struct i2c_device_id gtp_device_id[] = {{"fire,i2c_mpu6050", 0},{}};/*定义设备树匹配表*/
static const struct of_device_id mpu6050_of_match_table[] = {{.compatible = "fire,i2c_mpu6050"},{/* sentinel */}};/*定义i2c总线设备结构体*/
struct i2c_driver mpu6050_driver = {.probe = mpu6050_probe,.remove = mpu6050_remove,.id_table = gtp_device_id,.driver = {.name = "fire,i2c_mpu6050",.owner = THIS_MODULE,.of_match_table = mpu6050_of_match_table,},
};/*
*驱动初始化函数
*/
static int __init mpu6050_driver_init(void)
{int ret;pr_info("mpu6050_driver_init\n");ret = i2c_add_driver(&mpu6050_driver);return ret;
}/*
*驱动注销函数
*/
static void __exit mpu6050_driver_exit(void)
{pr_info("mpu6050_driver_exit\n");i2c_del_driver(&mpu6050_driver);
}module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);MODULE_LICENSE("GPL");

LCD驱动

mxsfb_probe

mxsfb.c

static int mxsfb_probe(struct platform_device *pdev)
{const struct of_device_id *of_id =of_match_device(mxsfb_dt_ids, &pdev->dev);struct resource *res;struct mxsfb_info *host;struct fb_info *fb_info;struct fb_videomode *mode;int ret;if (of_id)pdev->id_entry = of_id->data;fb_info = framebuffer_alloc(sizeof(struct mxsfb_info), &pdev->dev);//fb_info最重要的结构体if (!fb_info) {dev_err(&pdev->dev, "Failed to allocate fbdev\n");return -ENOMEM;}mode = devm_kzalloc(&pdev->dev, sizeof(struct fb_videomode),GFP_KERNEL);if (mode == NULL)return -ENOMEM;host = fb_info->par;res = platform_get_resource(pdev, IORESOURCE_MEM, 0);host->base = devm_ioremap_resource(&pdev->dev, res);//lcd寄存器基地址if (IS_ERR(host->base)) {ret = PTR_ERR(host->base);goto fb_release;}host->pdev = pdev;platform_set_drvdata(pdev, host);host->devdata = &mxsfb_devdata[pdev->id_entry->driver_data];host->clk = devm_clk_get(&host->pdev->dev, NULL);if (IS_ERR(host->clk)) {ret = PTR_ERR(host->clk);goto fb_release;}host->clk_axi = devm_clk_get(&host->pdev->dev, "axi");if (IS_ERR(host->clk_axi))host->clk_axi = NULL;host->clk_disp_axi = devm_clk_get(&host->pdev->dev, "disp_axi");if (IS_ERR(host->clk_disp_axi))host->clk_disp_axi = NULL;host->reg_lcd = devm_regulator_get(&pdev->dev, "lcd");if (IS_ERR(host->reg_lcd))host->reg_lcd = NULL;fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16,GFP_KERNEL);if (!fb_info->pseudo_palette) {ret = -ENOMEM;goto fb_release;}ret = mxsfb_init_fbinfo(fb_info, mode);if (ret != 0)goto fb_release;fb_videomode_to_var(&fb_info->var, mode);/* init the color fields */mxsfb_check_var(&fb_info->var, fb_info);platform_set_drvdata(pdev, fb_info);ret = register_framebuffer(fb_info);//注册framebufferif (ret != 0) {dev_err(&pdev->dev,"Failed to register framebuffer\n");goto fb_destroy;}if (!host->enabled) {mxsfb_enable_axi_clk(host);writel(0, host->base + LCDC_CTRL);mxsfb_disable_axi_clk(host);mxsfb_set_par(fb_info);mxsfb_enable_controller(fb_info);}dev_info(&pdev->dev, "initialized\n");return 0;fb_destroy:if (host->enabled)clk_disable_unprepare(host->clk);
fb_release:framebuffer_release(fb_info);return ret;
}

创建字符设备

fbmem.c

static int __init
fbmem_init(void)
{int ret;if (!proc_create("fb", 0, NULL, &fb_proc_fops))return -ENOMEM;ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);if (ret) {printk("unable to get major %d for fb devs\n", FB_MAJOR);goto err_chrdev;}fb_class = class_create(THIS_MODULE, "graphics");if (IS_ERR(fb_class)) {ret = PTR_ERR(fb_class);pr_warn("Unable to create fb class; errno = %d\n", ret);fb_class = NULL;goto err_class;}fb_console_init();return 0;err_class:unregister_chrdev(FB_MAJOR, "fb");
err_chrdev:remove_proc_entry("fb", NULL);return ret;
}#ifdef MODULE
module_init(fbmem_init);
static void __exit
fbmem_exit(void)
{fb_console_exit();remove_proc_entry("fb", NULL);class_destroy(fb_class);unregister_chrdev(FB_MAJOR, "fb");
}module_exit(fbmem_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Framebuffer base");
#else
subsys_initcall(fbmem_init);
#endif

imx6ul 驱动详解相关推荐

  1. 使用VS2010编译MongoDB C++驱动详解

    最近为了解决IM消息记录的高速度写入.多文档类型支持的需求,决定使用MongoDB来解决. 考虑到MongoDB对VS版本要求较高,与我现有的VS版本不兼容,在leveldb.ssdb.redis.h ...

  2. Pixhawk(PX4)之驱动详解篇(0)_前期准备(招贤令)

    Pixhawk(PX4)之驱动详解篇(0)_前期准备(招贤令) 原创 2017年03月01日 22:58:39 标签: 开发人员 / UAV / 软件 / 硬件 一.开篇 开源精神常在!!! 谁说软件 ...

  3. MTK 驱动(64)---Mtk touch panel驱动/TP驱动详解

    Mtk touch panel驱动/TP驱动详解 TP还算是比LCM好理解的多. 在启动过程中,先注册/mediatek/custom/command/kernel/touchpanel目录下的具体驱 ...

  4. linux usb gadget驱动详解(一)

    由于PC的推广,USB(通用串行总线)是我们最熟知的通信总线规范之一,其他的还有诸如以太网.PCIE总线和RS232串口等.这里我们主要讨论USB. USB是一个主从通信架构,但只能一主多从.其中us ...

  5. Z-STACK之cc2530LED驱动详解

    Z-STACK 之LED驱动详解      最近一段时间学习ZigBee,用的TI公司的cc2530,协议栈是z-stack,为了深入了解整个Z-stack,我从底层的驱动代码开始看起,首先是LED驱 ...

  6. 博通wifi驱动详解

    1        WLAN技术 WLAN是英文WirelessLAN的缩写,就是无线局域网的意思.无线以太网技术是一种基于无线传输的局域网技术,与有线网络技术相比,具有灵活.建网迅速.个人化等特点.将 ...

  7. LCD液晶屏驱动详解

    开发环境: 开发板:JZ2440V3 CPU:samsunS3C2440 内核:Linux3.4.2 编译工具:arm-linux-gcc 4.3.2 LCD:4.3存液晶屏AT043TN24 参考文 ...

  8. nvme 驱动详解[转]

    nvme 驱动详解 之1 http://blog.csdn.net/qqqqqq999999/article/details/47732319 首先打开driver/block下的kconfig文件, ...

  9. Linux-hexdump命令调试event驱动—详解(13)

    2018-01-03阅读 6300 hexdump: 查看文件的内容,比如二进制文件中包含的某些字符串,通常用来调试驱动用 描述: 我们以event1为例,当我们insmod挂载了键盘驱动后,出现一个 ...

最新文章

  1. 如何设置MathType下标的正斜体
  2. Excel 技术篇-跨页签统计某一区域下符合条件值的数量方法,COUNTIF函数、数量统计公式的用法实例演示
  3. springboot获取原生js请求_七节课带你学会SpringBoot,第三课
  4. 用python爬取淘宝用户数据的单位是_国内有没有数据爬取方面的公司?
  5. PHP模板引擎smarty详细介绍
  6. Echarts pie 饼图类型后显示数据
  7. python items() 函数的使用(一分钟读懂)
  8. golang errors 取 错误 信息_golang-标准errors包的学习
  9. 【Tips小技巧】电脑全屏截图网页滚动截图
  10. FLASH透明背景代码大全
  11. fseek(f,0,SEEK_SET);
  12. idea工具首次提交代码到git上
  13. 手把手教你如何使用Unity搭建简易图片服务器
  14. 程序员必备之沟通的艺术
  15. MBSE系统工程是什么
  16. 关于BH1750的使用说明
  17. 最小连续m个合数---枚举
  18. Vue中获取当前点击元素的父元素、子元素、兄弟元素
  19. 互联网金融平台常见绑卡鉴权方式分析对比
  20. PT 基于Multi Voltage的Physical Aware

热门文章

  1. 大学计算机基础教学目标,大学计算机基础教学大纲2015.doc
  2. 单片机2017福建省中职省赛_2017年福建省职业院校技能大赛首批设115个赛项
  3. Apifox实战——微信的第三方小程序提审发布
  4. 时尚【女连衣裙秋季新款连衣裙】搭配技巧
  5. 申报不能忘,2022年湖北省各地专精特新小巨人企业奖励补贴政策汇编(附申报条件)
  6. 基于VHDL的密码锁
  7. [量化学院]价值选股策略——基于机器学习算法
  8. 微信小程序学习之路——API媒体
  9. paddle 图标注_UI进阶干货 如何标注才能高度还原设计稿?
  10. SpringBoot与MongoDB的集成使用