对于rk3288,内核已经包含了spi_master的驱动,只需要在设备树上配置即可,在使用的时候将状态status改为ok即可。
spi0: spi@ff110000 {
compatible = "rockchip,rockchip-spi";
reg = <0xff110000 0x1000>;
interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default";
pinctrl-0 = <&spi0_txd &spi0_rxd &spi0_clk &spi0_cs0 &spi0_cs1>;
rockchip,spi-src-clk = <0>;
num-cs = <2>;
clocks =<&clk_spi0>, <&clk_gates6 4>;
clock-names = "spi","pclk_spi0";
dmas = <&pdma1 11>, <&pdma1 12>;
#dma-cells = <2>;
dma-names = "tx", "rx";
status = "disabled";
};
有了spi_master,还需要spi_driver和spi_board_info,spi_board_info是对spi设备硬件的资源描述,
&spi0 {
status = "okay";
max-freq = <48000000>;
spidev@00 {
compatible = "linux,spidev";
reg = <0x00>;
spi-max-frequency = <48000000>;
};
spi_oled@01 {
compatible = "rk3288,oled";
reg = <0x01>;
spi-max-frequency = <10000000>;
cd-gpio = <&gpio7 GPIO_A3 GPIO_ACTIVE_HIGH>;
//spi-cpha = <0>;
//spi-cpol = <0>;
};
在spi控制器下面定义一个spi设备:spi_oled。我手上的这个oled屏幕的spi需要3根线,SPICLK,SPIMOSI,OLED_DC,OLED_CSn。对于rk3288已经把这些接口定义出来了,在spi控制器中通过pinctrl来进行引用既可以。对于spi的片选信号,rk3288静态的定义个两个引脚cs0和cs1,这里使用cs1。对于设备地址0使用cs0,设备地址1使用cs1。
master->dev.parent = dws->parent_dev;
master->dev.of_node = dws->parent_dev->of_node;
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LOOP;
master->bus_num = dws->bus_num; //挂载在那一条总线上面
master->num_chipselect = dws->num_cs; //最大支持的片选数量
master->cleanup = dw_spi_cleanup;
master->setup = dw_spi_setup; //设置频率,模式等操作
master->transfer_one_message = dw_spi_transfer_one_message; // 用于发起spi传输
master->prepare_transfer_hardware = dw_spi_prepare_transfer;
master->unprepare_transfer_hardware = dw_spi_unprepare_transfer;
tasklet_init(&dws->pump_transfers, pump_transfers, (unsigned long)dws); //初始化任务队列,在transfer_one_message中被调用
ret = spi_register_master(master); //注册spi_master
内核中会去设备spi_master,其中关键的由master->transfer_hardware负责发起spi传输,master->dw_spi_setup用于初始化spi的配置,最后通过spi_register_master注册进内核。
然后对于spi设备的初始化,它的流程如下
spi_register_master
of_spi_register_master(master);
of_register_spi_devices(master); (Register child devices onto the SPI bus)
for_each_available_child_of_node(master->dev.of_node, nc) {
spi = spi_alloc_device(master); //分配一个spi_device
//设置spi_device
rc = spi_add_device(spi); //向内核添加spi设备
}
在设备树中添加了spi设备之后,还需要在内核中添加驱动文件,驱动程序的compatible需要和spi设备的compatible匹配才会调用probe函数。在probe函数中去把oled屏注册为一个字符设备:
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <sound/core.h>
#include <linux/spi/spi.h>
#include <asm/uaccess.h>
#include <linux/delay.h>
//#include <mach/hardware.h>
//#include <mach/regs-gpio.h>
#include <linux/gpio.h>
//#include <plat/gpio-cfg.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
/* 构造注册 spi_driver */
#define GPIO_HIGH 1
#define GPIO_LOW 0
static int major;
static struct class *class;
//static int spi_oled_dc_pin;
static struct spi_device *spi_oled_dev;
static unsigned char *ker_buf;
enum of_gpio_flags cd_flags;
struct platform_oled_data {
int cd_gpio;
unsigned long cd_gpio_flags;
};
struct platform_oled_data oledData;
static void OLED_Set_DC(char val)
{
gpio_set_value(oledData.cd_gpio,val);
}
static void OLEDWriteCmd(unsigned char cmd)
{
OLED_Set_DC(0); /* command */
spi_write(spi_oled_dev, &cmd, 1);
OLED_Set_DC(1); /* */
}
static void OLEDWriteDat(unsigned char dat)
{
OLED_Set_DC(1); /* data */
spi_write(spi_oled_dev, &dat, 1);
OLED_Set_DC(1); /* */
}
static void OLEDSetPageAddrMode(void)
{
OLEDWriteCmd(0x20);
OLEDWriteCmd(0x02);
}
static void OLEDSetPos(int page, int col)
{
OLEDWriteCmd(0xB0 + page); /* page address */
OLEDWriteCmd(col & 0xf); /* Lower Column Start Address */
OLEDWriteCmd(0x10 + (col >> 4)); /* Lower Higher Start Address */
}
static void OLEDClear(void)
{
int page, i;
for (page = 0; page < 8; page ++)
{
OLEDSetPos(page, 0);
for (i = 0; i < 128; i++)
OLEDWriteDat(0);
}
}
void OLEDClearPage(int page)
{
int i;
OLEDSetPos(page, 0);
for (i = 0; i < 128; i++)
OLEDWriteDat(0);
}
void OLEDInit(void)
{
/* 向OLED发命令以初始化 */
OLEDWriteCmd(0xAE); /*display off*/
OLEDWriteCmd(0x00); /*set lower column address*/
OLEDWriteCmd(0x10); /*set higher column address*/
OLEDWriteCmd(0x40); /*set display start line*/
OLEDWriteCmd(0xB0); /*set page address*/
OLEDWriteCmd(0x81); /*contract control*/
OLEDWriteCmd(0x66); /*128*/
OLEDWriteCmd(0xA1); /*set segment remap*/
OLEDWriteCmd(0xA6); /*normal / reverse*/
OLEDWriteCmd(0xA8); /*multiplex ratio*/
OLEDWriteCmd(0x3F); /*duty = 1/64*/
OLEDWriteCmd(0xC8); /*Com scan direction*/
OLEDWriteCmd(0xD3); /*set display offset*/
OLEDWriteCmd(0x00);
OLEDWriteCmd(0xD5); /*set osc division*/
OLEDWriteCmd(0x80);
OLEDWriteCmd(0xD9); /*set pre-charge period*/
OLEDWriteCmd(0x1f);
OLEDWriteCmd(0xDA); /*set COM pins*/
OLEDWriteCmd(0x12);
OLEDWriteCmd(0xdb); /*set vcomh*/
OLEDWriteCmd(0x30);
OLEDWriteCmd(0x8d); /*set charge pump enable*/
OLEDWriteCmd(0x14);
OLEDSetPageAddrMode();
OLEDClear();
OLEDWriteCmd(0xAF); /*display ON*/
}
#define OLED_CMD_INIT 0x100001
#define OLED_CMD_CLEAR_ALL 0x100002
#define OLED_CMD_CLEAR_PAGE 0x100003
#define OLED_CMD_SET_POS 0x100004
static long oled_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int page;
int col;
switch (cmd)
{
case OLED_CMD_INIT:
{
OLEDInit();
break;
}
case OLED_CMD_CLEAR_ALL:
{
OLEDClear();
break;
}
case OLED_CMD_CLEAR_PAGE:
{
page = arg;
OLEDClearPage(page);
break;
}
case OLED_CMD_SET_POS:
{
page = arg & 0xff;
col = (arg >> 8) & 0xff;
OLEDSetPos(page, col);
break;
}
}
return 0;
}
static ssize_t oled_write(struct file *file,
const char __user *buf,
size_t count, loff_t *ppos)
{
int ret;
if (count > 4096)
return -EINVAL;
ret = copy_from_user(ker_buf, buf, count);
OLED_Set_DC(1); /* data */
spi_write(spi_oled_dev, ker_buf, count);
return 0;
}
static struct file_operations oled_ops = {
.owner = THIS_MODULE,
.unlocked_ioctl = oled_ioctl,
.write = oled_write,
};
static int spi_oled_probe(struct spi_device *spi)
{
struct device_node *np = spi->dev.of_node;
int ret;
spi_oled_dev = spi;
printk("%s, %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
ker_buf = kmalloc(4096, GFP_KERNEL);
/* 注册一个 file_operations */
major = register_chrdev(0, "oled", &oled_ops);
class = class_create(THIS_MODULE, "oled");
/* 为了让mdev根据这些信息来创建设备节点 */
device_create(class, NULL, MKDEV(major, 0), NULL, "oled"); /* /dev/oled */
oledData.cd_gpio= of_get_named_gpio_flags(np, "cd-gpio", 0, &cd_flags);
if (gpio_is_valid(oledData.cd_gpio)) {
ret = devm_gpio_request_one(&spi->dev, oledData.cd_gpio, (cd_flags & OF_GPIO_ACTIVE_LOW) ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, "oled cmd/data pin");
if (ret != 0) {
dev_err(&spi->dev, "oled cd gpio_request error\n");
return -EIO;
}
gpio_direction_output(oledData.cd_gpio, 0);
gpio_set_value(oledData.cd_gpio,GPIO_HIGH);
msleep(20);
} else {
dev_info(&spi->dev, "oled cd pin invalid\n");
}
return 0;
}
static int spi_oled_remove(struct spi_device *spi)
{
device_destroy(class, MKDEV(major, 0));
class_destroy(class);
unregister_chrdev(major, "oled");
kfree(ker_buf);
return 0;
}
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "rk3288,oled" },
{},
};
static struct spi_driver spi_oled_drv = {
.driver = {
.name = "oled",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(spidev_dt_ids),
},
.probe = spi_oled_probe,
.remove = spi_oled_remove,
};
static int spi_oled_init(void)
{
return spi_register_driver(&spi_oled_drv);
}
static void spi_oled_exit(void)
{
spi_unregister_driver(&spi_oled_drv);
}