15.【linux驅動】spi驅動

spi系列文章:
1.spi 硬件、協議
2.spi(GPIO模擬)
3.spi驅動

設置時鐘

spi在arm-cortex系列當中一般與ssp聯繫一起。ssp兼容spi,兩者區別也不大。在4418上面沒有用spi模式調試成功,用spp卻成功了,參考了內核初始化源碼,也是ssp模式,沒辦法先用ssp模式。
spi控制器的初始化首先需要配置好時鐘,在4418的spi/ssp控制器中時鐘由兩個來源。一個是pclk,一個是sspclk。pclk是cpu訪問spi寄存器的時鐘,也就是說cpu改變spi/ssp寄存器的時鐘,一般等於cpu主頻,這個一般不需要管,在4418上也無法修改。sspclk決定了spi/ssp控制器的內部速度。最後分頻輸出爲sspclkout,這個纔是spi/ssp的時鐘。附上框圖如下。
在這裏插入圖片描述
spi/ssp時鐘線:

pll0
sspclk分頻
pll1
pll2
sspclk
一級分頻
spi/ssp controller
二級分頻
sspclkout

所以需要設置sspclk分頻、一級分頻、二級分頻。
在這裏插入圖片描述
sspclk由時鐘發生器產生,設置一下時鐘發生器。頻率我設置得比較高。

	ssp_clk = (struct SSP_CLK *)ioremap(0xc00ac000,sizeof(struct SSP_CLK));			//映射地址
	ssp_clk->clk = 0;
	ssp_clk->clk &= ~(1 << 1);
	ssp_clk->clk |= (2 << 2);
	ssp_clk->clk |= (2 << 5);
	ssp_clk->clk &= ~(1 << 15);
	ssp_clk->enb &= ~(1 << 2);
	udelay(100);
	ssp_clk->enb |= (1 << 2);

spi/ssp的一級分頻和二級分頻由後面的配置寄存器控制。

spi/ssp控制器復位

在設置spi/ssp控制器之前先做一下復位。復位的作用是在spi/ssp死鎖的時候用來恢復它們的功能的,初始化的時候也要做一下。根據手冊描述需要復位兩個寄存器,時間不能少於一個pclk。
在這裏插入圖片描述

	reset = (unsigned int *)ioremap(0xc0012004,sizeof(int));			//映射地址
	*reset |= (1 << 11);
	*reset |= (1 << 12);
	*reset &= ~(1 << 11);
	*reset &= ~(1 << 12);
	udelay(100);
	*reset |= (1 << 11);
	*reset |= (1 << 12);

然後設置GPIO功能

這邊spi功能爲1。

	nxp_soc_gpio_set_io_func(PAD_GPIO_C + 29, 1);	// 設置gpio功能
	nxp_soc_gpio_set_io_func(PAD_GPIO_C + 30, 1);	// 設置gpio功能
	nxp_soc_gpio_set_io_func(PAD_GPIO_C + 31, 1);	// 設置gpio功能
	nxp_soc_gpio_set_io_func(PAD_GPIO_D + 0, 1);	// 設置gpio功能

初始化spi/ssp寄存器

spi/ssp需要設置的寄存器主要有三個sspcr0、sspcr1 、sspdmacr 。設置了spi的工作模式、頻率、dma。其中sspcr0設置一級分頻的值,範圍是0~ 255最後需要加一作爲分頻係數,sspcr1 設置了二級分頻的值範圍是2~254。
頻率公式:
在這裏插入圖片描述
內容比較簡單,就直接上代碼了。

	spi0 = (struct SPI *)ioremap(0xc005b000,sizeof(struct SPI));			//映射地址
	spi0->sspcr0 = 0;
	spi0->sspcr0 |= (7 << 0);		//八位模式
	spi0->sspcr0 &= ~(1 << 4);		//spi模式
	spi0->sspcr0 &= ~(1 << 6);		//下降沿發送
	spi0->sspcr0 &= ~(1 << 7);		//空閒低電平
	spi0->sspcr0 |= (0x00 << 8);	//CLK Rate 0~255

	spi0->sspcr1 = 0;
	spi0->sspcr1 &= ~(1 << 0);		//普通傳輸模式
	spi0->sspcr1 |= (1 << 1);		//同步傳輸模式
	spi0->sspcr1 &= ~(1 << 2);		//主機模式
	spi0->sspcr1 |= (1 << 3);		//從機模式不產生時鐘

	spi0->sspcpsr |= 0x04;			//CLK Rate 2~254

	spi0->sspimsc = 0;				//disable interrupt
	spi0->sspicr = 3;				//cls interrupt
	spi0->sspdmacr = 0;				//disable dma

整體代碼

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/vmalloc.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <mach/soc.h>
#include <linux/delay.h>
#include <mach/platform.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/irq.h>
#include <linux/interrupt.h>

MODULE_LICENSE("GPL");

dev_t devid;
struct cdev char_dev;
struct class * char_class;
int buffer_size = 500000;
char * char_data;
const unsigned int WAIT_TIME = 20000;

#pragma pack(4)
static struct SPI{
	volatile unsigned int sspcr0;
	volatile unsigned int sspcr1;
	volatile unsigned int sspdr;
	volatile unsigned int sspsr;
	volatile unsigned int sspcpsr;
	volatile unsigned int sspimsc;
	volatile unsigned int sspris;
	volatile unsigned int sspmis;
	volatile unsigned int sspicr;
	volatile unsigned int sspdmacr;
}* spi0;
static struct SSP_CLK{
	unsigned int enb;
	unsigned int clk;
}* ssp_clk;
#pragma pack()
unsigned int * reset;

inline int spi_busy(void){
	return (spi0->sspsr & (1 << 4));
	// return (spi0->sspsr & (1 << 4)) >> 4;
}

void print_state(void){
	if(spi0->sspsr & (1 << 0)) printk("Transmit FIFO is empty.\n");
	if(spi0->sspsr & (1 << 1)) printk("Transmit FIFO is not full.\n");
	if(spi0->sspsr & (1 << 2)) printk("Receive FIFO is not empty.\n");
	if(spi0->sspsr & (1 << 3)) printk("Receive FIFO is full.\n");
	if(spi0->sspsr & (1 << 4)) printk("SSP is currently transmitting and/or receiving a frame or the transmit FIFO is not empty.\n");
}

inline void write_byte(unsigned char spi_Byte)
{
	volatile int wait_time = 0;
	while(spi_busy() != 0){
		wait_time++;
		if(wait_time > WAIT_TIME){
			printk("out time\n");
			return;
		}
	}
	spi0->sspdr = spi_Byte;
}


static ssize_t write(struct file * fl, const char __user * buf, size_t len, loff_t * offset){
	int ret = 0, copy_len, i, data_len = buffer_size, _offset, let;

	if(fl->f_pos + len > data_len)
		copy_len = data_len - fl->f_pos; //超過長度,複製剩餘部分
	else
		copy_len = len;					 //沒超過

	ret = copy_len;
	_offset = 0;
	while(ret){
		let = copy_from_user(char_data + fl->f_pos + _offset,buf + _offset,ret);
		_offset = _offset + ret - let;
		ret = let;
	}
	ret = copy_len - ret;

	for (i = 0; i < ret; ++i)
	{
		write_byte(* (char_data + fl->f_pos + i));
	}
	// *offset += ret;						 //移動文件指針
	return ret;
}

static long ioctl(struct file * fl, unsigned int cmd, unsigned long arg){
	unsigned int dir,size;
	dir = _IOC_DIR(cmd);
	size = _IOC_SIZE(cmd);

	if(dir == _IOC_WRITE){

	}
	return 0;
}


struct file_operations my_opts = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = ioctl,
	.write = write
};

static int init_dev(void){
	int ret = 0;

    devid = MKDEV(241, 1);								//換算設備號
    ret = register_chrdev_region(devid, 1, "char_test");//註冊設備,在/proc/drivers下面可以看到
    if (ret < 0)
        goto err0;

    cdev_init(&char_dev,&my_opts);						//綁定opt結構體
    char_dev.owner = THIS_MODULE;
    ret = cdev_add(&char_dev,devid,1);					//註冊字符設備驅動
    if (ret < 0)
    	goto err1;

    char_class = class_create(THIS_MODULE,"char_test"); //在/sys/class中創建文件夾
    device_create(char_class,NULL,devid,NULL,"char_test_dev_%d",1);//在上一步文件夾中創建char_test_dev_1

    char_data = vzalloc(buffer_size);

	err1:
	    unregister_chrdev_region(devid, 1);
    err0:
        return ret;
}

static int init_gpio(void){
	nxp_soc_gpio_set_io_func(PAD_GPIO_C + 29, 1);	// 設置gpio功能
	nxp_soc_gpio_set_io_func(PAD_GPIO_C + 30, 1);	// 設置gpio功能
	nxp_soc_gpio_set_io_func(PAD_GPIO_C + 31, 1);	// 設置gpio功能
	nxp_soc_gpio_set_io_func(PAD_GPIO_D + 0, 1);	// 設置gpio功能
	return 0;
}

static int un_init_gpio(void){
	nxp_soc_gpio_set_io_func(PAD_GPIO_C + 29, 0);	// 設置gpio功能
	nxp_soc_gpio_set_io_func(PAD_GPIO_C + 30, 0);	// 設置gpio功能
	nxp_soc_gpio_set_io_func(PAD_GPIO_C + 31, 0);	// 設置gpio功能
	nxp_soc_gpio_set_io_func(PAD_GPIO_D + 0, 0);	// 設置gpio功能
	return 0;
}

static int sys_reset(void){
	*reset |= (1 << 11);
	*reset |= (1 << 12);
	*reset &= ~(1 << 11);
	*reset &= ~(1 << 12);
	udelay(100);
	*reset |= (1 << 11);
	*reset |= (1 << 12);
	ssp_clk->clk = 0;
	ssp_clk->clk &= ~(1 << 1);
	ssp_clk->clk |= (2 << 2);
	ssp_clk->clk |= (2 << 5);
	ssp_clk->clk &= ~(1 << 15);
	ssp_clk->enb &= ~(1 << 2);
	udelay(100);
	ssp_clk->enb |= (1 << 2);
	return 0;
}

static int __init spi_init(void){
	int ret = 0;
	spi0 = (struct SPI *)ioremap(0xc005b000,sizeof(struct SPI));			//映射地址
	reset = (unsigned int *)ioremap(0xc0012004,sizeof(int));			//映射地址
	ssp_clk = (struct SSP_CLK *)ioremap(0xc00ac000,sizeof(struct SSP_CLK));			//映射地址

	ret = init_dev();
	if (ret < 0) return ret;
	ret = sys_reset();
	if (ret < 0) return ret;
	ret = init_gpio();
	if (ret < 0) return ret;

	spi0->sspcr0 = 0;
	spi0->sspcr0 |= (7 << 0);		//八位模式
	spi0->sspcr0 &= ~(1 << 4);		//spi模式
	spi0->sspcr0 &= ~(1 << 6);		//下降沿發送
	spi0->sspcr0 &= ~(1 << 7);		//空閒低電平
	spi0->sspcr0 |= (0x00 << 8);	//CLK Rate 0~255

	spi0->sspcr1 = 0;
	spi0->sspcr1 &= ~(1 << 0);		//普通傳輸模式
	spi0->sspcr1 |= (1 << 1);		//同步傳輸模式
	spi0->sspcr1 &= ~(1 << 2);		//主機模式
	spi0->sspcr1 |= (1 << 3);		//從機模式不產生時鐘

	spi0->sspcpsr |= 0x04;			//CLK Rate 2~254

	spi0->sspimsc = 0;				//disable interrupt
	spi0->sspicr = 3;				//cls interrupt
	spi0->sspdmacr = 0;				//disable dma

	printk("spi init\n");
    return 0;
}

static void __exit spi_exit(void){
	iounmap(spi0);
	iounmap(reset);

	un_init_gpio();

	unregister_chrdev_region(devid, 1);
	cdev_del(&char_dev);
	device_destroy(char_class,devid);
	class_destroy(char_class);
	printk("spi exit\n");
}

module_init(spi_init);
module_exit(spi_exit);

application

#include <stdio.h>
#include <linux/ioctl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include "main.h"

int fd_spi;	//spi
int fd_cs;	//cd
int fd_rs;	//數據/指令切換
int fd_re;	//復位

_lcd_dev lcddev;


void LCD_WR_REG(unsigned char Reg)
{
	char data[] = {
		Reg
	};
	write(fd_rs,"0",1);
	write(fd_cs,"0",1);
	write(fd_spi,data,1);
	write(fd_cs,"1",1);
}

 void LCD_WR_DATA(unsigned char Data)
{
	char data[] = {
		Data
	};
	write(fd_rs,"1",1);
	write(fd_cs,"0",1);
	write(fd_spi,data,1);
	write(fd_cs,"1",1);
}

void LCD_WRITE_DATA(unsigned char * data,int count)
{
	write(fd_rs,"1",1);
	write(fd_cs,"0",1);
	write(fd_spi,data,count);
	write(fd_cs,"1",1);
}

void LCD_WR_DATA_16Bit(unsigned short Data)
{	
  //18Bit
	LCD_WR_DATA((Data>>8)&0xF8);//RED
	LCD_WR_DATA((Data>>3)&0xFC);//GREEN
	LCD_WR_DATA(Data<<3);//BLUE
}

void LCD_WriteReg(unsigned char LCD_Reg, unsigned char LCD_RegValue)
{
  LCD_WR_REG(LCD_Reg);
	LCD_WR_DATA(LCD_RegValue);
}

void LCD_WriteRAM_Prepare(void)
{
 	LCD_WR_REG(lcddev.wramcmd);	  
}

void LCD_SetWindows(unsigned short xStar, unsigned short yStar,unsigned short xEnd,unsigned short yEnd)
{	
	LCD_WR_REG(lcddev.setxcmd);	
	LCD_WR_DATA(xStar>>8);
	LCD_WR_DATA(0x00FF&xStar);		
	LCD_WR_DATA(xEnd>>8);
	LCD_WR_DATA(0x00FF&xEnd);

	LCD_WR_REG(lcddev.setycmd);	
	LCD_WR_DATA(yStar>>8);
	LCD_WR_DATA(0x00FF&yStar);		
	LCD_WR_DATA(yEnd>>8);
	LCD_WR_DATA(0x00FF&yEnd);	

	LCD_WriteRAM_Prepare();	//開始寫入GRAM		
}

void LCD_Clear(unsigned short Color)
{
	unsigned int i,j;
	unsigned char data[lcddev.width * lcddev.height * 3];
	LCD_SetWindows(0,0,lcddev.width-1,lcddev.height-1);	
	for(i=0;i<lcddev.width * lcddev.height;i++)
	{
			 data[i * 3] = (Color>>8)&0xF8;
			 data[i * 3 + 1] = (Color>>3)&0xFC;
			 data[i * 3 + 2] = (Color<<3);
	}
	LCD_WRITE_DATA(data, lcddev.width * lcddev.height * 3);
}

void LCD_DrawPoint(unsigned short x,unsigned short y)
{
	LCD_SetWindows(x,y,x,y);//設置光標位置 
	LCD_WR_DATA(POINT_COLOR); 	    
} 	 

void LCD_SetCursor(unsigned short Xpos, unsigned short Ypos)
{	  	    			
	LCD_SetWindows(Xpos,Ypos,Xpos,Ypos);	
} 

void LCD_direction(unsigned char direction)
{ 
			lcddev.setxcmd=0x2A;
			lcddev.setycmd=0x2B;
			lcddev.wramcmd=0x2C;
	switch(direction){		  
		case 0:						 	 		
			lcddev.width=LCD_W;
			lcddev.height=LCD_H;		
			LCD_WriteReg(0x36,(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;
			LCD_WriteReg(0x36,(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;	
			LCD_WriteReg(0x36,(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;
			LCD_WriteReg(0x36,(1<<3)|(1<<7)|(1<<5));//BGR==1,MY==1,MX==0,MV==1
		break;	
		default:break;
	}		
	
}	 


void LCD_Reset(void)
{
	write(fd_re,"1",1);
	sleep(0.2);
	write(fd_re,"0",1);
	sleep(0.3);
	write(fd_re,"1",1);
	sleep(0.2);
}

void LCD_Init(void)
{
	LCD_Reset(); //初始化之前復位
//************* ILI9488初始化**********//	
	LCD_WR_REG(0XF7);
	LCD_WR_DATA(0xA9);
	LCD_WR_DATA(0x51);
	LCD_WR_DATA(0x2C);
	LCD_WR_DATA(0x82);
	LCD_WR_REG(0xC0);
	LCD_WR_DATA(0x11);
	LCD_WR_DATA(0x09);
	LCD_WR_REG(0xC1);
	LCD_WR_DATA(0x41);
	LCD_WR_REG(0XC5);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x0A);
	LCD_WR_DATA(0x80);
	LCD_WR_REG(0xB1);
	LCD_WR_DATA(0xB0);
	LCD_WR_DATA(0x11);
	LCD_WR_REG(0xB4);
	LCD_WR_DATA(0x02);
	LCD_WR_REG(0xB6);
	LCD_WR_DATA(0x02);
	LCD_WR_DATA(0x42);
	LCD_WR_REG(0xB7);
	LCD_WR_DATA(0xc6);
	LCD_WR_REG(0xBE);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x04);
	LCD_WR_REG(0xE9);
	LCD_WR_DATA(0x00);
	LCD_WR_REG(0x36);
	LCD_WR_DATA((1<<3)|(0<<7)|(1<<6)|(1<<5));
	LCD_WR_REG(0x3A);
	LCD_WR_DATA(0x66);
	LCD_WR_REG(0xE0);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x07);
	LCD_WR_DATA(0x10);
	LCD_WR_DATA(0x09);
	LCD_WR_DATA(0x17);
	LCD_WR_DATA(0x0B);
	LCD_WR_DATA(0x41);
	LCD_WR_DATA(0x89);
	LCD_WR_DATA(0x4B);
	LCD_WR_DATA(0x0A);
	LCD_WR_DATA(0x0C);
	LCD_WR_DATA(0x0E);
	LCD_WR_DATA(0x18);
	LCD_WR_DATA(0x1B);
	LCD_WR_DATA(0x0F);
	LCD_WR_REG(0XE1);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x17);
	LCD_WR_DATA(0x1A);
	LCD_WR_DATA(0x04);
	LCD_WR_DATA(0x0E);
	LCD_WR_DATA(0x06);
	LCD_WR_DATA(0x2F);
	LCD_WR_DATA(0x45);
	LCD_WR_DATA(0x43);
	LCD_WR_DATA(0x02);
	LCD_WR_DATA(0x0A);
	LCD_WR_DATA(0x09);
	LCD_WR_DATA(0x32);
	LCD_WR_DATA(0x36);
	LCD_WR_DATA(0x0F);
	LCD_WR_REG(0x11);
	sleep(0.2);
	LCD_WR_REG(0x29);

	//設置LCD屬性參數
	LCD_direction(USE_HORIZONTAL);//設置LCD顯示方向
}

int export_gpio(int no){
	char cmd[100];
	int ret = 0;
	sprintf(cmd,"echo %d > /sys/class/gpio/export",no);
	ret = system(cmd);
	if(ret != 0) return ret;
	sprintf(cmd,"echo out > /sys/class/gpio/gpio%d/direction",no);
	ret = system(cmd);
	return ret;
}

int init_cs(void)
{
	return export_gpio(68);
}

int init_rs(void)
{
	return export_gpio(71);
}

int init_re(void)
{
	return export_gpio(72);
}

int main(){
	int ret,cmd,i;
	if(access(CS_GPIO,F_OK) == -1){
		ret = init_cs();
	}
	if(ret != 0){
		perror("export gpio 68 error");
		return ret;
	}
	if(access(RS_GPIO,F_OK) == -1){
		ret = init_rs();
	}
	if(ret != 0){
		perror("export gpio 71 error");
		return ret;
	}
	if(access(RE_GPIO,F_OK) == -1){
		ret = init_re();
	}
	if(ret != 0){
		perror("export gpio 72 error");
		return ret;
	}

	fd_rs = open(RS_GPIO,O_RDWR);
	ret = fd_rs;
	if(ret < 0){
		perror("open");
		return ret;
	}
	fd_cs = open(CS_GPIO,O_RDWR);
	ret = fd_cs;
	if(ret < 0){
		perror("open");
		return ret;
	}
	fd_re = open(RE_GPIO,O_RDWR);
	ret = fd_re;
	if(ret < 0){
		perror("open");
		return ret;
	}

	fd_spi = open("/dev/char_test_dev_1",O_RDWR);
	ret = fd_spi;
	if(ret < 0){
		perror("open /dev/char_test_dev_1 error");
		return ret;
	}
	LCD_Init();
	for (i = 0; i < 1; ++i)
	{
		LCD_Clear(RED);
		LCD_Clear(GREEN);
		LCD_Clear(BLUE);
		LCD_Clear(RED);
	}
	close(fd_rs);
	close(fd_cs);
	close(fd_spi);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章