NanoPi NEO Air使用十一:编写SPI驱动点亮TFT屏幕,ST7789V

NanoPi NEO Air使用一:介绍
NanoPi NEO Air使用二:固件烧录
NanoPi NEO Air使用三:OverlayFS、CPU温度和频率、wifi、蓝牙、npi-config
NanoPi NEO Air使用四:操作GPIO
NanoPi NEO Air使用五:安装Xfce和xrdp,实现远程访问
NanoPi NEO Air使用六:使用摄像头
NanoPi NEO Air使用七:获取并编译U-boot和Linux的源码
NanoPi NEO Air使用八:编写个简单的驱动和应用程序
NanoPi NEO Air使用九:使用Linux内核自带的LED驱动
NanoPi NEO Air使用十:自己编写驱动来控制LED
NanoPi NEO Air使用十一:编写SPI驱动点亮TFT屏幕,ST7789V

开发板引出来了spi0
在这里插入图片描述
本节用spi0驱动一个spi接口的屏幕,屏幕如下:
在这里插入图片描述
240x240分辨率,1.3寸,主控为ST7789V。
在这里插入图片描述
与开发板的引脚连接确定如下:

功能IO
GNDPin6
5VPin2
LCD_RESETPin7-PG11
LCD_DCPin22-PA1
SPICLKPin23-PC2
SPIMOSIPin19-PC0

修改设备树

spi0节点定义在/home/ql/linux/H3/linux/arch/arm/boot/dts/sun8i-h3-nanopi.dtsi文件中,修改为:

&spi0 {
	/* needed to avoid dtc warning */
	#address-cells = <1>;
	#size-cells = <0>;

	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
	cs-gpios = <&pio 2 3 GPIO_ACTIVE_HIGH>, <&pio 0 6 GPIO_ACTIVE_HIGH>; /*SPI-CS:PC3 and PA6*/

	pitft: pitft@0{
		compatible = "testspiTFT";
		reg = <0>;
		status = "okay";

		spi-max-frequency = <50000000>;
		rotate = <90>;
		fps = <33>;
		buswidth = <8>;
		debug = <0x0>;
	};
};

  spi0节点的pinctrl-0属性来指示使用的引脚和功能,它的值为<&spi0_pins &spi0_cs_pins>,spi0_pins是pio的子节点,定义如下(在arch/arm/boot/dts/sunxi-h3-h5.dtsi中):

		pio: pinctrl@01c20800 {
			/* compatible is in per SoC .dtsi file */
			reg = <0x01c20800 0x400>;
			interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>,
				     <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
			clocks = <&ccu CLK_BUS_PIO>, <&osc24M>, <&osc32k>;
			clock-names = "apb", "hosc", "losc";
			gpio-controller;
			#gpio-cells = <3>;
			interrupt-controller;
			#interrupt-cells = <3>;

			csi_pins: csi {
				pins = "PE0", "PE1", "PE2", "PE3", "PE4",
				       "PE5", "PE6", "PE7", "PE8", "PE9",
				       "PE10", "PE11";
				function = "csi";
			};

......

			spi0_pins: spi0 {
				pins = "PC0", "PC1", "PC2", "PC3";
				function = "spi0";
			};

			spi1_pins: spi1 {
				pins = "PA15", "PA16", "PA14", "PA13";
				function = "spi1";
			};

			uart0_pins_a: uart0@0 {
				pins = "PA4", "PA5";
				function = "uart0";
			};
......
		};

spi0_cs_pins在下面和DC引脚、RESET引脚定义在一起:
在根节点下添加DC引脚和RESET引脚的节点:

	testTFTRes {
		compatible = "testTFTRes";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_testTFTRes>;
		gpios = <&pio 6 11 GPIO_ACTIVE_HIGH>; /* PG11 */
		status = "okay";    
	};

	testTFTDc {
		compatible = "testTFTDc";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_testTFTDc>;
		gpios = <&pio 0 1 GPIO_ACTIVE_HIGH>; /* PA1 */
		status = "okay";    
	};

在pio下添加pinctrl_testTFTRes、pinctrl_testTFTDc、spi0_cs_pins:

&pio {
	leds_npi: led_pins {
		pins = "PA10";
		function = "gpio_out";
	};

	pinctrl_testTFTRes: testTFTRes_pins {
		pins = "PG11";
		function = "gpio_out";
	};

	pinctrl_testTFTDc: testTFTDc_pins {
		pins = "PA1";
		function = "gpio_out";
	};

	spi0_cs_pins: spi0_cs_pins {
		pins = "PC3", "PA6";
		function = "gpio_out";
	};
};

修改完成后,可以在设备树文件中搜索“PG11”和“PA1”,看看有没有其他地方在使用这些IO。
/home/ql/linux/H3/linux/arch/arm/boot/dts/sun8i-h3-nanopi.dtsi文件最终修改为:

/*
 * Copyright (C) 2016 James Pettigrew <james@innovum.com.au>
 * Copyright (C) 2016 Milo Kim <woogyom.kim@gmail.com>
 *
 * This file is dual-licensed: you can use it either under the terms
 * of the GPL or the X11 license, at your option. Note that this dual
 * licensing only applies to this file, and not this project as a
 * whole.
 *
 *  a) This file is free software; you can redistribute it and/or
 *     modify it under the terms of the GNU General Public License as
 *     published by the Free Software Foundation; either version 2 of the
 *     License, or (at your option) any later version.
 *
 *     This file is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 * Or, alternatively,
 *
 *  b) Permission is hereby granted, free of charge, to any person
 *     obtaining a copy of this software and associated documentation
 *     files (the "Software"), to deal in the Software without
 *     restriction, including without limitation the rights to use,
 *     copy, modify, merge, publish, distribute, sublicense, and/or
 *     sell copies of the Software, and to permit persons to whom the
 *     Software is furnished to do so, subject to the following
 *     conditions:
 *
 *     The above copyright notice and this permission notice shall be
 *     included in all copies or substantial portions of the Software.
 *
 *     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 *     OTHER DEALINGS IN THE SOFTWARE.
 */

/dts-v1/;
#include "sun8i-h3.dtsi"
#include "sunxi-common-regulators.dtsi"

#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
#include <dt-bindings/pinctrl/sun4i-a10.h>
#include <dt-bindings/thermal/thermal.h>

/ {
	aliases {
		serial0 = &uart0;
		serial1 = &uart1;
		serial2 = &uart2;
		serial3 = &uart3;
		i2c0 = &i2c0;
		i2c1 = &i2c1;
		i2c2 = &i2c2;
		spi0 = &spi0;
		spi1 = &spi1;
		pwm0 = &pwm;
		mmc0 = &mmc0;
		mmc2 = &mmc2;
		ethernet0 = &emac;
		i2s0 = &i2s0;
		pcm5102a = &pcm5102a;
		//spidev0 = &spidev0;
		//spiflash = &spiflash;
		pitft = &pitft;
		//pitft_ts = &pitft_ts;
		ir = &ir;
	};

	chosen {
		stdout-path = "serial0:115200n8";
	};

	connector {
		compatible = "hdmi-connector";
		type = "a";

		port {
			hdmi_con_in: endpoint {
				remote-endpoint = <&hdmi_out_con>;
			};
		};
	};
	
	/*leds {
		compatible = "gpio-leds";
		pinctrl-names = "default";
		pinctrl-0 = <&leds_npi>, <&leds_r_npi>;

		status {
			label = "status_led";
			gpios = <&pio 0 10 GPIO_ACTIVE_HIGH>;
			linux,default-trigger = "heartbeat";
		};

		pwr {
			label = "LED2";
			gpios = <&r_pio 0 10 GPIO_ACTIVE_HIGH>;
			default-state = "on";
		};
	};*/   

	testleds {
		compatible = "test-gpio-leds";
		pinctrl-names = "default";
		pinctrl-0 = <&leds_npi>;
		gpios = <&pio 0 10 GPIO_ACTIVE_HIGH>;
		status = "okay";    
	};

	testTFTRes {
		compatible = "testTFTRes";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_testTFTRes>;
		gpios = <&pio 6 11 GPIO_ACTIVE_HIGH>; /* PG11 */
		status = "okay";    
	};

	testTFTDc {
		compatible = "testTFTDc";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_testTFTDc>;
		gpios = <&pio 0 1 GPIO_ACTIVE_HIGH>; /* PA1 */
		status = "okay";    
	};

	r_gpio_keys {
		compatible = "gpio-keys";
		input-name = "k1";
		pinctrl-names = "default";
		pinctrl-0 = <&sw_r_npi>;

		k1 {
			label = "k1";
			linux,code = <KEY_POWER>;
			gpios = <&r_pio 0 3 GPIO_ACTIVE_LOW>;
		};
	};

	vdd_cpux: gpio-regulator {
		compatible = "regulator-gpio";

		regulator-name = "vdd-cpux";
		regulator-type = "voltage";
		regulator-boot-on;
		regulator-always-on;
		regulator-min-microvolt = <1100000>;
		regulator-max-microvolt = <1300000>;
		regulator-ramp-delay = <50>; /* 4ms */

		gpios = <&r_pio 0 6 GPIO_ACTIVE_HIGH>;
		gpios-states = <0x1>;
		states = <1100000 0x0
			  1300000 0x1>;
	};

	pcm5102a: pcm5102a-codec {
		#sound-dai-cells = <0>;
		compatible = "ti,pcm5102a";
		status = "disabled";
	};

	sound_i2s {
		compatible = "simple-audio-card";
		simple-audio-card,name = "I2S-master";
		simple-audio-card,mclk-fs = <256>;
		simple-audio-card,format = "i2s";
		status = "okay";

		simple-audio-card,cpu {
			sound-dai = <&i2s0>;
		};

		simple-audio-card,codec {
			sound-dai = <&pcm5102a>;
		};
	};

	reg_vcc1v2: vcc1v2 {
		compatible = "regulator-fixed";
		regulator-name = "vcc1v2";
		regulator-min-microvolt = <1200000>;
		regulator-max-microvolt = <1200000>;
		regulator-always-on;
		regulator-boot-on;
		vin-supply = <&reg_vcc5v0>;
		gpio = <&r_pio 0 8 GPIO_ACTIVE_HIGH>; /* PL8 */
		enable-active-high;
	};

	reg_vcc_dram: vcc-dram {
		compatible = "regulator-fixed";
		regulator-name = "vcc-dram";
		regulator-min-microvolt = <1500000>;
		regulator-max-microvolt = <1500000>;
		regulator-always-on;
		regulator-boot-on;
		vin-supply = <&reg_vcc5v0>;
		gpio = <&r_pio 0 9 GPIO_ACTIVE_HIGH>; /* PL9 */
		enable-active-high;
	};

	reg_vdd_cpux: vdd-cpux {
		compatible = "regulator-fixed";
		regulator-name = "vdd-cpux-en";
		regulator-min-microvolt = <1200000>;
		regulator-max-microvolt = <1200000>;
		regulator-always-on;
		regulator-boot-on;
		vin-supply = <&reg_vcc5v0>;
		gpio = <&r_pio 0 8 GPIO_ACTIVE_HIGH>; /* PL8 */
		enable-active-high;
	};
};

&cpu0 {
	operating-points = <
		1008000	1300000
		816000	1100000
		624000	1100000
		480000	1100000
		>;
	#cooling-cells = <2>;
	cooling-min-level = <0>;
	cooling-max-level = <3>;
	cpu0-supply = <&vdd_cpux>;
};

&cpu_thermal {
	trips {
		cpu_warm: cpu_warm {
			temperature = <60000>;
			hysteresis = <2000>;
			type = "passive";
		};
		cpu_hot: cpu_hot {
			temperature = <70000>;
			hysteresis = <2000>;
			type = "passive";
		};
		cpu_very_hot: cpu_very_hot {
			temperature = <80000>;
			hysteresis = <2000>;
			type = "passive";
		};
		cpu_crit: cpu_crit {
			temperature = <100000>;
			hysteresis = <2000>;
			type = "critical";
		};
	};

	cooling-maps {
		cpu_warm_limit_cpu {
			trip = <&cpu_warm>;
			cooling-device = <&cpu0 THERMAL_NO_LIMIT 1>;
		};
		cpu_hot_limit_cpu {
			trip = <&cpu_hot>;
			cooling-device = <&cpu0 THERMAL_NO_LIMIT 2>;
		};
		cpu_very_hot_limit_cpu {
			trip = <&cpu_very_hot>;
			cooling-device = <&cpu0 3 THERMAL_NO_LIMIT>;
		};
	};
};

&ehci0 {
	status = "okay";
};

&ohci0 {
	status = "okay";
};

&ehci1 {
	status = "okay";
};

&ohci1 {
	status = "okay";
};

&ehci2 {
	status = "okay";
};

&ohci2 {
	status = "okay";
};

&ehci3 {
	status = "okay";
};

&ohci3 {
	status = "okay";
};

&mmc0 {
	bus-width = <4>;
	non-removable;
	pinctrl-names = "default";
	pinctrl-0 = <&mmc0_pins_a>, <&mmc0_cd_pin>;
	boot_device = <0>;
	status = "okay";
	vmmc-supply = <&reg_vcc3v3>;
};

&mmc2 {
	boot_device = <0>;
};

&pio {
	leds_npi: led_pins {
		pins = "PA10";
		function = "gpio_out";
	};

	pinctrl_testTFTRes: testTFTRes_pins {
		pins = "PG11";
		function = "gpio_out";
	};

	pinctrl_testTFTDc: testTFTDc_pins {
		pins = "PA1";
		function = "gpio_out";
	};

	spi0_cs_pins: spi0_cs_pins {
		pins = "PC3", "PA6";
		function = "gpio_out";
	};
};

&r_pio {
	leds_r_npi: led_pins {
		pins = "PL10";
		function = "gpio_out";
	};

	sw_r_npi: key_pins {
		pins = "PL3";
		function = "gpio_in";
	};
};

&uart0 {
	pinctrl-names = "default";
	pinctrl-0 = <&uart0_pins_a>;
	status = "okay";
};

&uart1 {
	pinctrl-names = "default";
	pinctrl-0 = <&uart1_pins>;
	status = "okay";
};

&uart2 {
	pinctrl-names = "default";
	pinctrl-0 = <&uart2_pins>;
	status = "okay";
};

&uart3 {
	pinctrl-names = "default";
	pinctrl-0 = <&uart3_pins>, <&uart3_rts_cts_pins>;
	status = "okay";
};

&i2c0 {
	status = "okay";
	rtc@68 {
		compatible = "dallas,ds1307";
		reg = <0x68>;
		};
};

&i2c1 {
	status = "okay";
};

&i2c2 {
	status = "okay";
};

&spi0 {
	/* needed to avoid dtc warning */
	#address-cells = <1>;
	#size-cells = <0>;

	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
	cs-gpios = <&pio 2 3 GPIO_ACTIVE_HIGH>, <&pio 0 6 GPIO_ACTIVE_HIGH>; /*SPI-CS:PC3 and PA6*/

	pitft: pitft@0{
		compatible = "testspiTFT";
		reg = <0>;
		status = "okay";

		spi-max-frequency = <50000000>;
		rotate = <90>;
		fps = <33>;
		buswidth = <8>;
		debug = <0x0>;
	};
};

&spi1 {
	// against uart3_rts_cts_pins, so disable
	status = "disable";
	spidev1: spi@1 {
		compatible = "nanopi,spidev";
		reg = <0>;
		spi-max-frequency = <10000000>;
	};
};

&de {
	status = "okay";
};

&hdmi {
	/*status = "okay";*/
	status = "disable";
};

&hdmi_out {
	hdmi_out_con: endpoint {
		remote-endpoint = <&hdmi_con_in>;
	};
};

&sound_hdmi {
	/*status = "okay";*/
	status = "disable";
};

&tcon0 {
	status = "okay";
};

&mixer0 {
	status = "okay";
};

&i2s0 {
	sound-dai = <&pcm5102a>;
	status = "disabled";
};

&i2s2 {
	status = "okay";
};

&emac {
	local-mac-address = [ 00 00 00 00 00 00 ];	
};

&codec {
	allwinner,audio-routing =
		"Line Out", "LINEOUT",
		"MIC1", "Mic",
		"Mic",  "MBIAS";
	status = "okay";
};

&pwm {
	pinctrl-names = "default";
	pinctrl-0 = <&pwm0_pins>;
	status = "disabled";
};

&reg_usb0_vbus {
	gpio = <&r_pio 0 2 GPIO_ACTIVE_HIGH>; /* PL2 */
	status = "okay";
};

&usb_otg {
	// OTG is not stable.
	// most nanopi-h3's MicroUSB support OTG, except:
	// 1. nanopi-neo-V1.4 support OTG, nanopi-neo-V1.3/1.2... support USB device.
	// 2. nanopi-k1 only use as power.
	dr_mode = "otg";
	status = "okay";
};

&usbphy {
	usb0_id_det-gpios = <&pio 6 12 GPIO_ACTIVE_HIGH>; /* PG12 */
	usb0_vbus-supply = <&reg_usb0_vbus>;
	status = "okay";
};

&ir {
	pinctrl-names = "default";
	pinctrl-0 = <&ir_pins_a>;
	status = "disabled";
};

编写驱动

编写驱动需要设备树中节点的路径,我们上面用到的testTFTRes和testTFTDc节点都在根节点下,因此他们的路径为“/testTFTRes”、"/testTFTDc"
spi0节点就需要找一下了,最终在arch/arm/boot/dts/sunxi-h3-h5.dtsi中找到:
在这里插入图片描述
spi0节点在soc节点下面,因此路径为“/soc/spi@01c68000”

设备树路径从系统里面也可以看出来:
在这里插入图片描述

新建一个目录,在此目录下添加spiTFT.c文件,内容为:

#include <linux/types.h> 
#include <linux/kernel.h> 
#include <linux/delay.h> 
#include <linux/ide.h> 
#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/errno.h> 
#include <linux/gpio.h> 
#include <linux/cdev.h> 
#include <linux/device.h> 
#include <linux/of_gpio.h> 
#include <linux/semaphore.h> 
#include <linux/timer.h> 
#include <linux/i2c.h> 
#include <linux/spi/spi.h> 
#include <linux/of.h> 
#include <linux/of_address.h> 
#include <linux/of_gpio.h> 
#include <linux/platform_device.h> 
#include <asm/mach/map.h> 
#include <asm/uaccess.h> 
#include <asm/io.h> 
#include <linux/spi/spi.h> 

#define ipsTft_CNT	1 
#define ipsTft_NAME	"ipsTft" 

#define LCD_W 240 
#define LCD_H 240 

#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  

// u8 buf[9] = { 
// 	RED, GREEN, BLUE, WHITE, BLACK, YELLOW, GRAY, BRRED, CYAN 
// }; 

struct ipsTft_dev { 
	dev_t devid;				/* 设备号 */ 
	struct cdev cdev;			/* cdev */ 
	struct class *class;		/* 类 */ 
	struct device *device;		/* 设备 */ 
	struct device_node	*nd; 	/* 设备节点 */ 
	int major;					/* 主设备号 */ 
	void *private_data;			/* 私有数据 */ 
	int dc_gpio;				/* 片选所使用的GPIO编号 */ 
    int res_gpio;               /* ips屏幕复位引脚 */ 
    int cs_gpio;                /* 共用磁力计的cs */ 
}; 

static struct ipsTft_dev ipsTftdev; 

void ipsTft_reginit(struct ipsTft_dev *dev); 

//1.3寸屏幕 
struct spi_lcd_cmd { 
    u8  reg_addr;  // command 
    u8  len;       //需要从spi_lcd_datas数组里发出数据字节数 
    int delay_ms;  //此命令发送数据完成后,需延时多久 
};

struct spi_lcd_cmd cmds[] = { 
    {0x36, 1, 30}, 
    {0x3A, 1, 30}, 
    {0xB2, 5, 30}, 
    {0xB7, 1, 30}, 
    {0xBB, 1, 30}, 
    {0xC0, 1, 30}, 
    {0xC2, 1, 30}, 
    {0xC3, 1, 30}, 
    {0xC4, 1, 30}, 
    {0xC6, 1, 30}, 
    {0xD0, 2, 30}, 
    {0xE0, 14, 30}, 
    {0xE1, 14, 30}, 
    {0x21, 0, 30}, 
    {0x11, 0, 120}, 
    {0x29, 0, 30}, 
}; 

u8 spi_lcd_datas[] = { 
    0x00, 
    0x05, 
    0x0c,0x0c,0x00,0x33,0x33, 
    0x35, 
    0x19, 
    0x2c, 
    0x01, 
    0x12, 
    0x20, 
    0x0F, 
    0xA4,0xA1, 
    0xD0,0x04,0x0D,0x11,0x13,0x2B,0x3F,0x54,0x4C,0x18,0x0D,0x0B,0x1F,0x23, 
    0xD0,0x04,0x0C,0x11,0x13,0x2C,0x3F,0x44,0x51,0x2F,0x1F,0x1F,0x20,0x23 
}; 
/* 
 * @description	: 向ipsTft多个寄存器写入数据 
 * @param - dev:  ipsTft设备 
 * @param - reg:  要写入的寄存器首地址 
 * @param - val:  要写入的数据缓冲区 
 * @param - len:  要写入的数据长度 
 * @return 	  :   操作结果 
 */ 
static s32 ipsTft_write_regs(struct ipsTft_dev *dev,u8 *buf, u8 len) 
{ 
	int ret; 
	struct spi_message m; 
	struct spi_transfer *t; 
	struct spi_device *spi = (struct spi_device *)dev->private_data; 
	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */ 
	t->tx_buf = buf;			/* 要写入的数据 */ 
	t->len = len;				/* 写入的字节数 */ 
	spi_message_init(&m);		/* 初始化spi_message */ 
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ 
	ret = spi_sync(spi, &m);	/* 同步发送 */ 
	kfree(t);					/* 释放内存 */ 
	return ret; 
} 
/* 
 * @description	: 向ipsTft指定寄存器写入指定的值,写一个寄存器 
 * @param - dev:  ipsTft设备 
 * @param - reg:  要写的寄存器 
 * @param - data: 要写入的值 
 * @return   :    无 
 */ 
static void ipsTft_write_onereg(struct ipsTft_dev *dev, u8 buf) 
{ 
	ipsTft_write_regs(dev,&buf, 1); 
    //spi_write(dev,&buf, 1); 
} 
/* 
    funciton: 写一个命令 
*/ 
void write_command(struct ipsTft_dev *dev, u8 cmd) 
{ 
    // dc , command:0 
    gpio_set_value(dev->dc_gpio, 0); 
    ipsTft_write_onereg(dev,cmd); 
} 
/* 
    funciton: 写一个数据 
*/ 
void write_data(struct ipsTft_dev *dev, u8 data) 
{ 
    gpio_set_value(dev->dc_gpio, 1); 
    ipsTft_write_onereg(dev,data); 
} 
/* 
    funciton: 写一些数据 
*/ 
static void write_datas(struct ipsTft_dev *dev, int data,int len) 
{ 
    gpio_set_value(dev->dc_gpio, 1); 
    ipsTft_write_regs(dev,(u8 *)&data,len); 
} 
/* 
 * @description		: 打开设备 
 * @param - inode 	: 传递给驱动的inode 
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的私有成员变量 
 * 					  一般在open的时候将private_data向私有设备结构体赋值。 
 * @return 			: 0 成功;其他 失败 
 */ 
static int ipsTft_open(struct inode *inode, struct file *filp) 
{ 
	filp->private_data = &ipsTftdev; /* 设置私有数据 */ 
	// TODO something
	return 0; 
} 
/* 
 * @description		: 关闭/释放设备 
 * @param - filp 	: 要关闭的设备文件(文件描述符) 
 * @return 			: 0 成功;其他 失败 
 */ 
static int ipsTft_release(struct inode *inode, struct file *filp) 
{ 
	return 0; 
} 
/* ipsTft操作函数 */ 
static const struct file_operations ipsTft_ops = { 
	.owner = THIS_MODULE, 
	.open = ipsTft_open, 
	.release = ipsTft_release, 
}; 

void Address_set(struct ipsTft_dev *dev,unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2) 
{ 
    write_command(dev,0x2a); 
    write_data(dev,x1>>8); 
    write_data(dev,x1); 
    write_data(dev,x2>>8); 
    write_data(dev,x2); 
    write_command(dev,0x2b); 
    write_data(dev,y1>>8); 
    write_data(dev,y1); 
    write_data(dev,y2>>8); 
    write_data(dev,y2); 
    write_command(dev,0x2C); 
} 
/* 
    刷屏函数 
*/ 
void LCD_Clear(struct ipsTft_dev *dev,u16 Color) 
{ 
	u16 i,j; 
	Address_set(dev,0,0,LCD_W-1,LCD_H-1); 
	write_command(dev,0x2C); 
	for(i=0;i<LCD_W;i++) 
	{ 
		for (j=0;j<LCD_H;j++) 
		{ 
			//write_datas(dev,0xF800,2);	    //全红 
			write_data(dev,Color>>8); 
			write_data(dev,Color); 
		} 
	} 
} 
/* 
 * ipsTft内部寄存器初始化函数 
 * @param  	: 无 
 * @return 	: 无 
 */ 
void ipsTft_reginit(struct ipsTft_dev *dev) 
{ 
    int i, j, n; 
    gpio_set_value(ipsTftdev.res_gpio, 0); 
    mdelay(20); 
    gpio_set_value(ipsTftdev.res_gpio, 1); 
    mdelay(20); 
    n = 0; // n用于记录数据数组spi_lcd_datas的位置 
    //发命令,并发出命令所需的数据 
    for (i = 0; i < ARRAY_SIZE(cmds); i++) //命令 
    { 
        write_command(dev, cmds[i].reg_addr); 
        for (j = 0; j < cmds[i].len; j++) //发出命令后,需要发出的数据 
            if(cmds[i].len!=0) 
                write_data(dev, spi_lcd_datas[n++]); 
        if (cmds[i].delay_ms) //如有延时则延时 
            mdelay(cmds[i].delay_ms); 
    } 
    n=0; 
    LCD_Clear(dev,RED); // 安装驱动模块的时候刷个红色的屏
    printk("ips init finish!\n"); 
} 
 /* 
  * @description     : spi驱动的probe函数,当驱动与 
  *                    设备匹配以后此函数就会执行 
  * @param - client  : spi设备 
  * @param - id      : spi设备ID 
  * 
  */ 
static int ipsTft_probe(struct spi_device *spi) 
{ 
	int ret = 0; 
	
	printk("TFT driver and device was matched!\r\n");
	
	/* 1、构建设备号 */ 
	if (ipsTftdev.major) { 
		ipsTftdev.devid = MKDEV(ipsTftdev.major, 0); 
		register_chrdev_region(ipsTftdev.devid, ipsTft_CNT, ipsTft_NAME); 
	} else { 
		alloc_chrdev_region(&ipsTftdev.devid, 0, ipsTft_CNT, ipsTft_NAME); 
		ipsTftdev.major = MAJOR(ipsTftdev.devid); 
	} 
	/* 2、注册设备 */ 
	cdev_init(&ipsTftdev.cdev, &ipsTft_ops); 
	cdev_add(&ipsTftdev.cdev, ipsTftdev.devid, ipsTft_CNT); 
	/* 3、创建类 */ 
	ipsTftdev.class = class_create(THIS_MODULE, ipsTft_NAME); 
	if (IS_ERR(ipsTftdev.class)) { 
		return PTR_ERR(ipsTftdev.class); 
	} 
	/* 4、创建设备 */ 
	ipsTftdev.device = device_create(ipsTftdev.class, NULL, ipsTftdev.devid, NULL, ipsTft_NAME); 
	if (IS_ERR(ipsTftdev.device)) { 
		return PTR_ERR(ipsTftdev.device); 
	} 
	/* 获取设备树中的 spi0 节点 */
	ipsTftdev.nd = of_find_node_by_path("/soc/spi@01c68000"); 
	if(ipsTftdev.nd == NULL) { 
		printk("spi0 node not find!\r\n"); 
		return -EINVAL; 
	} 
	
	/* 获取设备树中 spi0 节点的 cs-gpios 属性,得到 CS 所使用的 GPIO 编号 */ 
	ipsTftdev.cs_gpio = of_get_named_gpio(ipsTftdev.nd, "cs-gpios", 0); 
	if(ipsTftdev.cs_gpio < 0) { 
		printk("can't get cs-gpios"); 
		return -EINVAL; 
	} 
	/* 获取设备树中 testTFTRes 节点 */ 
    ipsTftdev.nd = of_find_node_by_path("/testTFTRes"); 
	if(ipsTftdev.nd == NULL) { 
		printk("res-gpio node not find!\r\n"); 
		return -EINVAL; 
    } 
	/* 获取设备树中 testTFTRes 节点的 gpios 属性,得到 RES 所使用的 GPIO 编号 */ 
    ipsTftdev.res_gpio = of_get_named_gpio(ipsTftdev.nd, "gpios", 0); 
    if(ipsTftdev.res_gpio < 0) { 
		printk("can't get res-gpio"); 
		return -EINVAL; 
	} 
	/* 获取设备树中 testTFTDc 节点 */ 
    ipsTftdev.nd = of_find_node_by_path("/testTFTDc"); 
	if(ipsTftdev.nd == NULL) { 
		printk("ipsDcgpio node not find!\r\n"); 
		return -EINVAL; 
    } 
	/* 获取设备树中 testTFTDc 节点的 gpios 属性,得到 RES 所使用的 GPIO 编号 */ 
    ipsTftdev.dc_gpio = of_get_named_gpio(ipsTftdev.nd, "gpios", 0); 
    if(ipsTftdev.dc_gpio < 0) { 
		printk("can't get ipsDc-gpio"); 
		return -EINVAL; 
	} 
	/* 设置GPIO1_IO20为输出,并且输出高电平 */ 
	ret = gpio_direction_output(ipsTftdev.cs_gpio, 1); 
	if(ret < 0) { 
		printk("can't set cs gpio!\r\n"); 
	} 
    ret = gpio_direction_output(ipsTftdev.res_gpio, 1); 
	if(ret < 0) { 
		printk("can't set res gpio!\r\n"); 
	} 
    ret = gpio_direction_output(ipsTftdev.dc_gpio, 1); 
	if(ret < 0) { 
		printk("can't set dc gpio!\r\n"); 
	} 
	/*初始化spi_device */ 
	spi->mode = SPI_MODE_2;	/*MODE0,CPOL=0,CPHA=0 */
	spi_setup(spi); 
	ipsTftdev.private_data = spi; /* 设置私有数据 */ 
	/* 初始化ipsTft内部寄存器 */ 
	ipsTft_reginit(&ipsTftdev); 
	return 0; 
} 
/* 
 * @description     : spi驱动的remove函数,移除spi驱动的时候此函数会执行 
 * @param - client 	: spi设备 
 * @return          : 0,成功;其他负值,失败 
 */ 
static int ipsTft_remove(struct spi_device *spi) 
{ 
	/* 删除设备 */ 
	cdev_del(&ipsTftdev.cdev); 
	unregister_chrdev_region(ipsTftdev.devid, ipsTft_CNT); 
	/* 注销掉类和设备 */ 
	device_destroy(ipsTftdev.class, ipsTftdev.devid); 
	class_destroy(ipsTftdev.class); 
	return 0; 
} 

/* 设备树匹配列表 */ 
static const struct of_device_id ipsTft_of_match[] = { 
	{ .compatible = "testspiTFT" }, 
	{ /* Sentinel */ } 
}; 
/* SPI驱动结构体 */ 
static struct spi_driver ipsTft_driver = { 
	.probe = ipsTft_probe, 
	.remove = ipsTft_remove, 
	.driver = { 
		   	.name = "ipsTft",                  /* 驱动名字,用于和设备匹配 */
		   	.of_match_table = ipsTft_of_match, /* 设备树匹配表 		 */ 
		   }, 
}; 
/* 
 * @description	: 驱动入口函数 
 * @param 		: 无 
 * @return 		: 无 
 */ 
static int __init ipsTft_init(void) 
{ 
	return spi_register_driver(&ipsTft_driver); 
} 
/* 
 * @description	: 驱动出口函数 
 * @param 		: 无 
 * @return 		: 无 
 */ 
static void __exit ipsTft_exit(void) 
{ 
	spi_unregister_driver(&ipsTft_driver); 
} 
module_init(ipsTft_init); 
module_exit(ipsTft_exit); 
MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("qlexcel"); 

添加Makefile

再次目录下添加Makefile文件,内容为:

KERNELDIR := /home/ql/linux/H3/linux
CURRENT_PATH := $(shell pwd)

obj-m := spiTFT.o

build: kernel_modules

kernel_modules:
	make ARCH=arm CROSS_COMPILE=arm-linux- -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	make ARCH=arm CROSS_COMPILE=arm-linux- -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译

上面2个文件添加完成后,目录结构如下:
在这里插入图片描述
在该目录下执行make编译驱动。
执行如下命令编译设备树:

cd /home/ql/linux/H3/linux
make dtbs ARCH=arm CROSS_COMPILE=arm-linux-

测试

把设备树、驱动传到开发板上,命令如下:

scp /home/ql/linux/H3/linux/arch/arm/boot/dts/sun8i-h3-nanopi-neo-air.dtb root@192.168.0.103:/boot
scp /home/ql/linux/H3/MyDriver/03_spiTFT/spiTFT.ko root@192.168.0.103:/lib/modules/4.14.111/

传好后,重启开发板。
使用如下命令更改控制台消息等级:

echo 5 >/proc/sys/kernel/printk
cat /proc/sys/kernel/printk

进入/lib/modules/4.14.111目录,执行insmod spiTFT.ko加载驱动,如果正常就可以看到如下输出:
在这里插入图片描述
同时屏幕被刷成红色。

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

【ql君】qlexcel

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值