1.framebuffer驱动
2.spi framebuffer驱动
3.spi framebuffer驱动(切换并显示虚拟终端)
接这上一节
spi framebuffer驱动实现了,但是只有刷屏。6个虚拟终端并没有显示在屏幕上,要想办法让虚拟终端显示到我们的fb上面来。最总虚拟终端的源代码(过程不详述),发现了这么个地方:
/driver/video/console/fbcon.c
static int fbcon_event_notify(struct notifier_block *self,
unsigned long action, void *data)
{
struct fb_event *event = data;
struct fb_info *info = event->info;
struct fb_videomode *mode;
struct fb_con2fbmap *con2fb;
struct fb_blit_caps *caps;
int idx, ret = 0;
/*
* ignore all events except driver registration and deregistration
* if fbcon is not active
*/
if (fbcon_has_exited && !(action == FB_EVENT_FB_REGISTERED ||
action == FB_EVENT_FB_UNREGISTERED))
goto done;
switch(action) {
case FB_EVENT_SUSPEND:
fbcon_suspended(info);
break;
case FB_EVENT_RESUME:
fbcon_resumed(info);
break;
case FB_EVENT_MODE_CHANGE:
fbcon_modechanged(info);
break;
case FB_EVENT_MODE_CHANGE_ALL:
fbcon_set_all_vcs(info);
break;
case FB_EVENT_MODE_DELETE:
mode = event->data;
ret = fbcon_mode_deleted(info, mode);
break;
case FB_EVENT_FB_UNBIND:
idx = info->node;
ret = fbcon_fb_unbind(idx);
break;
case FB_EVENT_FB_REGISTERED:
ret = fbcon_fb_registered(info);
break;
case FB_EVENT_FB_UNREGISTERED:
ret = fbcon_fb_unregistered(info);
break;
case FB_EVENT_SET_CONSOLE_MAP:
con2fb = event->data;
ret = set_con2fb_map(con2fb->console - 1,
con2fb->framebuffer, 1);
break;
case FB_EVENT_GET_CONSOLE_MAP:
con2fb = event->data;
con2fb->framebuffer = con2fb_map[con2fb->console - 1];
break;
case FB_EVENT_BLANK:
fbcon_fb_blanked(info, *(int *)event->data);
break;
case FB_EVENT_NEW_MODELIST:
fbcon_new_modelist(info);
break;
case FB_EVENT_GET_REQ:
caps = event->data;
fbcon_get_requirement(info, caps);
break;
case FB_EVENT_REMAP_ALL_CONSOLE:
idx = info->node;
fbcon_remap_all(idx);
break;
}
done:
return ret;
}
切换虚拟终端输出就在这个地方了,只需要发送一个fb_notify消息即可。我们使用最后这个
case FB_EVENT_REMAP_ALL_CONSOLE:
idx = info->node;
fbcon_remap_all(idx);
break;
切换所有终端。源码
struct fb_info * fb_init(struct spi_device *spi) //此函数在spi设备驱动的probe函数里被调用
{
struct fb_info *fbi;
u8 *v_addr;
u32 p_addr;
lcd_data_t *data;
struct fb_event event;
v_addr = dma_alloc_coherent(NULL, LCD_W * LCD_H * 3, &p_addr, GFP_KERNEL);
//额外分配lcd_data_t类型空间
fbi = framebuffer_alloc(sizeof(lcd_data_t), NULL);
data = fbi->par; //data指针指向额外分配的空间
data->spi = spi;
fbi->var.xres = LCD_W;
fbi->var.yres = LCD_H;
fbi->var.xres_virtual = LCD_W;
fbi->var.yres_virtual = LCD_H;
fbi->var.bits_per_pixel = 24;
fbi->var.red.offset = 16;
fbi->var.red.length = 5;
fbi->var.green.offset = 8;
fbi->var.green.length = 6;
fbi->var.blue.offset = 0;
fbi->var.blue.length = 5;
// fbi->var.pixclock = 93006;
fbi->var.activate = FB_ACTIVATE_NOW;
strcpy(fbi->fix.id, "hello_fb");
fbi->fix.smem_start = p_addr; //显存的物理地址
fbi->fix.smem_len = LCD_W * LCD_H * 3;
fbi->fix.type = FB_TYPE_PACKED_PIXELS;
fbi->fix.visual = FB_VISUAL_TRUECOLOR;
fbi->fix.line_length = LCD_W * 3;
fbi->fbops = &fops;
fbi->screen_base = v_addr; //显存虚拟地址
fbi->screen_size = LCD_W * LCD_H * 3; //显存大小
spi_set_drvdata(spi, fbi);
register_framebuffer(fbi);
data->thread = kthread_run(thread_func, fbi, spi->modalias);
event.info = fbi;
fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event);
return fbi;
}
void fb_del(struct spi_device *spi) //此函数在spi设备驱动remove时被调用
{
struct fb_info *fbi = spi_get_drvdata(spi);
lcd_data_t *data = fbi->par;
kthread_stop(data->thread); //让刷图线程退出
unregister_framebuffer(fbi);
dma_free_coherent(NULL, fbi->fix.smem_len, fbi->screen_base, fbi->fix.smem_start);
framebuffer_release(fbi);
}
在framebuffer驱动注册完成的最后发送一个fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event);
就切换完成了。
终端切换成功了,但是图像没有办法显示,都是乱码。手头的屏幕是控制器是ILI9488,spi传送的图像数据一个像素由三个字节组成,分别是蓝绿红。三个字节并不代表是24位,其中有效数据只占16位,蓝5位,绿6位,红5位。也就是rgb565,奇葩的是这三个颜色在一个字节中还是高位对齐,也就是说低位并没有使用,注意并不是大端对齐模式。为了对齐最后蓝和红也占6位了,最后变成了奇葩的rgb666吧。附上数据手册中的数据结构图
这种摆放方式不知道内核中有没有,软件支持性也不清楚。索性直接转换吧。为了使绘图效率达到最高,就不在spi传输的时候做转换了,而是在写入fb缓冲内存的时候就吧格式摆放正确。先做一下虚拟终端的绘图部分。虚拟终端主要调用fb_ops结构体中的几个函数完成字符绘制,我们只需要重写其中两个:fb_imageblit
fb_fillrect
。fb_imageblit
是绘制文本的主要函数,他接收矢量字符,坐标,向fb缓冲区绘图。
适量字符每一位代表一个像素,比如Z的适量字符:
/* 90 0x5a 'Z' */
0x00, 0x00, /* 0000000000 */
0x3f, 0x80, /* 0011111110 */
0x21, 0x80, /* 0010000110 */
0x01, 0x80, /* 0000000110 */
0x03, 0x00, /* 0000001100 */
0x03, 0x00, /* 0000001100 */
0x06, 0x00, /* 0000011000 */
0x06, 0x00, /* 0000011000 */
0x0c, 0x00, /* 0000110000 */
0x0c, 0x00, /* 0000110000 */
0x18, 0x00, /* 0001100000 */
0x18, 0x00, /* 0001100000 */
0x30, 0x00, /* 0011000000 */
0x30, 0x80, /* 0011000010 */
0x3f, 0x80, /* 0011111110 */
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
根据矢量字符以我们的特殊rgb666绘图:
void fb_imageblit(struct fb_info *info, const struct fb_image *image) {
unsigned char *p, *p2, shift, *src;
u32 dx, dy, width, height, bpp, i, j;
dx = image->dx;
dy = image->dy;
width = image->width;
height = image->height;
bpp = info->var.bits_per_pixel;
src = image->data;
p = info->screen_base + info->fix.line_length * dy + dx * bpp;
for (i = 0; i < height; ++i)
{
p2 = p;
shift = 8;
for (j = 0; j < width; ++j)
{
shift--;
if (*src & (1 << shift)) {
p2[0] = 0xf8;
p2[1] = 0xf8;
p2[2] = 0xf8;
} else {
p2[0] = 0x00;
p2[1] = 0x00;
p2[2] = 0x00;
}
if (!shift) { shift = 8; src++; };
p2 += 3;
}
p += info->fix.line_length;
}
}
fb_fillrect
是清除字符调用的函数,在需要的区域填充黑色即可。
static void fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
{
unsigned char *p, *p2;
u32 dx, dy, width, height, bpp, i, j;
dx = rect->dx;
dy = rect->dy;
width = rect->width;
height = rect->height;
bpp = info->var.bits_per_pixel;
p = info->screen_base + info->fix.line_length * dy + dx * bpp;
for (i = 0; i < height; ++i)
{
p2 = p;
for (j = 0; j < width; ++j)
{
p2[0] = 0x00;
p2[1] = 0xf8;
p2[2] = 0x00;
p2 += 3;
}
p += info->fix.line_length;
}
}
framebuffer.c
#include
#include
#include
#include
#include
#include
#include
#include
#include "fb_spi.h"
typedef struct {
struct spi_device *spi;
struct task_struct *thread;
} lcd_data_t;
static void fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
{
unsigned char *p, *p2;
u32 dx, dy, width, height, bpp, i, j;
dx = rect->dx;
dy = rect->dy;
width = rect->width;
height = rect->height;
bpp = info->var.bits_per_pixel;
p = info->screen_base + info->fix.line_length * dy + dx * bpp;
for (i = 0; i < height; ++i)
{
p2 = p;
for (j = 0; j < width; ++j)
{
p2[0] = 0x00;
p2[1] = 0xf8;
p2[2] = 0x00;
p2 += 3;
}
p += info->fix.line_length;
}
}
void fb_imageblit(struct fb_info *info, const struct fb_image *image) {
unsigned char *p, *p2, shift, *src;
u32 dx, dy, width, height, bpp, i, j;
dx = image->dx;
dy = image->dy;
width = image->width;
height = image->height;
bpp = info->var.bits_per_pixel;
src = image->data;
p = info->screen_base + info->fix.line_length * dy + dx * bpp;
for (i = 0; i < height; ++i)
{
p2 = p;
shift = 8;
for (j = 0; j < width; ++j)
{
shift--;
if (*src & (1 << shift)) {
p2[0] = 0xf8;
p2[1] = 0xf8;
p2[2] = 0xf8;
} else {
p2[0] = 0x00;
p2[1] = 0x00;
p2[2] = 0x00;
}
if (!shift) { shift = 8; src++; };
p2 += 3;
}
p += info->fix.line_length;
}
}
struct fb_ops fops = {
.owner = THIS_MODULE,
.fb_imageblit = fb_imageblit,
.fb_fillrect = fb_fillrect,
};
/* from pxafb.c */
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16 - bf->length;
return chan << bf->offset;
}
extern void show_fb(struct fb_info *fbi, struct spi_device *spi);
int thread_func(void *data)
{
struct fb_info *fbi = (struct fb_info *)data;
lcd_data_t *ldata = fbi->par;
while (1)
{
if (kthread_should_stop())
break;
show_fb(fbi, ldata->spi);
}
return 0;
}
struct fb_info * fb_init(struct spi_device *spi) //此函数在spi设备驱动的probe函数里被调用
{
struct fb_info *fbi;
u8 *v_addr;
u32 p_addr;
lcd_data_t *data;
struct fb_event event;
v_addr = dma_alloc_coherent(NULL, LCD_W * LCD_H * 3, &p_addr, GFP_KERNEL);
//额外分配lcd_data_t类型空间
fbi = framebuffer_alloc(sizeof(lcd_data_t), NULL);
data = fbi->par; //data指针指向额外分配的空间
data->spi = spi;
fbi->var.xres = LCD_W;
fbi->var.yres = LCD_H;
fbi->var.xres_virtual = LCD_W;
fbi->var.yres_virtual = LCD_H;
fbi->var.bits_per_pixel = 24;
fbi->var.red.offset = 16;
fbi->var.red.length = 5;
fbi->var.green.offset = 8;
fbi->var.green.length = 6;
fbi->var.blue.offset = 0;
fbi->var.blue.length = 5;
// fbi->var.pixclock = 93006;
fbi->var.activate = FB_ACTIVATE_NOW;
strcpy(fbi->fix.id, "hello_fb");
fbi->fix.smem_start = p_addr; //显存的物理地址
fbi->fix.smem_len = LCD_W * LCD_H * 3;
fbi->fix.type = FB_TYPE_PACKED_PIXELS;
fbi->fix.visual = FB_VISUAL_TRUECOLOR;
fbi->fix.line_length = LCD_W * 3;
fbi->fbops = &fops;
fbi->screen_base = v_addr; //显存虚拟地址
fbi->screen_size = LCD_W * LCD_H * 3; //显存大小
spi_set_drvdata(spi, fbi);
register_framebuffer(fbi);
data->thread = kthread_run(thread_func, fbi, spi->modalias);
event.info = fbi;
fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event);
return fbi;
}
void fb_del(struct spi_device *spi) //此函数在spi设备驱动remove时被调用
{
struct fb_info *fbi = spi_get_drvdata(spi);
lcd_data_t *data = fbi->par;
kthread_stop(data->thread); //让刷图线程退出
unregister_framebuffer(fbi);
dma_free_coherent(NULL, fbi->fix.smem_len, fbi->screen_base, fbi->fix.smem_start);
framebuffer_release(fbi);
}
spi.h
struct Lcd_dev
{
unsigned short width; //LCD 宽度
unsigned short height; //LCD 高度
unsigned short id; //LCD ID
unsigned char dir; //横屏还是竖屏控制:0,竖屏;1,横屏。
unsigned short wramcmd; //开始写gram指令
unsigned short setxcmd; //设置x坐标指令
unsigned short setycmd; //设置y坐标指令
};
//画笔颜色
#define WHITE 0xFFFF
#define BLACK 0x0000
#define BLUE 0x001F
#define BRED 0XF81F
#define GRED 0XFFE0
#define GBLUE 0X07FF
#define RED 0xF800
#define MAGENTA 0xF81F
#define GREEN 0x07E0
#define CYAN 0x7FFF
#define YELLOW 0xFFE0
#define BROWN 0XBC40 //棕色
#define BRRED 0XFC07 //棕红色
#define GRAY 0X8430 //灰色
//GUI颜色
#define DARKBLUE 0X01CF //深蓝色
#define LIGHTBLUE 0X7D7C //浅蓝色
#define GRAYBLUE 0X5458 //灰蓝色
//以上三色为PANEL的颜色
#define LIGHTGREEN 0X841F //浅绿色
#define LGRAY 0XC618 //浅灰色(PANNEL),窗体背景色
#define LGRAYBLUE 0XA651 //浅灰蓝色(中间层颜色)
#define LBBLUE 0X2B12 //浅棕蓝色(选择条目的反色)
//LCD的画笔颜色和背景色
#define POINT_COLOR (0x0000) //画笔颜色
#define BACK_COLOR (0xFFFF) //背景色
//定义LCD的尺寸
#define LCD_W 320
#define LCD_H 480
#define USE_HORIZONTAL 0 //定义液晶屏顺时针旋转方向 0-0度旋转,1-90度旋转,2-180度旋转,3-270度旋转
spi.c
#include
#include
#include
#include
#include
#include
#include
#include "fb_dev.h"
#include "fb_spi.h"
struct spi_lcd_cmd {
u8 d_or_r;
u8 data; // command
int delay_ms; //此命令发送数据完成后,需延时多久
} cmds[] = {
{0, 0XF7, 0},
{1, 0xA9, 0},
{1, 0x51, 0},
{1, 0x2C, 0},
{1, 0x82, 0},
{0, 0xC0, 0},
{1, 0x11, 0},
{1, 0x09, 0},
{0, 0xC1, 0},
{1, 0x41, 0},
{0, 0XC5, 0},
{1, 0x00, 0},
{1, 0x0A, 0},
{1, 0x80, 0},
{0, 0xB1, 0},
{1, 0xB0, 0},
{1, 0x11, 0},
{0, 0xB4, 0},
{1, 0x02, 0},
{0, 0xB6, 0},
{1, 0x02, 0},
{1, 0x42, 0},
{0, 0xB7, 0},
{1, 0xc6, 0},
{0, 0xBE, 0},
{1, 0x00, 0},
{1, 0x04, 0},
{0, 0xE9, 0},
{1, 0x00, 0},
{0, 0x36, 0},
{1, (1 << 3) | (0 << 7) | (1 << 6) | (1 << 5), 0},
{0, 0x3A, 0},
{1, 0x66, 0},
{0, 0xE0, 0},
{1, 0x00, 0},
{1, 0x07, 0},
{1, 0x10, 0},
{1, 0x09, 0},
{1, 0x17, 0},
{1, 0x0B, 0},
{1, 0x41, 0},
{1, 0x89, 0},
{1, 0x4B, 0},
{1, 0x0A, 0},
{1, 0x0C, 0},
{1, 0x0E, 0},
{1, 0x18, 0},
{1, 0x1B, 0},
{1, 0x0F, 0},
{0, 0XE1, 0},
{1, 0x00, 0},
{1, 0x17, 0},
{1, 0x1A, 0},
{1, 0x04, 0},
{1, 0x0E, 0},
{1, 0x06, 0},
{1, 0x2F, 0},
{1, 0x45, 0},
{1, 0x43, 0},
{1, 0x02, 0},
{1, 0x0A, 0},
{1, 0x09, 0},
{1, 0x32, 0},
{1, 0x36, 0},
{1, 0x0F, 0},
{0, 0x11, 0},
{0, 0x29, 0},
};
struct Lcd_dev lcddev;
static inline void write_u8(struct spi_device * spi, u8 d_or_r, u8 cmd)
{
struct fb_data *pdata = spi->dev.platform_data;
gpio_set_value(pdata->rs, d_or_r);
spi_write(spi, &cmd, 1);
}
static void write_u24s(struct spi_device * spi, u8 * data, unsigned int len)
{
struct fb_data *pdata = spi->dev.platform_data;
gpio_set_value(pdata->rs, 1);
spi_write(spi, data, len);
}
void lcd_set_window(struct spi_device * spi, unsigned short xStar, unsigned short yStar, unsigned short xEnd, unsigned short yEnd)
{
write_u8(spi, 0, lcddev.setxcmd);
write_u8(spi, 1, xStar >> 8);
write_u8(spi, 1, 0x00FF & xStar);
write_u8(spi, 1, xEnd >> 8);
write_u8(spi, 1, 0x00FF & xEnd);
write_u8(spi, 0, lcddev.setycmd);
write_u8(spi, 1, yStar >> 8);
write_u8(spi, 1, 0x00FF & yStar);
write_u8(spi, 1, yEnd >> 8);
write_u8(spi, 1, 0x00FF & yEnd);
write_u8(spi, 0, lcddev.wramcmd);
}
void lcd_clear(struct spi_device * spi, struct fb_info * fbi, unsigned short Color)
{
unsigned int i;
unsigned char *p;
lcd_set_window(spi, 0, 0, lcddev.width - 1, lcddev.height - 1);
p = fbi->screen_base;
for (i = 0; i < fbi->var.xres * fbi->var.yres; i++)
{
p[i * 3] = (Color >> 8) & 0xF8;
p[i * 3 + 1] = (Color >> 3) & 0xFC;
p[i * 3 + 2] = (Color << 3);
}
}
void lcd_direction(struct spi_device * spi, unsigned char direction)
{
lcddev.setxcmd = 0x2A;
lcddev.setycmd = 0x2B;
lcddev.wramcmd = 0x2C;
switch (direction) {
case 0:
lcddev.width = LCD_W;
lcddev.height = LCD_H;
write_u8(spi, 0, 0x36);
write_u8(spi, 1, (1 << 3) | (0 << 6) | (0 << 7)); //BGR==1,MY==0,MX==0,MV==0
break;
case 1:
lcddev.width = LCD_H;
lcddev.height = LCD_W;
write_u8(spi, 0, 0x36);
write_u8(spi, 1, (1 << 3) | (0 << 7) | (1 << 6) | (1 << 5)); //BGR==1,MY==1,MX==0,MV==1
break;
case 2:
lcddev.width = LCD_W;
lcddev.height = LCD_H;
write_u8(spi, 0, 0x36);
write_u8(spi, 1, (1 << 3) | (1 << 6) | (1 << 7)); //BGR==1,MY==0,MX==0,MV==0
break;
case 3:
lcddev.width = LCD_H;
lcddev.height = LCD_W;
write_u8(spi, 0, 0x36);
write_u8(spi, 1, (1 << 3) | (1 << 7) | (1 << 5)); //BGR==1,MY==1,MX==0,MV==1
break;
default: break;
}
}
//初始化spi_lcd
static void spi_lcd_init(struct spi_device * spi)
{
struct fb_data *pdata = spi->dev.platform_data;
int i, n;
// 屏复位
gpio_set_value(pdata->reset, 0);
mdelay(100);
gpio_set_value(pdata->reset, 1);
mdelay(100);
n = 0; // n用于记录数据数组spi_lcd_datas的位置
//发命令,并发出命令所需的数据
for (i = 0; i < ARRAY_SIZE(cmds); i++) //命令
{
write_u8(spi, cmds[i].d_or_r, cmds[i].data);
if (cmds[i].delay_ms)
mdelay(cmds[i].delay_ms);
}
}
void show_fb(struct fb_info * fbi, struct spi_device * spi)
{
u8 *p = (u8 *)(fbi->screen_base);
lcd_set_window(spi, 0, 0, lcddev.width - 1, lcddev.height - 1); //从屏的0,0坐标开始刷
write_u24s(spi, p, fbi->screen_size/2);
write_u24s(spi, p + fbi->screen_size/2, fbi->screen_size/2);
}
extern struct fb_info * fb_init(struct spi_device *);
static int spi_probe(struct spi_device * spi)
{
struct fb_data *pdata = spi->dev.platform_data;
int ret;
struct fb_info * fbi;
fbi = NULL;
ret = gpio_request(pdata->reset, spi->modalias);
if (ret < 0)
goto err0;
ret = gpio_request(pdata->rs, spi->modalias);
if (ret < 0)
goto err1;
gpio_direction_output(pdata->rs, 0);
gpio_direction_output(pdata->reset, 1);
spi_lcd_init(spi); //初始化
lcd_direction(spi, USE_HORIZONTAL); //设置LCD显示方向
fbi = fb_init(spi); //fb设备初始化
// lcd_clear(spi, fbi, BLUE);
printk("probe ...%s\n", spi->modalias);
return 0;
err1:
gpio_free(pdata->reset);
err0:
return ret;
}
extern void fb_del(struct spi_device *);
static int spi_remove(struct spi_device * spi)
{
struct fb_data *pdata = spi->dev.platform_data;
fb_del(spi); //fb设备回收
gpio_free(pdata->rs);
gpio_free(pdata->reset);
printk("%s remove\n", spi->modalias);
return 0;
}
static struct spi_driver spi_drv = {
.driver = {
.owner = THIS_MODULE,
.name = "hello_spi_fb",
},
.probe = spi_probe,
.remove = spi_remove,
};
static int __init hello_init(void) {
int ret;
ret = spi_register_driver(&spi_drv);
if (ret) {
printk("hello driver fail\n");
} else {
printk("hello driver init\n");
}
return ret;
}
static void __exit hello_exit(void) {
spi_unregister_driver(&spi_drv);
printk("hello device exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
dev.h
#ifndef __FB_DEV__
#define __FB_DEV__
struct fb_data{
int reset;
int rs;
};
#endif
dev.c
#include
#include
#include
#include
#include
#include
#include
#include
#include "fb_dev.h"
static struct fb_data fb1_plat_data = {
.reset = PAD_GPIO_D + 21,
.rs = PAD_GPIO_D + 17,
};
struct pl022_config_chip spi0_info = {
/* available POLLING_TRANSFER, INTERRUPT_TRANSFER, DMA_TRANSFER */
.com_mode = CFG_SPI0_COM_MODE,
.iface = SSP_INTERFACE_MOTOROLA_SPI,
/* We can only act as master but SSP_SLAVE is possible in theory */
.hierarchy = SSP_MASTER,
/* 0 = drive TX even as slave, 1 = do not drive TX as slave */
.slave_tx_disable = 1,
.rx_lev_trig = SSP_RX_4_OR_MORE_ELEM,
.tx_lev_trig = SSP_TX_4_OR_MORE_EMPTY_LOC,
.ctrl_len = SSP_BITS_8,
.wait_state = SSP_MWIRE_WAIT_ZERO,
.duplex = SSP_MICROWIRE_CHANNEL_FULL_DUPLEX,
.clkdelay = SSP_FEEDBACK_CLK_DELAY_1T,
};
static struct spi_board_info spi_plat_board = {
.modalias = "hello_spi_fb", /* fixup */
.max_speed_hz = 25000000, /* max spi clock (SCK) speed in HZ */
.bus_num = 0, /* Note> set bus num, must be smaller than ARRAY_SIZE(spi_plat_device) */
.chip_select = 1, /* Note> set chip select num, must be smaller than spi cs_num */
.controller_data = &spi0_info,
.mode = SPI_MODE_3 | SPI_CPOL | SPI_CPHA,
.platform_data = &fb1_plat_data,
};
__attribute__ ((unused)) static void device_spi_delete(struct spi_master *master, unsigned cs)
{
struct device *dev;
char str[32];
snprintf(str, sizeof(str), "%s.%u", dev_name(&master->dev), cs);
dev = bus_find_device_by_name(&spi_bus_type, NULL, str);
if (dev) {
printk(": Deleting %s\n", str);
device_del(dev);
}
}
static struct spi_device *spi_device;
static int __init hello_init(void) {
struct spi_master *master;
master = spi_busnum_to_master(spi_plat_board.bus_num);
if (!master) {
printk(": spi_busnum_to_master(%d) returned NULL\n",
spi_plat_board.bus_num);
return -EINVAL;
}
/* make sure it's available */
// device_spi_delete(master, spi_plat_board.chip_select);
spi_device = spi_new_device(master, &spi_plat_board);
put_device(&master->dev);
if (!spi_device) {
printk(": spi_new_device() returned NULL\n");
return -EPERM;
}
return 0;
printk("hello device init\n");
return 0;
}
static void __exit hello_exit(void) {
if (spi_device) {
if (spi_device->master->cleanup) {
spi_device->master->cleanup(spi_device);
}
device_del(&spi_device->dev);
kfree(spi_device);
}
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");