天天看點

NanoPi NEO Air使用十一:編寫SPI驅動點亮TFT螢幕,ST7789V修改裝置樹編寫驅動添加Makefile編譯測試

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

NanoPi NEO Air使用十一:編寫SPI驅動點亮TFT螢幕,ST7789V修改裝置樹編寫驅動添加Makefile編譯測試

本節用spi0驅動一個spi接口的螢幕,螢幕如下:

NanoPi NEO Air使用十一:編寫SPI驅動點亮TFT螢幕,ST7789V修改裝置樹編寫驅動添加Makefile編譯測試

240x240分辨率,1.3寸,主要為ST7789V。

NanoPi NEO Air使用十一:編寫SPI驅動點亮TFT螢幕,ST7789V修改裝置樹編寫驅動添加Makefile編譯測試

與開發闆的引腳連接配接确定如下:

功能 IO
GND Pin6
5V Pin2
LCD_RESET Pin7-PG11
LCD_DC Pin22-PA1
SPICLK Pin23-PC2
SPIMOSI Pin19-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 <[email protected]>
 * Copyright (C) 2016 Milo Kim <[email protected]>
 *
 * 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中找到:

NanoPi NEO Air使用十一:編寫SPI驅動點亮TFT螢幕,ST7789V修改裝置樹編寫驅動添加Makefile編譯測試

spi0節點在soc節點下面,是以路徑為“/soc/[email protected]”

裝置樹路徑從系統裡面也可以看出來:

NanoPi NEO Air使用十一:編寫SPI驅動點亮TFT螢幕,ST7789V修改裝置樹編寫驅動添加Makefile編譯測試

建立一個目錄,在此目錄下添加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/[email protected]"); 
	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個檔案添加完成後,目錄結構如下:

NanoPi NEO Air使用十一:編寫SPI驅動點亮TFT螢幕,ST7789V修改裝置樹編寫驅動添加Makefile編譯測試

在該目錄下執行

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 [email protected]:/boot
scp /home/ql/linux/H3/MyDriver/03_spiTFT/spiTFT.ko [email protected]:/lib/modules/4.14.111/
           

傳好後,重新開機開發闆。

使用如下指令更改控制台消息等級:

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

進入/lib/modules/4.14.111目錄,執行

insmod spiTFT.ko

加載驅動,如果正常就可以看到如下輸出:

NanoPi NEO Air使用十一:編寫SPI驅動點亮TFT螢幕,ST7789V修改裝置樹編寫驅動添加Makefile編譯測試

同時螢幕被刷成紅色。

繼續閱讀