NanoPi NEO Air使用十一:编写SPI驱动点亮TFT屏幕,ST7789V
开发板引出来了spi0本节用spi0驱动一个spi接口的屏幕,屏幕如下:240x240分辨率,1.3寸,主控为ST7789。与开发板的引脚连接确定如下:功能IOGNDPin65VPin2LCD_RESETPin7-PG11LCD_DCPin22-PA1SPICLKPin23-PC2SPIMOSIPin19-PC0修改设备树spi0节点定义在/home/ql/linux/H3/linux/arch/
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 |
---|---|
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 <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 = <®_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 = <®_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 = <®_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 = <®_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";
};
®_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 = <®_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
加载驱动,如果正常就可以看到如下输出:
同时屏幕被刷成红色。
更多推荐
所有评论(0)