diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 03bed11..48f1f71 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -17,6 +17,8 @@ config SH_LCD_MIPI_DSI source "drivers/char/agp/Kconfig" +source "drivers/video/fbtft/Kconfig" + source "drivers/gpu/vga/Kconfig" source "drivers/gpu/host1x/Kconfig" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 07905d0..14810e4 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -4,6 +4,7 @@ # Each configuration option enables a list of files. +obj-y += fbtft/ obj-$(CONFIG_VGASTATE) += vgastate.o obj-$(CONFIG_HDMI) += hdmi.o obj-y += fb_notify.o diff --git a/drivers/video/fbtft/Kconfig b/drivers/video/fbtft/Kconfig new file mode 100644 index 0000000..995a910 --- /dev/null +++ b/drivers/video/fbtft/Kconfig @@ -0,0 +1,169 @@ +menuconfig FB_TFT + tristate "Support for small TFT LCD display modules" + depends on FB && SPI && GPIOLIB + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + select FB_DEFERRED_IO + select FB_BACKLIGHT + +config FB_TFT_AGM1264K_FL + tristate "FB driver for the AGM1264K-FL LCD display" + depends on FB_TFT + help + Framebuffer support for the AGM1264K-FL LCD display (two Samsung KS0108 compatable chips) + +config FB_TFT_BD663474 + tristate "FB driver for the BD663474 LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for BD663474 + +config FB_TFT_HX8340BN + tristate "FB driver for the HX8340BN LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for HX8340BN + +config FB_TFT_HX8347D + tristate "FB driver for the HX8347D LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for HX8347D + +config FB_TFT_HX8353D + tristate "FB driver for the HX8353D LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for HX8353D + +config FB_TFT_ILI9320 + tristate "FB driver for the ILI9320 LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for ILI9320 + +config FB_TFT_ILI9325 + tristate "FB driver for the ILI9325 LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for ILI9325 + +config FB_TFT_ILI9340 + tristate "FB driver for the ILI9340 LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for ILI9340 + +config FB_TFT_ILI9341 + tristate "FB driver for the ILI9341 LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for ILI9341 + +config FB_TFT_ILI9481 + tristate "FB driver for the ILI9481 LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for ILI9481 + +config FB_TFT_ILI9486 + tristate "FB driver for the ILI9486 LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for ILI9486 + +config FB_TFT_PCD8544 + tristate "FB driver for the PCD8544 LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for PCD8544 + +config FB_TFT_RA8875 + tristate "FB driver for the RA8875 LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for RA8875 + +config FB_TFT_S6D02A1 + tristate "FB driver for the S6D02A1 LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for S6D02A1 + +config FB_TFT_S6D1121 + tristate "FB driver for the S6D1211 LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for S6D1121 + +config FB_TFT_SSD1289 + tristate "FB driver for the SSD1289 LCD Controller" + depends on FB_TFT + help + Framebuffer support for SSD1289 + +config FB_TFT_SSD1306 + tristate "FB driver for the SSD1306 OLED Controller" + depends on FB_TFT + help + Framebuffer support for SSD1306 + +config FB_TFT_SSD1331 + tristate "FB driver for the SSD1331 LCD Controller" + depends on FB_TFT + help + Framebuffer support for SSD1331 + +config FB_TFT_SSD1351 + tristate "FB driver for the SSD1351 LCD Controller" + depends on FB_TFT + help + Framebuffer support for SSD1351 + +config FB_TFT_ST7735R + tristate "FB driver for the ST7735R LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for ST7735R + +config FB_TFT_TINYLCD + tristate "FB driver for tinylcd.com display" + depends on FB_TFT + help + Custom Framebuffer support for tinylcd.com display + +config FB_TFT_TLS8204 + tristate "FB driver for the TLS8204 LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for TLS8204 + +config FB_TFT_UC1701 + tristate "FB driver for the UC1701 LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for UC1701 + +config FB_TFT_UPD161704 + tristate "FB driver for the uPD161704 LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for uPD161704 + +config FB_TFT_WATTEROTT + tristate "FB driver for the WATTEROTT LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for WATTEROTT + +config FB_FLEX + tristate "Generic FB driver for TFT LCD displays" + depends on FB_TFT + help + Generic Framebuffer support for TFT LCD displays. + +config FB_TFT_FBTFT_DEVICE + tristate "Module to for adding FBTFT devices" + depends on FB_TFT diff --git a/drivers/video/fbtft/Makefile b/drivers/video/fbtft/Makefile new file mode 100644 index 0000000..71c755d --- /dev/null +++ b/drivers/video/fbtft/Makefile @@ -0,0 +1,60 @@ +ifneq ($(KERNELRELEASE),) +# kbuild part of makefile + +# Optionally, include config file to allow out of tree kernel modules build +-include $(src)/.config + +# Core module +obj-$(CONFIG_FB_TFT) += fbtft.o +fbtft-y += fbtft-core.o fbtft-sysfs.o fbtft-bus.o fbtft-io.o + +# drivers +obj-$(CONFIG_FB_TFT_AGM1264K_FL) += fb_agm1264k-fl.o +obj-$(CONFIG_FB_TFT_BD663474) += fb_bd663474.o +obj-$(CONFIG_FB_TFT_HX8340BN) += fb_hx8340bn.o +obj-$(CONFIG_FB_TFT_HX8347D) += fb_hx8347d.o +obj-$(CONFIG_FB_TFT_HX8353D) += fb_hx8353d.o +obj-$(CONFIG_FB_TFT_ILI9320) += fb_ili9320.o +obj-$(CONFIG_FB_TFT_ILI9325) += fb_ili9325.o +obj-$(CONFIG_FB_TFT_ILI9340) += fb_ili9340.o +obj-$(CONFIG_FB_TFT_ILI9341) += fb_ili9341.o +obj-$(CONFIG_FB_TFT_ILI9481) += fb_ili9481.o +obj-$(CONFIG_FB_TFT_ILI9486) += fb_ili9486.o +obj-$(CONFIG_FB_TFT_PCD8544) += fb_pcd8544.o +obj-$(CONFIG_FB_TFT_RA8875) += fb_ra8875.o +obj-$(CONFIG_FB_TFT_S6D02A1) += fb_s6d02a1.o +obj-$(CONFIG_FB_TFT_S6D1121) += fb_s6d1121.o +obj-$(CONFIG_FB_TFT_SSD1289) += fb_ssd1289.o +obj-$(CONFIG_FB_TFT_SSD1306) += fb_ssd1306.o +obj-$(CONFIG_FB_TFT_SSD1331) += fb_ssd1331.o +obj-$(CONFIG_FB_TFT_SSD1351) += fb_ssd1351.o +obj-$(CONFIG_FB_TFT_ST7735R) += fb_st7735r.o +obj-$(CONFIG_FB_TFT_TINYLCD) += fb_tinylcd.o +obj-$(CONFIG_FB_TFT_TLS8204) += fb_tls8204.o +obj-$(CONFIG_FB_TFT_UC1701) += fb_uc1701.o +obj-$(CONFIG_FB_TFT_UPD161704) += fb_upd161704.o +obj-$(CONFIG_FB_TFT_WATTEROTT) += fb_watterott.o +obj-$(CONFIG_FB_FLEX) += flexfb.o + +# Device modules +obj-$(CONFIG_FB_TFT_FBTFT_DEVICE) += fbtft_device.o + +else +# normal makefile +KDIR ?= /lib/modules/`uname -r`/build + +default: .config + $(MAKE) -C $(KDIR) M=$$PWD modules + +.config: + grep config Kconfig | cut -d' ' -f2 | sed 's@^@CONFIG_@; s@$$@=m@' > .config + +install: + $(MAKE) -C $(KDIR) M=$$PWD modules_install + + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions \ + modules.order Module.symvers + +endif diff --git a/drivers/video/fbtft/README b/drivers/video/fbtft/README new file mode 100644 index 0000000..9bebc98 --- /dev/null +++ b/drivers/video/fbtft/README @@ -0,0 +1,37 @@ + FBTFT +========= + +2015-01-19 +The FBTFT drivers are now in the Linux kernel staging tree: https://git.kernel.org/cgit/linux/kernel/git/gregkh/staging.git/tree/drivers/staging/fbtft?h=staging-testing +Development in this github repo has ceased. + + +Linux Framebuffer drivers for small TFT LCD display modules. +The module 'fbtft' makes writing drivers for some of these displays very easy. + +Development is done on a Raspberry Pi running the Raspbian "wheezy" distribution. + +INSTALLATION + Download kernel sources + + From Linux 3.15 + cd drivers/video/fbdev + git clone https://github.com/notro/fbtft.git + + Add to drivers/video/fbdev/Kconfig: source "drivers/video/fbdev/fbtft/Kconfig" + Add to drivers/video/fbdev/Makefile: obj-y += fbtft/ + + Before Linux 3.15 + cd drivers/video + git clone https://github.com/notro/fbtft.git + + Add to drivers/video/Kconfig: source "drivers/video/fbtft/Kconfig" + Add to drivers/video/Makefile: obj-y += fbtft/ + + Enable driver(s) in menuconfig and build the kernel + + +See wiki for more information: https://github.com/notro/fbtft/wiki + + +Source: https://github.com/notro/fbtft/ diff --git a/drivers/video/fbtft/dts/overlays/rpi/hy28a-overlay.dts b/drivers/video/fbtft/dts/overlays/rpi/hy28a-overlay.dts new file mode 100644 index 0000000..621497f --- /dev/null +++ b/drivers/video/fbtft/dts/overlays/rpi/hy28a-overlay.dts @@ -0,0 +1,87 @@ +/* + * Device Tree overlay for HY28A display shield by Texy + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + + spidev@0{ + status = "disabled"; + }; + + spidev@1{ + status = "disabled"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + hy28a_pins: hy28a_pins { + brcm,pins = <17 25 18>; + brcm,function = <0 1 1>; /* in out out */ + }; + }; + }; + + fragment@2 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + hy28a: hy28a@0{ + compatible = "ilitek,ili9320"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&hy28a_pins>; + + spi-max-frequency = <32000000>; + spi-cpol; + spi-cpha; + rotate = <270>; + bgr; + fps = <50>; + buswidth = <8>; + startbyte = <0x70>; + reset-gpios = <&gpio 25 0>; + led-gpios = <&gpio 18 1>; + debug = <0>; + }; + + hy28a_ts: hy28a-ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + + spi-max-frequency = <2000000>; + interrupts = <17 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 17 0>; + ti,x-plate-ohms = /bits/ 16 <100>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + __overrides__ { + speed = <&hy28a>,"spi-max-frequency:0"; + rotate = <&hy28a>,"rotate:0"; + fps = <&hy28a>,"fps:0"; + debug = <&hy28a>,"debug:0"; + xohms = <&hy28a_ts>,"ti,x-plate-ohms;0"; + resetgpio = <&hy28a>,"reset-gpios:4", + <&hy28a_pins>, "brcm,pins:1"; + ledgpio = <&hy28a>,"led-gpios:4", + <&hy28a_pins>, "brcm,pins:2"; + }; +}; diff --git a/drivers/video/fbtft/dts/overlays/rpi/hy28b-overlay.dts b/drivers/video/fbtft/dts/overlays/rpi/hy28b-overlay.dts new file mode 100644 index 0000000..f774c4a --- /dev/null +++ b/drivers/video/fbtft/dts/overlays/rpi/hy28b-overlay.dts @@ -0,0 +1,142 @@ +/* + * Device Tree overlay for HY28b display shield by Texy + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + + spidev@0{ + status = "disabled"; + }; + + spidev@1{ + status = "disabled"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + hy28b_pins: hy28b_pins { + brcm,pins = <17 25 18>; + brcm,function = <0 1 1>; /* in out out */ + }; + }; + }; + + fragment@2 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + hy28b: hy28b@0{ + compatible = "ilitek,ili9325"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&hy28b_pins>; + + spi-max-frequency = <48000000>; + spi-cpol; + spi-cpha; + rotate = <270>; + bgr; + fps = <50>; + buswidth = <8>; + startbyte = <0x70>; + reset-gpios = <&gpio 25 0>; + led-gpios = <&gpio 18 1>; + + gamma = "04 1F 4 7 7 0 7 7 6 0\n0F 00 1 7 4 0 0 0 6 7"; + + init = <0x10000e7 0x0010 + 0x1000000 0x0001 + 0x1000001 0x0100 + 0x1000002 0x0700 + 0x1000003 0x1030 + 0x1000004 0x0000 + 0x1000008 0x0207 + 0x1000009 0x0000 + 0x100000a 0x0000 + 0x100000c 0x0001 + 0x100000d 0x0000 + 0x100000f 0x0000 + 0x1000010 0x0000 + 0x1000011 0x0007 + 0x1000012 0x0000 + 0x1000013 0x0000 + 0x2000032 + 0x1000010 0x1590 + 0x1000011 0x0227 + 0x2000032 + 0x1000012 0x009c + 0x2000032 + 0x1000013 0x1900 + 0x1000029 0x0023 + 0x100002b 0x000e + 0x2000032 + 0x1000020 0x0000 + 0x1000021 0x0000 + 0x2000032 + 0x1000050 0x0000 + 0x1000051 0x00ef + 0x1000052 0x0000 + 0x1000053 0x013f + 0x1000060 0xa700 + 0x1000061 0x0001 + 0x100006a 0x0000 + 0x1000080 0x0000 + 0x1000081 0x0000 + 0x1000082 0x0000 + 0x1000083 0x0000 + 0x1000084 0x0000 + 0x1000085 0x0000 + 0x1000090 0x0010 + 0x1000092 0x0000 + 0x1000093 0x0003 + 0x1000095 0x0110 + 0x1000097 0x0000 + 0x1000098 0x0000 + 0x1000007 0x0133 + 0x1000020 0x0000 + 0x1000021 0x0000 + 0x2000064>; + debug = <0>; + }; + + hy28b_ts: hy28b-ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + + spi-max-frequency = <2000000>; + interrupts = <17 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 17 0>; + ti,x-plate-ohms = /bits/ 16 <100>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + __overrides__ { + speed = <&hy28b>,"spi-max-frequency:0"; + rotate = <&hy28b>,"rotate:0"; + fps = <&hy28b>,"fps:0"; + debug = <&hy28b>,"debug:0"; + xohms = <&hy28b_ts>,"ti,x-plate-ohms;0"; + resetgpio = <&hy28b>,"reset-gpios:4", + <&hy28b_pins>, "brcm,pins:1"; + ledgpio = <&hy28b>,"led-gpios:4", + <&hy28b_pins>, "brcm,pins:2"; + }; +}; diff --git a/drivers/video/fbtft/dts/overlays/rpi/mz61581-overlay.dts b/drivers/video/fbtft/dts/overlays/rpi/mz61581-overlay.dts new file mode 100644 index 0000000..c06fe12 --- /dev/null +++ b/drivers/video/fbtft/dts/overlays/rpi/mz61581-overlay.dts @@ -0,0 +1,109 @@ +/* + * Device Tree overlay for MZ61581-PI-EXT 2014.12.28 by Tontec + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + + spidev@0{ + status = "disabled"; + }; + + spidev@1{ + status = "disabled"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + mz61581_pins: mz61581_pins { + brcm,pins = <4 15 18 25>; + brcm,function = <0 1 1 1>; /* in out out out */ + }; + }; + }; + + fragment@2 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + mz61581: mz61581@0{ + compatible = "samsung,s6d02a1"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&mz61581_pins>; + + spi-max-frequency = <128000000>; + spi-cpol; + spi-cpha; + + width = <320>; + height = <480>; + rotate = <270>; + bgr; + fps = <30>; + buswidth = <8>; + + reset-gpios = <&gpio 15 0>; + dc-gpios = <&gpio 25 0>; + led-gpios = <&gpio 18 0>; + + init = <0x10000b0 00 + 0x1000011 + 0x20000ff + 0x10000b3 0x02 0x00 0x00 0x00 + 0x10000c0 0x13 0x3b 0x00 0x02 0x00 0x01 0x00 0x43 + 0x10000c1 0x08 0x16 0x08 0x08 + 0x10000c4 0x11 0x07 0x03 0x03 + 0x10000c6 0x00 + 0x10000c8 0x03 0x03 0x13 0x5c 0x03 0x07 0x14 0x08 0x00 0x21 0x08 0x14 0x07 0x53 0x0c 0x13 0x03 0x03 0x21 0x00 + 0x1000035 0x00 + 0x1000036 0xa0 + 0x100003a 0x55 + 0x1000044 0x00 0x01 + 0x10000d0 0x07 0x07 0x1d 0x03 + 0x10000d1 0x03 0x30 0x10 + 0x10000d2 0x03 0x14 0x04 + 0x1000029 + 0x100002c>; + + /* This is a workaround to make sure the init sequence slows down and doesn't fail */ + debug = <3>; + }; + + mz61581_ts: mz61581_ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + + spi-max-frequency = <2000000>; + interrupts = <4 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 4 0>; + + ti,x-plate-ohms = /bits/ 16 <60>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + __overrides__ { + speed = <&mz61581>, "spi-max-frequency:0"; + rotate = <&mz61581>, "rotate:0"; + fps = <&mz61581>, "fps:0"; + debug = <&mz61581>, "debug:0"; + xohms = <&mz61581_ts>,"ti,x-plate-ohms;0"; + }; +}; diff --git a/drivers/video/fbtft/dts/overlays/rpi/piscreen-overlay.dts b/drivers/video/fbtft/dts/overlays/rpi/piscreen-overlay.dts new file mode 100644 index 0000000..8cd6a95 --- /dev/null +++ b/drivers/video/fbtft/dts/overlays/rpi/piscreen-overlay.dts @@ -0,0 +1,94 @@ +/* + * Device Tree overlay for PiScreen 3.5" display shield by Ozzmaker + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + + spidev@0{ + status = "disabled"; + }; + + spidev@1{ + status = "disabled"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + piscreen_pins: piscreen_pins { + brcm,pins = <17 25 24 22>; + brcm,function = <0 1 1 1>; /* in out out out */ + }; + }; + }; + + fragment@2 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + piscreen: piscreen@0{ + compatible = "ilitek,ili9486"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&piscreen_pins>; + + spi-max-frequency = <32000000>; + rotate = <270>; + bgr; + fps = <30>; + buswidth = <8>; + regwidth = <16>; + reset-gpios = <&gpio 25 0>; + dc-gpios = <&gpio 24 0>; + led-gpios = <&gpio 22 1>; + debug = <0>; + + init = <0x10000b0 0x00 + 0x1000011 + 0x20000ff + 0x100003a 0x55 + 0x1000036 0x28 + 0x10000c2 0x44 + 0x10000c5 0x00 0x00 0x00 0x00 + 0x10000e0 0x0f 0x1f 0x1c 0x0c 0x0f 0x08 0x48 0x98 0x37 0x0a 0x13 0x04 0x11 0x0d 0x00 + 0x10000e1 0x0f 0x32 0x2e 0x0b 0x0d 0x05 0x47 0x75 0x37 0x06 0x10 0x03 0x24 0x20 0x00 + 0x10000e2 0x0f 0x32 0x2e 0x0b 0x0d 0x05 0x47 0x75 0x37 0x06 0x10 0x03 0x24 0x20 0x00 + 0x1000011 + 0x1000029>; + }; + + piscreen-ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + + spi-max-frequency = <2000000>; + interrupts = <17 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 17 0>; + ti,x-plate-ohms = /bits/ 16 <100>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + __overrides__ { + speed = <&piscreen>,"spi-max-frequency:0"; + rotate = <&piscreen>,"rotate:0"; + fps = <&piscreen>,"fps:0"; + debug = <&piscreen>,"debug:0"; + }; +}; diff --git a/drivers/video/fbtft/dts/overlays/rpi/pitft28-resistive-overlay.dts b/drivers/video/fbtft/dts/overlays/rpi/pitft28-resistive-overlay.dts new file mode 100644 index 0000000..e8a9365 --- /dev/null +++ b/drivers/video/fbtft/dts/overlays/rpi/pitft28-resistive-overlay.dts @@ -0,0 +1,115 @@ +/* + * Device Tree overlay for pitft resistive by Adafruit + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + + spidev@0{ + status = "disabled"; + }; + + spidev@1{ + status = "disabled"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + pitft_pins: pitft_pins { + brcm,pins = <24 25>; + brcm,function = <0 1>; /* in out */ + brcm,pull = <2 0>; /* pullup none */ + }; + }; + }; + + fragment@2 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + pitft: pitft@0{ + compatible = "ilitek,ili9340"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&pitft_pins>; + + spi-max-frequency = <16000000>; + rotate = <90>; + fps = <25>; + bgr; + buswidth = <8>; + dc-gpios = <&gpio 25 0>; + debug = <0>; + }; + + pitft_ts@1 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "st,stmpe610"; + reg = <1>; + + spi-max-frequency = <500000>; + irq-gpio = <&gpio 24 0x2>; /* IRQF_TRIGGER_FALLING */ + interrupts = <24 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + interrupt-controller; + + stmpe_touchscreen { + compatible = "st,stmpe-ts"; + st,sample-time = <4>; + st,mod-12b = <1>; + st,ref-sel = <0>; + st,adc-freq = <2>; + st,ave-ctrl = <3>; + st,touch-det-delay = <4>; + st,settling = <2>; + st,fraction-z = <7>; + st,i-drive = <0>; + }; + + stmpe_gpio: stmpe_gpio { + #gpio-cells = <2>; + compatible = "st,stmpe-gpio"; + /* + * only GPIO2 is wired/available + * and it is wired to the backlight + */ + st,norequest-mask = <0x7b>; + }; + }; + }; + }; + + fragment@3 { + target-path = "/soc"; + __overlay__ { + backlight { + compatible = "gpio-backlight"; + gpios = <&stmpe_gpio 2 0>; + default-on; + }; + }; + }; + + __overrides__ { + speed = <&pitft>,"spi-max-frequency:0"; + rotate = <&pitft>,"rotate:0"; + fps = <&pitft>,"fps:0"; + debug = <&pitft>,"debug:0"; + }; +}; diff --git a/drivers/video/fbtft/dts/overlays/rpi/rpi-display-overlay.dts b/drivers/video/fbtft/dts/overlays/rpi/rpi-display-overlay.dts new file mode 100644 index 0000000..0578810 --- /dev/null +++ b/drivers/video/fbtft/dts/overlays/rpi/rpi-display-overlay.dts @@ -0,0 +1,81 @@ +/* + * Device Tree overlay for rpi-display by Watterott + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + + spidev@0{ + status = "disabled"; + }; + + spidev@1{ + status = "disabled"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + rpi_display_pins: rpi_display_pins { + brcm,pins = <18 23 24 25>; + brcm,function = <1 1 1 0>; /* out out out in */ + }; + }; + }; + + fragment@2 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + rpidisplay: rpi-display@0{ + compatible = "ilitek,ili9341"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&rpi_display_pins>; + + spi-max-frequency = <32000000>; + rotate = <270>; + bgr; + fps = <30>; + buswidth = <8>; + reset-gpios = <&gpio 23 0>; + dc-gpios = <&gpio 24 0>; + led-gpios = <&gpio 18 1>; + debug = <0>; + }; + + rpidisplay_ts: rpi-display-ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + + spi-max-frequency = <2000000>; + interrupts = <25 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 25 0>; + ti,x-plate-ohms = /bits/ 16 <60>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + __overrides__ { + speed = <&rpidisplay>,"spi-max-frequency:0"; + rotate = <&rpidisplay>,"rotate:0"; + fps = <&rpidisplay>,"fps:0"; + debug = <&rpidisplay>,"debug:0"; + xohms = <&rpidisplay_ts>,"ti,x-plate-ohms;0"; + }; +}; diff --git a/drivers/video/fbtft/dts/overlays/rpi/tinylcd35-overlay.dts b/drivers/video/fbtft/dts/overlays/rpi/tinylcd35-overlay.dts new file mode 100644 index 0000000..881d2eb --- /dev/null +++ b/drivers/video/fbtft/dts/overlays/rpi/tinylcd35-overlay.dts @@ -0,0 +1,181 @@ +/* + * tinylcd 3.5" display + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + + spidev@0{ + status = "disabled"; + }; + + spidev@1{ + status = "disabled"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + tinylcd35_pins: tinylcd35_pins { + brcm,pins = <25 24 18>; + brcm,function = <1>; /* out */ + }; + tinylcd35_ts_pins: tinylcd35_ts_pins { + brcm,pins = <5>; + brcm,function = <0>; /* in */ + }; + keypad_pins: keypad_pins { + brcm,pins = <4 17 22 23 27>; + brcm,function = <0>; /* in */ + brcm,pull = <1>; /* down */ + }; + }; + }; + + fragment@2 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + + tinylcd35: tinylcd35@0{ + compatible = "neosec,tinylcd"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&tinylcd35_pins>, + <&tinylcd35_ts_pins>; + + spi-max-frequency = <48000000>; + rotate = <270>; + fps = <20>; + bgr; + buswidth = <8>; + reset-gpios = <&gpio 25 0>; + dc-gpios = <&gpio 24 0>; + led-gpios = <&gpio 18 1>; + debug = <0>; + + init = <0x10000B0 0x80 + 0x10000C0 0x0A 0x0A + 0x10000C1 0x01 0x01 + 0x10000C2 0x33 + 0x10000C5 0x00 0x42 0x80 + 0x10000B1 0xD0 0x11 + 0x10000B4 0x02 + 0x10000B6 0x00 0x22 0x3B + 0x10000B7 0x07 + 0x1000036 0x58 + 0x10000F0 0x36 0xA5 0xD3 + 0x10000E5 0x80 + 0x10000E5 0x01 + 0x10000B3 0x00 + 0x10000E5 0x00 + 0x10000F0 0x36 0xA5 0x53 + 0x10000E0 0x00 0x35 0x33 0x00 0x00 0x00 0x00 0x35 0x33 0x00 0x00 0x00 + 0x100003A 0x55 + 0x1000011 + 0x2000001 + 0x1000029>; + }; + + tinylcd35_ts: tinylcd35_ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + status = "disabled"; + + spi-max-frequency = <2000000>; + interrupts = <5 2>; /* high-to-low edge triggered */ + interrupt-parent = <&gpio>; + pendown-gpio = <&gpio 5 0>; + ti,x-plate-ohms = /bits/ 16 <100>; + ti,pressure-max = /bits/ 16 <255>; + }; + }; + }; + + fragment@3 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + pcf8563: pcf8563@51 { + compatible = "nxp,pcf8563"; + reg = <0x51>; + status = "disabled"; + }; + }; + }; + + /* + * Values for input event code is found under the + * 'Keys and buttons' heading in include/uapi/linux/input.h + */ + fragment@4 { + target-path = "/soc"; + __overlay__ { + keypad: keypad { + compatible = "gpio-keys"; + #address-cells = <1>; + #size-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&keypad_pins>; + status = "disabled"; + autorepeat; + + button@17 { + label = "GPIO KEY_UP"; + linux,code = <103>; + gpios = <&gpio 17 0>; + }; + button@22 { + label = "GPIO KEY_DOWN"; + linux,code = <108>; + gpios = <&gpio 22 0>; + }; + button@27 { + label = "GPIO KEY_LEFT"; + linux,code = <105>; + gpios = <&gpio 27 0>; + }; + button@23 { + label = "GPIO KEY_RIGHT"; + linux,code = <106>; + gpios = <&gpio 23 0>; + }; + button@4 { + label = "GPIO KEY_ENTER"; + linux,code = <28>; + gpios = <&gpio 4 0>; + }; + }; + }; + }; + + __overrides__ { + speed = <&tinylcd35>,"spi-max-frequency:0"; + rotate = <&tinylcd35>,"rotate:0"; + fps = <&tinylcd35>,"fps:0"; + debug = <&tinylcd35>,"debug:0"; + touch = <&tinylcd35_ts>,"status"; + touchgpio = <&tinylcd35_ts_pins>,"brcm,pins:0", + <&tinylcd35_ts>,"interrupts:0", + <&tinylcd35_ts>,"pendown-gpio:4"; + xohms = <&tinylcd35_ts>,"ti,x-plate-ohms;0"; + rtc = <&i2c1>,"status", + <&pcf8563>,"status"; + keypad = <&keypad>,"status"; + }; +}; diff --git a/drivers/video/fbtft/dts/rpi.dts b/drivers/video/fbtft/dts/rpi.dts new file mode 100644 index 0000000..6c3ab6e --- /dev/null +++ b/drivers/video/fbtft/dts/rpi.dts @@ -0,0 +1,414 @@ + +/* + * FBTFT Device Tree part for the Raspberry Pi + * Add this file to the end of the *rpi-b.dts file + * + * Please keep it sorted alphabetically on display name + * + * Notes: + * - use ads7846 instead of tsc2046 to get module autoloading + * - use polarity 1 to drive backlight initially low (off): + * led-gpios = <&gpio 18 1>; + */ + +&spi0 { + /* this is provided here to make it easy to enable SPI when editing this file */ +// status = "okay"; +}; + + + +/* + * Texy + * 2.8" TFT + Touch Shield Board (320x240) HY28A + * + */ +&spi0 { + hy28a@0{ + compatible = "ilitek,ili9320"; + reg = <0>; + status = "disabled"; + + spi-max-frequency = <32000000>; + spi-cpol; + spi-cpha; + rotate = <270>; + bgr; + fps = <50>; + buswidth = <8>; + startbyte = <0x70>; + reset-gpios = <&gpio 25 0>; + led-gpios = <&gpio 18 1>; + debug = <0>; + }; + + hy28a_ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + status = "disabled"; + + spi-max-frequency = <2000000>; + interrupts = <3 17>; + pendown-gpio = <&gpio 17 0>; + ti,x-plate-ohms = /bits/ 16 <100>; + ti,pressure-max = /bits/ 16 <255>; + }; +}; + + + + +/* + * Texy + * 2.8" TFT + Touch Shield Board (320x240) HY28B + * NOT TESTED + */ +&spi0 { + hy28b@0{ + compatible = "ilitek,ili9325"; + reg = <0>; + status = "disabled"; + + spi-max-frequency = <48000000>; + rotate = <270>; + bgr; + fps = <50>; + buswidth = <8>; + startbyte = <0x70>; + reset-gpios = <&gpio 25 0>; + led-gpios = <&gpio 18 1>; + + gamma = "04 1F 4 7 7 0 7 7 6 0\n0F 00 1 7 4 0 0 0 6 7"; + + init = <0x10000e7 0x0010 + 0x1000000 0x0001 + 0x1000001 0x0100 + 0x1000002 0x0700 + 0x1000003 0x1030 + 0x1000004 0x0000 + 0x1000008 0x0207 + 0x1000009 0x0000 + 0x100000a 0x0000 + 0x100000c 0x0001 + 0x100000d 0x0000 + 0x100000f 0x0000 + 0x1000010 0x0000 + 0x1000011 0x0007 + 0x1000012 0x0000 + 0x1000013 0x0000 + 0x2000032 + 0x1000010 0x1590 + 0x1000011 0x0227 + 0x2000032 + 0x1000012 0x009c + 0x2000032 + 0x1000013 0x1900 + 0x1000029 0x0023 + 0x100002b 0x000e + 0x2000032 + 0x1000020 0x0000 + 0x1000021 0x0000 + 0x2000032 + 0x1000050 0x0000 + 0x1000051 0x00ef + 0x1000052 0x0000 + 0x1000053 0x013f + 0x1000060 0xa700 + 0x1000061 0x0001 + 0x100006a 0x0000 + 0x1000080 0x0000 + 0x1000081 0x0000 + 0x1000082 0x0000 + 0x1000083 0x0000 + 0x1000084 0x0000 + 0x1000085 0x0000 + 0x1000090 0x0010 + 0x1000092 0x0000 + 0x1000093 0x0003 + 0x1000095 0x0110 + 0x1000097 0x0000 + 0x1000098 0x0000 + 0x1000007 0x0133 + 0x1000020 0x0000 + 0x1000021 0x0000 + 0x2000064>; + debug = <0>; + }; + + hy28b_ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + status = "disabled"; + + spi-max-frequency = <2000000>; + interrupts = <3 17>; + pendown-gpio = <&gpio 17 0>; + ti,x-plate-ohms = /bits/ 16 <100>; + ti,pressure-max = /bits/ 16 <255>; + }; +}; + + + + +/* + * ITEAD + * ITDB02-2.8 + * + */ +/ { + itdb28 { + compatible = "ilitek,ili9325"; + status = "disabled"; + + rotate = <0>; + bgr; + buswidth = <8>; + reset-gpios = <&gpio 17 0>; + dc-gpios = <&gpio 3 0>; + cs-gpios = <&gpio 27 0>; + wr-gpios = <&gpio 2 0>; + db-gpios = <&gpio 9 0>, + <&gpio 11 0>, + <&gpio 18 0>, + <&gpio 23 0>, + <&gpio 24 0>, + <&gpio 25 0>, + <&gpio 8 0>, + <&gpio 7 0>; + /* LED pin drives backlight directly. Use transistor (50mA) */ + /* led-gpios = <&gpio 4 1>; */ + debug = <0>; + }; +}; + + + + +/* + * Watterott + * RPi-Display - 2.8" Touch-Display (320x240) + * + */ +&spi0 { + rpi-display@0{ + compatible = "ilitek,ili9341"; + reg = <0>; + status = "disabled"; + + spi-max-frequency = <32000000>; + rotate = <270>; + bgr; + fps = <30>; + buswidth = <8>; + reset-gpios = <&gpio 23 0>; + dc-gpios = <&gpio 24 0>; + led-gpios = <&gpio 18 1>; + debug = <0>; + }; + + rpi-display_ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + status = "disabled"; + + spi-max-frequency = <2000000>; + interrupts = <3 25>; + pendown-gpio = <&gpio 25 0>; + ti,x-plate-ohms = /bits/ 16 <60>; + ti,pressure-max = /bits/ 16 <255>; + }; +}; + + + + +/* + * Ozzmaker + * PiScreen - 3.5" TFT touchscreen (480x320) + * + */ +&spi0 { + piscreen@0{ + compatible = "ilitek,ili9486"; + reg = <0>; + status = "disabled"; + + spi-max-frequency = <20000000>; + rotate = <270>; + bgr; + fps = <30>; + buswidth = <8>; + reset-gpios = <&gpio 25 0>; + dc-gpios = <&gpio 24 0>; + led-gpios = <&gpio 22 1>; + + init = <0x10000b0 0x00 + 0x1000011 + 0x20000FF + 0x100003A 0x55 + 0x1000036 0x28 + 0x10000C2 0x44 + 0x10000C5 0x00 0x00 0x00 0x00 + 0x10000E0 0x0F 0x1F 0x1C 0x0C 0x0F 0x08 0x48 0x98 0x37 0x0A 0x13 0x04 0x11 0x0D 0x00 + 0x10000E1 0x0F 0x32 0x2E 0x0B 0x0D 0x05 0x47 0x75 0x37 0x06 0x10 0x03 0x24 0x20 0x00 + 0x10000E2 0x0F 0x32 0x2E 0x0B 0x0D 0x05 0x47 0x75 0x37 0x06 0x10 0x03 0x24 0x20 0x00 + 0x1000011 + 0X1000029>; + debug = <0>; + }; + + piscreen_ts@1 { + compatible = "ti,ads7846"; + reg = <1>; + status = "disabled"; + + spi-max-frequency = <2000000>; + interrupts = <3 17>; + pendown-gpio = <&gpio 17 0>; + ti,x-plate-ohms = /bits/ 16 <100>; + ti,pressure-max = /bits/ 16 <255>; + }; +}; + + + + +/* + * Adafruit + * PiTFT Mini Kit - 320x240 2.8" TFT+Touchscreen for Raspberry Pi + * + * Couldn't get touch controller to work + */ + +&gpio { + stmpets_pins: stmpets_pins { + brcm,pins = <24>; + brcm,function = <0>; /* gpio in */ + brcm,pull = <2>; /* pull up */ + }; +}; + +&spi0 { + pitft@0{ + compatible = "ilitek,ili9340"; + reg = <0>; + status = "disabled"; + + spi-max-frequency = <32000000>; + rotate = <90>; + bgr; + buswidth = <8>; + dc-gpios = <&gpio 25 0>; + + init = <0x1000001 + 0x2000005 + 0x1000028 + 0x10000EF 0x03 0x80 0x02 + 0x10000CF 0x00 0xC1 0x30 + 0x10000ED 0x64 0x03 0x12 0x81 + 0x10000E8 0x85 0x00 0x78 + 0x10000CB 0x39 0x2C 0x00 0x34 0x02 + 0x10000F7 0x20 + 0x10000EA 0x00 0x00 + 0x10000C0 0x23 + 0x10000C1 0x10 + 0x10000C5 0x3e 0x28 + 0x10000C7 0x86 + 0x100003A 0x55 + 0x10000B1 0x00 0x18 + 0x10000B6 0x08 0x82 0x27 + 0x10000F2 0x00 + 0x1000026 0x01 + 0x10000E0 0x0F 0x31 0x2B 0x0C 0x0E 0x08 0x4E 0xF1 0x37 0x07 0x10 0x03 0x0E 0x09 0x00 + 0x10000E1 0x00 0x0E 0x14 0x03 0x11 0x07 0x31 0xC1 0x48 0x08 0x0F 0x0C 0x31 0x36 0x0F + 0x1000011 + 0x2000064 + 0x1000029 + 0x2000014>; + debug = <0>; + }; +/* +Touchscreen didn't work. I guess it has something to do with it being an interrupt controller. +I have never tried this before with DT and ARCH_BCM2708. +On ARCH_BCM2835 it should be OK, since the GPIO controller acts as an interrupt controller as well. +(stmpe_device can't be used from 3.16, because irq_base can't be set anymore) + +[ 8.889056] irq: irq_create_mapping(0xc3008800, 0xc2) +[ 8.895698] irq: -> using domain @c3008800 +[ 8.900796] irq: -> existing mapping on virq 194 +[ 8.925048] stmpe-spi spi0.1: stmpe610 detected, chip id: 0x811 +[ 8.936650] irq: Added domain (null) +[ 8.941780] irq: irq_create_mapping(0xc1dc1a20, 0x0) +[ 8.949047] irq: -> using domain @c1dc1a20 +[ 8.954421] irq: -> virq allocation failed +[ 8.959926] irq: irq_create_mapping(0xc1dc1a20, 0x1) +[ 8.967366] irq: -> using domain @c1dc1a20 +[ 8.972870] irq: -> virq allocation failed +*/ + pitft_ts@1 { + compatible = "st,stmpe610"; + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + status = "disabled"; + pinctrl-names = "default"; + pinctrl-0 = <&stmpets_pins>; + + spi-max-frequency = <500000>; + interrupts = <3 24>; + interrupt-parent = <&intc>; + interrupt-controller; + + stmpe_touchscreen { + compatible = "st,stmpe-ts"; + st,sample-time = <4>; + st,mod-12b = <1>; + st,ref-sel = <0>; + st,adc-freq = <2>; + st,ave-ctrl = <3>; + st,touch-det-delay = <4>; + st,settling = <2>; + st,fraction-z = <7>; + st,i-drive = <0>; + }; + }; +}; + + + + +/* + * NeoSec LLC + * 3.5 inch TFT Display (320x480) + * + */ +&spi0 { + tinylcd35@0{ + compatible = "neosec,tinylcd"; + reg = <0>; + status = "disabled"; + + spi-max-frequency = <48000000>; + rotate = <270>; + bgr; + fps = <50>; + buswidth = <8>; + reset-gpios = <&gpio 25 0>; + dc-gpios = <&gpio 24 0>; + led-gpios = <&gpio 18 1>; + debug = <0>; + }; + + tinylcd35@1 { + compatible = "ti,ads7846"; + reg = <1>; + status = "disabled"; + + spi-max-frequency = <2000000>; + interrupts = <3 3>; + pendown-gpio = <&gpio 3 0>; + ti,x-plate-ohms = /bits/ 16 <100>; + ti,pressure-max = /bits/ 16 <255>; + }; +}; diff --git a/drivers/video/fbtft/fb_agm1264k-fl.c b/drivers/video/fbtft/fb_agm1264k-fl.c new file mode 100644 index 0000000..7fe4fa0 --- /dev/null +++ b/drivers/video/fbtft/fb_agm1264k-fl.c @@ -0,0 +1,462 @@ +/* + * FB driver for Two KS0108 LCD controllers in AGM1264K-FL display + * + * Copyright (C) 2014 ololoshka2871 + * + * This program 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 program 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "fbtft.h" + +/* Uncomment text line to use negative image on display */ +/*#define NEGATIVE*/ + +#define WHITE 0xff +#define BLACK 0 + +#define DRVNAME "fb_agm1264k-fl" +#define WIDTH 64 +#define HEIGHT 64 +#define TOTALWIDTH (WIDTH * 2) /* because 2 x ks0108 in one display */ +#define FPS 20 + +#define EPIN gpio.wr +#define RS gpio.dc +#define RW gpio.aux[2] +#define CS0 gpio.aux[0] +#define CS1 gpio.aux[1] + + +/* diffusing error (“Floyd-Steinberg”) */ +#define DIFFUSING_MATRIX_WIDTH 2 +#define DIFFUSING_MATRIX_HEIGHT 2 + +static const signed char +diffusing_matrix[DIFFUSING_MATRIX_WIDTH][DIFFUSING_MATRIX_HEIGHT] = { + {-1, 3}, + {3, 2}, +}; + +static const unsigned char gamma_correction_table[] = { +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, +1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, +6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, +13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, +22, 22, 23, 23, 24, 25, 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 32, +33, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40, 41, 42, 43, 43, 44, 45, +46, 47, 48, 49, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, +62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 79, 81, +82, 83, 84, 85, 87, 88, 89, 90, 91, 93, 94, 95, 97, 98, 99, 100, 102, +103, 105, 106, 107, 109, 110, 111, 113, 114, 116, 117, 119, 120, 121, +123, 124, 126, 127, 129, 130, 132, 133, 135, 137, 138, 140, 141, 143, +145, 146, 148, 149, 151, 153, 154, 156, 158, 159, 161, 163, 165, 166, +168, 170, 172, 173, 175, 177, 179, 181, 182, 184, 186, 188, 190, 192, +194, 196, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, +221, 223, 225, 227, 229, 231, 234, 236, 238, 240, 242, 244, 246, 248, +251, 253, 255 +}; + +static int init_display(struct fbtft_par *par) +{ + u8 i; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + for (i = 0; i < 2; ++i) { + write_reg(par, i, 0x3f); /* display on */ + write_reg(par, i, 0x40); /* set x to 0 */ + write_reg(par, i, 0xb0); /* set page to 0 */ + write_reg(par, i, 0xc0); /* set start line to 0 */ + } + + return 0; +} + +void reset(struct fbtft_par *par) +{ + if (par->gpio.reset == -1) + return; + + fbtft_dev_dbg(DEBUG_RESET, par, par->info->device, "%s()\n", __func__); + + gpio_set_value(par->gpio.reset, 0); + udelay(20); + gpio_set_value(par->gpio.reset, 1); + mdelay(120); +} + +/* Check if all necessary GPIOS defined */ +static int verify_gpios(struct fbtft_par *par) +{ + int i; + + fbtft_dev_dbg(DEBUG_VERIFY_GPIOS, par, par->info->device, + "%s()\n", __func__); + + if (par->EPIN < 0) { + dev_err(par->info->device, + "Missing info about 'wr' (aka E) gpio. Aborting.\n"); + return -EINVAL; + } + for (i = 0; i < 8; ++i) { + if (par->gpio.db[i] < 0) { + dev_err(par->info->device, + "Missing info about 'db[%i]' gpio. Aborting.\n", + i); + return -EINVAL; + } + } + if (par->CS0 < 0) { + dev_err(par->info->device, + "Missing info about 'cs0' gpio. Aborting.\n"); + return -EINVAL; + } + if (par->CS1 < 0) { + dev_err(par->info->device, + "Missing info about 'cs1' gpio. Aborting.\n"); + return -EINVAL; + } + if (par->RW < 0) { + dev_err(par->info->device, + "Missing info about 'rw' gpio. Aborting.\n"); + return -EINVAL; + } + + return 0; +} + +static unsigned long +request_gpios_match(struct fbtft_par *par, const struct fbtft_gpio *gpio) +{ + fbtft_dev_dbg(DEBUG_REQUEST_GPIOS_MATCH, par, par->info->device, + "%s('%s')\n", __func__, gpio->name); + + if (strcasecmp(gpio->name, "wr") == 0) { + /* left ks0108 E pin */ + par->EPIN = gpio->gpio; + return GPIOF_OUT_INIT_LOW; + } else if (strcasecmp(gpio->name, "cs0") == 0) { + /* left ks0108 controller pin */ + par->CS0 = gpio->gpio; + return GPIOF_OUT_INIT_HIGH; + } else if (strcasecmp(gpio->name, "cs1") == 0) { + /* right ks0108 controller pin */ + par->CS1 = gpio->gpio; + return GPIOF_OUT_INIT_HIGH; + } + + /* if write (rw = 0) e(1->0) perform write */ + /* if read (rw = 1) e(0->1) set data on D0-7*/ + else if (strcasecmp(gpio->name, "rw") == 0) { + par->RW = gpio->gpio; + return GPIOF_OUT_INIT_LOW; + } + + return FBTFT_GPIO_NO_MATCH; +} + +/* This function oses to enter commands + * first byte - destination controller 0 or 1 + * folowing - commands + */ +static void write_reg8_bus8(struct fbtft_par *par, int len, ...) +{ + va_list args; + int i, ret; + u8 *buf = (u8 *)par->buf; + + if (unlikely(par->debug & DEBUG_WRITE_REGISTER)) { + va_start(args, len); + for (i = 0; i < len; i++) + buf[i] = (u8)va_arg(args, unsigned int); + + va_end(args); + fbtft_par_dbg_hex(DEBUG_WRITE_REGISTER, par, + par->info->device, u8, buf, len, "%s: ", __func__); + } + + va_start(args, len); + + *buf = (u8)va_arg(args, unsigned int); + + if (*buf > 1) { + va_end(args); + dev_err(par->info->device, "%s: Incorrect chip sellect request (%d)\n", + __func__, *buf); + return; + } + + /* select chip */ + if (*buf) { + /* cs1 */ + gpio_set_value(par->CS0, 1); + gpio_set_value(par->CS1, 0); + } else { + /* cs0 */ + gpio_set_value(par->CS0, 0); + gpio_set_value(par->CS1, 1); + } + + gpio_set_value(par->RS, 0); /* RS->0 (command mode) */ + len--; + + if (len) { + i = len; + while (i--) + *buf++ = (u8)va_arg(args, unsigned int); + ret = par->fbtftops.write(par, par->buf, len * (sizeof(u8))); + if (ret < 0) { + va_end(args); + dev_err(par->info->device, "%s: write() failed and returned %d\n", + __func__, ret); + return; + } + } + + va_end(args); +} + +static struct +{ + int xs, ys_page, xe, ye_page; +} addr_win; + +/* save display writing zone */ +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + addr_win.xs = xs; + addr_win.ys_page = ys / 8; + addr_win.xe = xe; + addr_win.ye_page = ye / 8; + + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys_page=%d, xe=%d, ye_page=%d)\n", __func__, + addr_win.xs, addr_win.ys_page, addr_win.xe, addr_win.ye_page); +} + +static void +construct_line_bitmap(struct fbtft_par *par, u8 *dest, signed short *src, + int xs, int xe, int y) +{ + int x, i; + + for (x = xs; x < xe; ++x) { + u8 res = 0; + + for (i = 0; i < 8; i++) + if (src[(y * 8 + i) * par->info->var.xres + x]) + res |= 1 << i; +#ifdef NEGATIVE + *dest++ = res; +#else + *dest++ = ~res; +#endif + } +} + +static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) +{ + u16 *vmem16 = (u16 *)par->info->screen_base; + u8 *buf = par->txbuf.buf; + int x, y; + int ret = 0; + + /* buffer to convert RGB565 -> grayscale16 -> Ditherd image 1bpp */ + signed short *convert_buf = kmalloc(par->info->var.xres * + par->info->var.yres * sizeof(signed short), GFP_NOIO); + + fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s()\n", __func__); + + /* converting to grayscale16 */ + for (x = 0; x < par->info->var.xres; ++x) + for (y = 0; y < par->info->var.yres; ++y) { + u16 pixel = vmem16[y * par->info->var.xres + x]; + u16 b = pixel & 0x1f; + u16 g = (pixel & (0x3f << 5)) >> 5; + u16 r = (pixel & (0x1f << (5 + 6))) >> (5 + 6); + + pixel = (299 * r + 587 * g + 114 * b) / 200; + if (pixel > 255) + pixel = 255; + + /* gamma-correction by table */ + convert_buf[y * par->info->var.xres + x] = + (signed short)gamma_correction_table[pixel]; + } + + /* Image Dithering */ + for (x = 0; x < par->info->var.xres; ++x) + for (y = 0; y < par->info->var.yres; ++y) { + signed short pixel = + convert_buf[y * par->info->var.xres + x]; + signed short error_b = pixel - BLACK; + signed short error_w = pixel - WHITE; + signed short error; + u16 i, j; + + /* what color close? */ + if (abs(error_b) >= abs(error_w)) { + /* white */ + error = error_w; + pixel = 0xff; + } else { + /* black */ + error = error_b; + pixel = 0; + } + + error /= 8; + + /* diffusion matrix row */ + for (i = 0; i < DIFFUSING_MATRIX_WIDTH; ++i) + /* diffusion matrix column */ + for (j = 0; j < DIFFUSING_MATRIX_HEIGHT; ++j) { + signed short *write_pos; + signed char coeff; + + /* skip pixels out of zone */ + if (x + i < 0 || + x + i >= par->info->var.xres + || y + j >= par->info->var.yres) + continue; + write_pos = &convert_buf[ + (y + j) * par->info->var.xres + + x + i]; + coeff = diffusing_matrix[i][j]; + if (coeff == -1) + /* pixel itself */ + *write_pos = pixel; + else { + signed short p = *write_pos + + error * coeff; + + if (p > WHITE) + p = WHITE; + if (p < BLACK) + p = BLACK; + *write_pos = p; + } + } + } + + /* 1 string = 2 pages */ + for (y = addr_win.ys_page; y <= addr_win.ye_page; ++y) { + /* left half of display */ + if (addr_win.xs < par->info->var.xres / 2) { + construct_line_bitmap(par, buf, convert_buf, + addr_win.xs, par->info->var.xres / 2, y); + + len = par->info->var.xres / 2 - addr_win.xs; + + /* select left side (sc0) + * set addr + */ + write_reg(par, 0x00, (1 << 6) | (u8)addr_win.xs); + write_reg(par, 0x00, (0x17 << 3) | (u8)y); + + /* write bitmap */ + gpio_set_value(par->RS, 1); /* RS->1 (data mode) */ + ret = par->fbtftops.write(par, buf, len); + if (ret < 0) + dev_err(par->info->device, + "%s: write failed and returned: %d\n", + __func__, ret); + } + /* right half of display */ + if (addr_win.xe >= par->info->var.xres / 2) { + construct_line_bitmap(par, buf, + convert_buf, par->info->var.xres / 2, + addr_win.xe + 1, y); + + len = addr_win.xe + 1 - par->info->var.xres / 2; + + /* select right side (sc1) + * set addr + */ + write_reg(par, 0x01, (1 << 6)); + write_reg(par, 0x01, (0x17 << 3) | (u8)y); + + /* write bitmap */ + gpio_set_value(par->RS, 1); /* RS->1 (data mode) */ + par->fbtftops.write(par, buf, len); + if (ret < 0) + dev_err(par->info->device, + "%s: write failed and returned: %d\n", + __func__, ret); + } + } + kfree(convert_buf); + + gpio_set_value(par->CS0, 1); + gpio_set_value(par->CS1, 1); + + return ret; +} + +static int write(struct fbtft_par *par, void *buf, size_t len) +{ + fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, + "%s(len=%d): ", __func__, len); + + gpio_set_value(par->RW, 0); /* set write mode */ + + + while (len--) { + u8 i, data; + + data = *(u8 *) buf++; + + /* set data bus */ + for (i = 0; i < 8; ++i) + gpio_set_value(par->gpio.db[i], data & (1 << i)); + /* set E */ + gpio_set_value(par->EPIN, 1); + udelay(5); + /* unset E - write */ + gpio_set_value(par->EPIN, 0); + udelay(1); + } + + return 0; +} + +static struct fbtft_display display = { + .regwidth = 8, + .width = TOTALWIDTH, + .height = HEIGHT, + .fps = FPS, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .verify_gpios = verify_gpios, + .request_gpios_match = request_gpios_match, + .reset = reset, + .write = write, + .write_register = write_reg8_bus8, + .write_vmem = write_vmem, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "displaytronic,fb_agm1264k-fl", &display); + +MODULE_ALIAS("platform:" DRVNAME); + +MODULE_DESCRIPTION("Two KS0108 LCD controllers in AGM1264K-FL display"); +MODULE_AUTHOR("ololoshka2871"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_bd663474.c b/drivers/video/fbtft/fb_bd663474.c new file mode 100644 index 0000000..7e00c60 --- /dev/null +++ b/drivers/video/fbtft/fb_bd663474.c @@ -0,0 +1,193 @@ +/* + * FB driver for the uPD161704 LCD Controller + * + * Copyright (C) 2014 Seong-Woo Kim + * + * Based on fb_ili9325.c by Noralf Tronnes + * Based on ili9325.c by Jeroen Domburg + * Init code from UTFT library by Henning Karlsen + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_bd663474" +#define WIDTH 240 +#define HEIGHT 320 +#define BPP 16 + +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + if (par->gpio.cs != -1) + gpio_set_value(par->gpio.cs, 0); /* Activate chip */ + + par->fbtftops.reset(par); + + /* Initialization sequence from Lib_UTFT */ + + /* oscillator start */ + write_reg(par, 0x000,0x0001); /*oscillator 0: stop, 1: operation */ + mdelay(10); + + /* Power settings */ + write_reg(par, 0x100, 0x0000 ); /* power supply setup */ + write_reg(par, 0x101, 0x0000 ); + write_reg(par, 0x102, 0x3110 ); + write_reg(par, 0x103, 0xe200 ); + write_reg(par, 0x110, 0x009d ); + write_reg(par, 0x111, 0x0022 ); + write_reg(par, 0x100, 0x0120 ); + mdelay( 20 ); + + write_reg(par, 0x100, 0x3120 ); + mdelay( 80 ); + /* Display control */ + write_reg(par, 0x001, 0x0100 ); + write_reg(par, 0x002, 0x0000 ); + write_reg(par, 0x003, 0x1230 ); + write_reg(par, 0x006, 0x0000 ); + write_reg(par, 0x007, 0x0101 ); + write_reg(par, 0x008, 0x0808 ); + write_reg(par, 0x009, 0x0000 ); + write_reg(par, 0x00b, 0x0000 ); + write_reg(par, 0x00c, 0x0000 ); + write_reg(par, 0x00d, 0x0018 ); + /* LTPS control settings */ + write_reg(par, 0x012, 0x0000 ); + write_reg(par, 0x013, 0x0000 ); + write_reg(par, 0x018, 0x0000 ); + write_reg(par, 0x019, 0x0000 ); + + write_reg(par, 0x203, 0x0000 ); + write_reg(par, 0x204, 0x0000 ); + + write_reg(par, 0x210, 0x0000 ); + write_reg(par, 0x211, 0x00ef ); + write_reg(par, 0x212, 0x0000 ); + write_reg(par, 0x213, 0x013f ); + write_reg(par, 0x214, 0x0000 ); + write_reg(par, 0x215, 0x0000 ); + write_reg(par, 0x216, 0x0000 ); + write_reg(par, 0x217, 0x0000 ); + + /* Gray scale settings */ + write_reg(par, 0x300, 0x5343); + write_reg(par, 0x301, 0x1021); + write_reg(par, 0x302, 0x0003); + write_reg(par, 0x303, 0x0011); + write_reg(par, 0x304, 0x050a); + write_reg(par, 0x305, 0x4342); + write_reg(par, 0x306, 0x1100); + write_reg(par, 0x307, 0x0003); + write_reg(par, 0x308, 0x1201); + write_reg(par, 0x309, 0x050a); + + /* RAM access settings */ + write_reg(par, 0x400, 0x4027 ); + write_reg(par, 0x401, 0x0000 ); + write_reg(par, 0x402, 0x0000 ); /* First screen drive position (1) */ + write_reg(par, 0x403, 0x013f ); /* First screen drive position (2) */ + write_reg(par, 0x404, 0x0000 ); + + write_reg(par, 0x200, 0x0000 ); + write_reg(par, 0x201, 0x0000 ); + write_reg(par, 0x100, 0x7120 ); + write_reg(par, 0x007, 0x0103 ); + mdelay( 10 ); + write_reg(par, 0x007, 0x0113 ); + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + switch (par->info->var.rotate) { + /* R200h = Horizontal GRAM Start Address */ + /* R201h = Vertical GRAM Start Address */ + case 0: + write_reg(par, 0x0200, xs); + write_reg(par, 0x0201, ys); + break; + case 180: + write_reg(par, 0x0200, WIDTH - 1 - xs); + write_reg(par, 0x0201, HEIGHT - 1 - ys); + break; + case 270: + write_reg(par, 0x0200, WIDTH - 1 - ys); + write_reg(par, 0x0201, xs); + break; + case 90: + write_reg(par, 0x0200, ys); + write_reg(par, 0x0201, HEIGHT - 1 - xs); + break; + } + write_reg(par, 0x202); /* Write Data to GRAM */ +} + +static int set_var(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + switch (par->info->var.rotate) { + /* AM: GRAM update direction */ + case 0: + write_reg(par, 0x003, 0x1230); + break; + case 180: + write_reg(par, 0x003, 0x1200); + break; + case 270: + write_reg(par, 0x003, 0x1228); + break; + case 90: + write_reg(par, 0x003, 0x1218); + break; + } + + return 0; +} + +static struct fbtft_display display = { + .regwidth = 16, + .width = WIDTH, + .height = HEIGHT, + .bpp = BPP, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_var = set_var, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "hitachi,bd663474", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:bd663474"); +MODULE_ALIAS("platform:bd663474"); + +MODULE_DESCRIPTION("FB driver for the uPD161704 LCD Controller"); +MODULE_AUTHOR("Seong-Woo Kim"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_hx8340bn.c b/drivers/video/fbtft/fb_hx8340bn.c new file mode 100644 index 0000000..3939502 --- /dev/null +++ b/drivers/video/fbtft/fb_hx8340bn.c @@ -0,0 +1,229 @@ +/* + * FB driver for the HX8340BN LCD Controller + * + * This display uses 9-bit SPI: Data/Command bit + 8 data bits + * For platforms that doesn't support 9-bit, the driver is capable + * of emulating this using 8-bit transfer. + * This is done by transfering eight 9-bit words in 9 bytes. + * + * Copyright (C) 2013 Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_hx8340bn" +#define WIDTH 176 +#define HEIGHT 220 +#define TXBUFLEN (4 * PAGE_SIZE) +#define DEFAULT_GAMMA "1 3 0E 5 0 2 09 0 6 1 7 1 0 2 2\n" \ + "3 3 17 8 4 7 05 7 6 0 3 1 6 0 0 " + + +static bool emulate; +module_param(emulate, bool, 0); +MODULE_PARM_DESC(emulate, "Force emulation in 9-bit mode"); + + +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + /* BTL221722-276L startup sequence, from datasheet */ + + /* SETEXTCOM: Set extended command set (C1h) + This command is used to set extended command set access enable. + Enable: After command (C1h), must write: ffh,83h,40h */ + write_reg(par, 0xC1, 0xFF, 0x83, 0x40); + + /* Sleep out + This command turns off sleep mode. + In this mode the DC/DC converter is enabled, Internal oscillator + is started, and panel scanning is started. */ + write_reg(par, 0x11); + mdelay(150); + + /* Undoc'd register? */ + write_reg(par, 0xCA, 0x70, 0x00, 0xD9); + + /* SETOSC: Set Internal Oscillator (B0h) + This command is used to set internal oscillator related settings */ + /* OSC_EN: Enable internal oscillator */ + /* Internal oscillator frequency: 125% x 2.52MHz */ + write_reg(par, 0xB0, 0x01, 0x11); + + /* Drive ability setting */ + write_reg(par, 0xC9, 0x90, 0x49, 0x10, 0x28, 0x28, 0x10, 0x00, 0x06); + mdelay(20); + + /* SETPWCTR5: Set Power Control 5(B5h) + This command is used to set VCOM Low and VCOM High Voltage */ + /* VCOMH 0110101 : 3.925 */ + /* VCOML 0100000 : -1.700 */ + /* 45h=69 VCOMH: "VMH" + 5d VCOML: "VMH" + 5d */ + write_reg(par, 0xB5, 0x35, 0x20, 0x45); + + /* SETPWCTR4: Set Power Control 4(B4h) + VRH[4:0]: Specify the VREG1 voltage adjusting. + VREG1 voltage is for gamma voltage setting. + BT[2:0]: Switch the output factor of step-up circuit 2 + for VGH and VGL voltage generation. */ + write_reg(par, 0xB4, 0x33, 0x25, 0x4C); + mdelay(10); + + /* Interface Pixel Format (3Ah) + This command is used to define the format of RGB picture data, + which is to be transfer via the system and RGB interface. */ + /* RGB interface: 16 Bit/Pixel */ + write_reg(par, 0x3A, 0x05); + + /* Display on (29h) + This command is used to recover from DISPLAY OFF mode. + Output from the Frame Memory is enabled. */ + write_reg(par, 0x29); + mdelay(10); + + return 0; +} + +void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + write_reg(par, FBTFT_CASET, 0x00, xs, 0x00, xe); + write_reg(par, FBTFT_RASET, 0x00, ys, 0x00, ye); + write_reg(par, FBTFT_RAMWR); +} + +static int set_var(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* MADCTL - Memory data access control */ + /* RGB/BGR can be set with H/W pin SRGB and MADCTL BGR bit */ +#define MY (1 << 7) +#define MX (1 << 6) +#define MV (1 << 5) + switch (par->info->var.rotate) { + case 0: + write_reg(par, 0x36, (par->bgr << 3)); + break; + case 270: + write_reg(par, 0x36, MX | MV | (par->bgr << 3)); + break; + case 180: + write_reg(par, 0x36, MX | MY | (par->bgr << 3)); + break; + case 90: + write_reg(par, 0x36, MY | MV | (par->bgr << 3)); + break; + } + + return 0; +} + +/* + Gamma Curve selection, GC (only GC0 can be customized): + 0 = 2.2, 1 = 1.8, 2 = 2.5, 3 = 1.0 + Gamma string format: + OP0 OP1 CP0 CP1 CP2 CP3 CP4 MP0 MP1 MP2 MP3 MP4 MP5 CGM0 CGM1 + ON0 ON1 CN0 CN1 CN2 CN3 CN4 MN0 MN1 MN2 MN3 MN4 MN5 XXXX GC +*/ +#define CURVE(num, idx) curves[num*par->gamma.num_values + idx] +static int set_gamma(struct fbtft_par *par, unsigned long *curves) +{ + unsigned long mask[] = { + 0b1111, 0b1111, 0b11111, 0b1111, 0b1111, 0b1111, 0b11111, + 0b111, 0b111, 0b111, 0b111, 0b111, 0b111, 0b11, 0b11, + 0b1111, 0b1111, 0b11111, 0b1111, 0b1111, 0b1111, 0b11111, + 0b111, 0b111, 0b111, 0b111, 0b111, 0b111, 0b0, 0b0 }; + int i, j; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* apply mask */ + for (i = 0; i < par->gamma.num_curves; i++) + for (j = 0; j < par->gamma.num_values; j++) + CURVE(i, j) &= mask[i * par->gamma.num_values + j]; + + write_reg(par, 0x26, 1 << CURVE(1, 14)); /* Gamma Set (26h) */ + + if (CURVE(1, 14)) + return 0; /* only GC0 can be customized */ + + write_reg(par, 0xC2, + (CURVE(0, 8) << 4) | CURVE(0, 7), + (CURVE(0, 10) << 4) | CURVE(0, 9), + (CURVE(0, 12) << 4) | CURVE(0, 11), + CURVE(0, 2), + (CURVE(0, 4) << 4) | CURVE(0, 3), + CURVE(0, 5), + CURVE(0, 6), + (CURVE(0, 1) << 4) | CURVE(0, 0), + (CURVE(0, 14) << 2) | CURVE(0, 13)); + + write_reg(par, 0xC3, + (CURVE(1, 8) << 4) | CURVE(1, 7), + (CURVE(1, 10) << 4) | CURVE(1, 9), + (CURVE(1, 12) << 4) | CURVE(1, 11), + CURVE(1, 2), + (CURVE(1, 4) << 4) | CURVE(1, 3), + CURVE(1, 5), + CURVE(1, 6), + (CURVE(1, 1) << 4) | CURVE(1, 0)); + + mdelay(10); + + return 0; +} +#undef CURVE + + +static struct fbtft_display display = { + .regwidth = 8, + .width = WIDTH, + .height = HEIGHT, + .txbuflen = TXBUFLEN, + .gamma_num = 2, + .gamma_len = 15, + .gamma = DEFAULT_GAMMA, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_var = set_var, + .set_gamma = set_gamma, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "himax,hx8340bn", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:hx8340bn"); +MODULE_ALIAS("platform:hx8340bn"); + +MODULE_DESCRIPTION("FB driver for the HX8340BN LCD Controller"); +MODULE_AUTHOR("Noralf Tronnes"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_hx8347d.c b/drivers/video/fbtft/fb_hx8347d.c new file mode 100644 index 0000000..8139a8f --- /dev/null +++ b/drivers/video/fbtft/fb_hx8347d.c @@ -0,0 +1,181 @@ +/* + * FB driver for the HX8347D LCD Controller + * + * Copyright (C) 2013 Christian Vogelgsang + * + * Based on driver code found here: https://github.com/watterott/r61505u-Adapter + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_hx8347d" +#define WIDTH 320 +#define HEIGHT 240 +#define DEFAULT_GAMMA "0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" \ + "0 0 0 0 0 0 0 0 0 0 0 0 0 0" + + +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + /* driving ability */ + write_reg(par, 0xEA, 0x00); + write_reg(par, 0xEB, 0x20); + write_reg(par, 0xEC, 0x0C); + write_reg(par, 0xED, 0xC4); + write_reg(par, 0xE8, 0x40); + write_reg(par, 0xE9, 0x38); + write_reg(par, 0xF1, 0x01); + write_reg(par, 0xF2, 0x10); + write_reg(par, 0x27, 0xA3); + + /* power voltage */ + write_reg(par, 0x1B, 0x1B); + write_reg(par, 0x1A, 0x01); + write_reg(par, 0x24, 0x2F); + write_reg(par, 0x25, 0x57); + + /* VCOM offset */ + write_reg(par, 0x23, 0x8D); /* for flicker adjust */ + + /* power on */ + write_reg(par, 0x18, 0x36); + write_reg(par, 0x19, 0x01); /* start osc */ + write_reg(par, 0x01, 0x00); /* wakeup */ + write_reg(par, 0x1F, 0x88); + mdelay(5); + write_reg(par, 0x1F, 0x80); + mdelay(5); + write_reg(par, 0x1F, 0x90); + mdelay(5); + write_reg(par, 0x1F, 0xD0); + mdelay(5); + + /* color selection */ + write_reg(par, 0x17, 0x05); /* 65k */ + + /*panel characteristic */ + write_reg(par, 0x36, 0x00); + + /*display on */ + write_reg(par, 0x28, 0x38); + mdelay(40); + write_reg(par, 0x28, 0x3C); + + /* orientation */ + write_reg(par, 0x16, 0x60 | (par->bgr << 3)); + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + write_reg(par, 0x02, (xs >> 8) & 0xFF); + write_reg(par, 0x03, xs & 0xFF); + write_reg(par, 0x04, (xe >> 8) & 0xFF); + write_reg(par, 0x05, xe & 0xFF); + write_reg(par, 0x06, (ys >> 8) & 0xFF); + write_reg(par, 0x07, ys & 0xFF); + write_reg(par, 0x08, (ye >> 8) & 0xFF); + write_reg(par, 0x09, ye & 0xFF); + write_reg(par, 0x22); +} + +/* + Gamma string format: + VRP0 VRP1 VRP2 VRP3 VRP4 VRP5 PRP0 PRP1 PKP0 PKP1 PKP2 PKP3 PKP4 CGM + VRN0 VRN1 VRN2 VRN3 VRN4 VRN5 PRN0 PRN1 PKN0 PKN1 PKN2 PKN3 PKN4 CGM +*/ +#define CURVE(num, idx) curves[num*par->gamma.num_values + idx] +static int set_gamma(struct fbtft_par *par, unsigned long *curves) +{ + unsigned long mask[] = { + 0b111111, 0b111111, 0b111111, 0b111111, 0b111111, 0b111111, + 0b1111111, 0b1111111, + 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, + 0b1111}; + int i, j; + int acc = 0; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* apply mask */ + for (i = 0; i < par->gamma.num_curves; i++) + for (j = 0; j < par->gamma.num_values; j++) { + acc += CURVE(i, j); + CURVE(i, j) &= mask[j]; + } + + if (acc == 0) /* skip if all values are zero */ + return 0; + + for (i = 0; i < par->gamma.num_curves; i++) { + write_reg(par, 0x40 + (i * 0x10), CURVE(i, 0)); + write_reg(par, 0x41 + (i * 0x10), CURVE(i, 1)); + write_reg(par, 0x42 + (i * 0x10), CURVE(i, 2)); + write_reg(par, 0x43 + (i * 0x10), CURVE(i, 3)); + write_reg(par, 0x44 + (i * 0x10), CURVE(i, 4)); + write_reg(par, 0x45 + (i * 0x10), CURVE(i, 5)); + write_reg(par, 0x46 + (i * 0x10), CURVE(i, 6)); + write_reg(par, 0x47 + (i * 0x10), CURVE(i, 7)); + write_reg(par, 0x48 + (i * 0x10), CURVE(i, 8)); + write_reg(par, 0x49 + (i * 0x10), CURVE(i, 9)); + write_reg(par, 0x4A + (i * 0x10), CURVE(i, 10)); + write_reg(par, 0x4B + (i * 0x10), CURVE(i, 11)); + write_reg(par, 0x4C + (i * 0x10), CURVE(i, 12)); + } + write_reg(par, 0x5D, (CURVE(1, 0) << 4) | CURVE(0, 0)); + + return 0; +} +#undef CURVE + + +static struct fbtft_display display = { + .regwidth = 8, + .width = WIDTH, + .height = HEIGHT, + .gamma_num = 2, + .gamma_len = 14, + .gamma = DEFAULT_GAMMA, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_gamma = set_gamma, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "himax,hx8347d", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:hx8347d"); +MODULE_ALIAS("platform:hx8347d"); + +MODULE_DESCRIPTION("FB driver for the HX8347D LCD Controller"); +MODULE_AUTHOR("Christian Vogelgsang"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_hx8353d.c b/drivers/video/fbtft/fb_hx8353d.c new file mode 100644 index 0000000..c9512dc --- /dev/null +++ b/drivers/video/fbtft/fb_hx8353d.c @@ -0,0 +1,166 @@ +/* + * FB driver for the HX8353D LCD Controller + * + * Copyright (c) 2014 Petr Olivka + * Copyright (c) 2013 Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_hx8353d" +#define DEFAULT_GAMMA "50 77 40 08 BF 00 03 0F 00 01 73 00 72 03 B0 0F 08 00 0F" + +static int init_display(struct fbtft_par *par) +{ + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + mdelay(150); + + /* SETEXTC */ + write_reg(par, 0xB9, 0xFF, 0x83, 0x53); + + /* RADJ */ + write_reg(par, 0xB0, 0x3C, 0x01); + + /* VCOM */ + write_reg(par, 0xB6, 0x94, 0x6C, 0x50); + + /* PWR */ + write_reg(par, 0xB1, 0x00, 0x01, 0x1B, 0x03, 0x01, 0x08, 0x77, 0x89); + + /* COLMOD */ + write_reg(par, 0x3A, 0x05); + + /* MEM ACCESS */ + write_reg(par, 0x36, 0xC0); + + /* SLPOUT - Sleep out & booster on */ + write_reg(par, 0x11); + mdelay(150); + + /* DISPON - Display On */ + write_reg(par, 0x29); + + /* RGBSET */ + write_reg(par, 0x2D, + 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62); + + return 0; +}; + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* column address */ + write_reg(par, 0x2a, xs >> 8, xs & 0xff, xe >> 8, xe & 0xff); + + /* row adress */ + write_reg(par, 0x2b, ys >> 8, ys & 0xff, ye >> 8, ye & 0xff); + + /* memory write */ + write_reg(par, 0x2c); +} + +#define my (1 << 7) +#define mx (1 << 6) +#define mv (1 << 5) +static int set_var(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* madctl - memory data access control + rgb/bgr: + 1. mode selection pin srgb + rgb h/w pin for color filter setting: 0=rgb, 1=bgr + 2. madctl rgb bit + rgb-bgr order color filter panel: 0=rgb, 1=bgr */ + switch (par->info->var.rotate) { + case 0: + write_reg(par, 0x36, mx | my | (par->bgr << 3)); + break; + case 270: + write_reg(par, 0x36, my | mv | (par->bgr << 3)); + break; + case 180: + write_reg(par, 0x36, (par->bgr << 3)); + break; + case 90: + write_reg(par, 0x36, mx | mv | (par->bgr << 3)); + break; + } + + return 0; +} + +/* + gamma string format: +*/ +static int set_gamma(struct fbtft_par *par, unsigned long *curves) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + write_reg(par, 0xE0, + curves[0], curves[1], curves[2], curves[3], + curves[4], curves[5], curves[6], curves[7], + curves[8], curves[9], curves[10], curves[11], + curves[12], curves[13], curves[14], curves[15], + curves[16], curves[17], curves[18]); + + return 0; +} + + +static struct fbtft_display display = { + .regwidth = 8, + .width = 128, + .height = 160, + .gamma_num = 1, + .gamma_len = 19, + .gamma = DEFAULT_GAMMA, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_var = set_var, + .set_gamma = set_gamma, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "himax,hx8353d", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:hx8353d"); +MODULE_ALIAS("platform:hx8353d"); + +MODULE_DESCRIPTION("FB driver for the HX8353D LCD Controller"); +MODULE_AUTHOR("Petr Olivka"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_ili9320.c b/drivers/video/fbtft/fb_ili9320.c new file mode 100644 index 0000000..b26d893 --- /dev/null +++ b/drivers/video/fbtft/fb_ili9320.c @@ -0,0 +1,234 @@ +/* + * FB driver for the ILI9320 LCD Controller + * + * Copyright (C) 2013 Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_ili9320" +#define WIDTH 240 +#define HEIGHT 320 +#define DEFAULT_GAMMA "07 07 6 0 0 0 5 5 4 0\n" \ + "07 08 4 7 5 1 2 0 7 7" + + +static unsigned read_devicecode(struct fbtft_par *par) +{ + int ret; + u8 rxbuf[8] = {0, }; + + write_reg(par, 0x0000); + ret = par->fbtftops.read(par, rxbuf, 4); + return (rxbuf[2] << 8) | rxbuf[3]; +} + +static int init_display(struct fbtft_par *par) +{ + unsigned devcode; + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + devcode = read_devicecode(par); + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "Device code: 0x%04X\n", + devcode); + if ((devcode != 0x0000) && (devcode != 0x9320)) + dev_warn(par->info->device, + "Unrecognized Device code: 0x%04X (expected 0x9320)\n", + devcode); + + /* Initialization sequence from ILI9320 Application Notes */ + + /* *********** Start Initial Sequence ********* */ + write_reg(par, 0x00E5, 0x8000); /* Set the Vcore voltage and this setting is must. */ + write_reg(par, 0x0000, 0x0001); /* Start internal OSC. */ + write_reg(par, 0x0001, 0x0100); /* set SS and SM bit */ + write_reg(par, 0x0002, 0x0700); /* set 1 line inversion */ + write_reg(par, 0x0004, 0x0000); /* Resize register */ + write_reg(par, 0x0008, 0x0202); /* set the back and front porch */ + write_reg(par, 0x0009, 0x0000); /* set non-display area refresh cycle */ + write_reg(par, 0x000A, 0x0000); /* FMARK function */ + write_reg(par, 0x000C, 0x0000); /* RGB interface setting */ + write_reg(par, 0x000D, 0x0000); /* Frame marker Position */ + write_reg(par, 0x000F, 0x0000); /* RGB interface polarity */ + + /* ***********Power On sequence *************** */ + write_reg(par, 0x0010, 0x0000); /* SAP, BT[3:0], AP, DSTB, SLP, STB */ + write_reg(par, 0x0011, 0x0007); /* DC1[2:0], DC0[2:0], VC[2:0] */ + write_reg(par, 0x0012, 0x0000); /* VREG1OUT voltage */ + write_reg(par, 0x0013, 0x0000); /* VDV[4:0] for VCOM amplitude */ + mdelay(200); /* Dis-charge capacitor power voltage */ + write_reg(par, 0x0010, 0x17B0); /* SAP, BT[3:0], AP, DSTB, SLP, STB */ + write_reg(par, 0x0011, 0x0031); /* R11h=0x0031 at VCI=3.3V DC1[2:0], DC0[2:0], VC[2:0] */ + mdelay(50); + write_reg(par, 0x0012, 0x0138); /* R12h=0x0138 at VCI=3.3V VREG1OUT voltage */ + mdelay(50); + write_reg(par, 0x0013, 0x1800); /* R13h=0x1800 at VCI=3.3V VDV[4:0] for VCOM amplitude */ + write_reg(par, 0x0029, 0x0008); /* R29h=0x0008 at VCI=3.3V VCM[4:0] for VCOMH */ + mdelay(50); + write_reg(par, 0x0020, 0x0000); /* GRAM horizontal Address */ + write_reg(par, 0x0021, 0x0000); /* GRAM Vertical Address */ + + /* ------------------ Set GRAM area --------------- */ + write_reg(par, 0x0050, 0x0000); /* Horizontal GRAM Start Address */ + write_reg(par, 0x0051, 0x00EF); /* Horizontal GRAM End Address */ + write_reg(par, 0x0052, 0x0000); /* Vertical GRAM Start Address */ + write_reg(par, 0x0053, 0x013F); /* Vertical GRAM Start Address */ + write_reg(par, 0x0060, 0x2700); /* Gate Scan Line */ + write_reg(par, 0x0061, 0x0001); /* NDL,VLE, REV */ + write_reg(par, 0x006A, 0x0000); /* set scrolling line */ + + /* -------------- Partial Display Control --------- */ + write_reg(par, 0x0080, 0x0000); + write_reg(par, 0x0081, 0x0000); + write_reg(par, 0x0082, 0x0000); + write_reg(par, 0x0083, 0x0000); + write_reg(par, 0x0084, 0x0000); + write_reg(par, 0x0085, 0x0000); + + /* -------------- Panel Control ------------------- */ + write_reg(par, 0x0090, 0x0010); + write_reg(par, 0x0092, 0x0000); + write_reg(par, 0x0093, 0x0003); + write_reg(par, 0x0095, 0x0110); + write_reg(par, 0x0097, 0x0000); + write_reg(par, 0x0098, 0x0000); + write_reg(par, 0x0007, 0x0173); /* 262K color and display ON */ + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + switch (par->info->var.rotate) { + /* R20h = Horizontal GRAM Start Address */ + /* R21h = Vertical GRAM Start Address */ + case 0: + write_reg(par, 0x0020, xs); + write_reg(par, 0x0021, ys); + break; + case 180: + write_reg(par, 0x0020, WIDTH - 1 - xs); + write_reg(par, 0x0021, HEIGHT - 1 - ys); + break; + case 270: + write_reg(par, 0x0020, WIDTH - 1 - ys); + write_reg(par, 0x0021, xs); + break; + case 90: + write_reg(par, 0x0020, ys); + write_reg(par, 0x0021, HEIGHT - 1 - xs); + break; + } + write_reg(par, 0x0022); /* Write Data to GRAM */ +} + +static int set_var(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + switch (par->info->var.rotate) { + case 0: + write_reg(par, 0x3, (par->bgr << 12) | 0x30); + break; + case 270: + write_reg(par, 0x3, (par->bgr << 12) | 0x28); + break; + case 180: + write_reg(par, 0x3, (par->bgr << 12) | 0x00); + break; + case 90: + write_reg(par, 0x3, (par->bgr << 12) | 0x18); + break; + } + return 0; +} + +/* + Gamma string format: + VRP0 VRP1 RP0 RP1 KP0 KP1 KP2 KP3 KP4 KP5 + VRN0 VRN1 RN0 RN1 KN0 KN1 KN2 KN3 KN4 KN5 +*/ +#define CURVE(num, idx) curves[num*par->gamma.num_values + idx] +static int set_gamma(struct fbtft_par *par, unsigned long *curves) +{ + unsigned long mask[] = { + 0b11111, 0b11111, 0b111, 0b111, 0b111, + 0b111, 0b111, 0b111, 0b111, 0b111, + 0b11111, 0b11111, 0b111, 0b111, 0b111, + 0b111, 0b111, 0b111, 0b111, 0b111 }; + int i, j; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* apply mask */ + for (i = 0; i < 2; i++) + for (j = 0; j < 10; j++) + CURVE(i, j) &= mask[i*par->gamma.num_values + j]; + + write_reg(par, 0x0030, CURVE(0, 5) << 8 | CURVE(0, 4)); + write_reg(par, 0x0031, CURVE(0, 7) << 8 | CURVE(0, 6)); + write_reg(par, 0x0032, CURVE(0, 9) << 8 | CURVE(0, 8)); + write_reg(par, 0x0035, CURVE(0, 3) << 8 | CURVE(0, 2)); + write_reg(par, 0x0036, CURVE(0, 1) << 8 | CURVE(0, 0)); + + write_reg(par, 0x0037, CURVE(1, 5) << 8 | CURVE(1, 4)); + write_reg(par, 0x0038, CURVE(1, 7) << 8 | CURVE(1, 6)); + write_reg(par, 0x0039, CURVE(1, 9) << 8 | CURVE(1, 8)); + write_reg(par, 0x003C, CURVE(1, 3) << 8 | CURVE(1, 2)); + write_reg(par, 0x003D, CURVE(1, 1) << 8 | CURVE(1, 0)); + + return 0; +} +#undef CURVE + + +static struct fbtft_display display = { + .regwidth = 16, + .width = WIDTH, + .height = HEIGHT, + .gamma_num = 2, + .gamma_len = 10, + .gamma = DEFAULT_GAMMA, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_var = set_var, + .set_gamma = set_gamma, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "ilitek,ili9320", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:ili9320"); +MODULE_ALIAS("platform:ili9320"); + +MODULE_DESCRIPTION("FB driver for the ILI9320 LCD Controller"); +MODULE_AUTHOR("Noralf Tronnes"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_ili9325.c b/drivers/video/fbtft/fb_ili9325.c new file mode 100644 index 0000000..5f88145 --- /dev/null +++ b/drivers/video/fbtft/fb_ili9325.c @@ -0,0 +1,291 @@ +/* + * FB driver for the ILI9325 LCD Controller + * + * Copyright (C) 2013 Noralf Tronnes + * + * Based on ili9325.c by Jeroen Domburg + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_ili9325" +#define WIDTH 240 +#define HEIGHT 320 +#define BPP 16 +#define FPS 20 +#define DEFAULT_GAMMA "0F 00 7 2 0 0 6 5 4 1\n" \ + "04 16 2 7 6 3 2 1 7 7" + + +static unsigned bt = 6; /* VGL=Vci*4 , VGH=Vci*4 */ +module_param(bt, uint, 0); +MODULE_PARM_DESC(bt, "Sets the factor used in the step-up circuits"); + +static unsigned vc = 0b011; /* Vci1=Vci*0.80 */ +module_param(vc, uint, 0); +MODULE_PARM_DESC(vc, +"Sets the ratio factor of Vci to generate the reference voltages Vci1"); + +static unsigned vrh = 0b1101; /* VREG1OUT=Vci*1.85 */ +module_param(vrh, uint, 0); +MODULE_PARM_DESC(vrh, +"Set the amplifying rate (1.6 ~ 1.9) of Vci applied to output the VREG1OUT"); + +static unsigned vdv = 0b10010; /* VCOMH amplitude=VREG1OUT*0.98 */ +module_param(vdv, uint, 0); +MODULE_PARM_DESC(vdv, +"Select the factor of VREG1OUT to set the amplitude of Vcom"); + +static unsigned vcm = 0b001010; /* VCOMH=VREG1OUT*0.735 */ +module_param(vcm, uint, 0); +MODULE_PARM_DESC(vcm, "Set the internal VcomH voltage"); + + +/* +Verify that this configuration is within the Voltage limits + +Display module configuration: Vcc = IOVcc = Vci = 3.3V + + Voltages +---------- +Vci = 3.3 +Vci1 = Vci * 0.80 = 2.64 +DDVDH = Vci1 * 2 = 5.28 +VCL = -Vci1 = -2.64 +VREG1OUT = Vci * 1.85 = 4.88 +VCOMH = VREG1OUT * 0.735 = 3.59 +VCOM amplitude = VREG1OUT * 0.98 = 4.79 +VGH = Vci * 4 = 13.2 +VGL = -Vci * 4 = -13.2 + + Limits +-------- +Power supplies +1.65 < IOVcc < 3.30 => 1.65 < 3.3 < 3.30 +2.40 < Vcc < 3.30 => 2.40 < 3.3 < 3.30 +2.50 < Vci < 3.30 => 2.50 < 3.3 < 3.30 + +Source/VCOM power supply voltage + 4.50 < DDVDH < 6.0 => 4.50 < 5.28 < 6.0 +-3.0 < VCL < -2.0 => -3.0 < -2.64 < -2.0 +VCI - VCL < 6.0 => 5.94 < 6.0 + +Gate driver output voltage + 10 < VGH < 20 => 10 < 13.2 < 20 +-15 < VGL < -5 => -15 < -13.2 < -5 +VGH - VGL < 32 => 26.4 < 32 + +VCOM driver output voltage +VCOMH - VCOML < 6.0 => 4.79 < 6.0 +*/ + +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + if (par->gpio.cs != -1) + gpio_set_value(par->gpio.cs, 0); /* Activate chip */ + + bt &= 0b111; + vc &= 0b111; + vrh &= 0b1111; + vdv &= 0b11111; + vcm &= 0b111111; + + /* Initialization sequence from ILI9325 Application Notes */ + + /* ----------- Start Initial Sequence ----------- */ + write_reg(par, 0x00E3, 0x3008); /* Set internal timing */ + write_reg(par, 0x00E7, 0x0012); /* Set internal timing */ + write_reg(par, 0x00EF, 0x1231); /* Set internal timing */ + write_reg(par, 0x0001, 0x0100); /* set SS and SM bit */ + write_reg(par, 0x0002, 0x0700); /* set 1 line inversion */ + write_reg(par, 0x0004, 0x0000); /* Resize register */ + write_reg(par, 0x0008, 0x0207); /* set the back porch and front porch */ + write_reg(par, 0x0009, 0x0000); /* set non-display area refresh cycle */ + write_reg(par, 0x000A, 0x0000); /* FMARK function */ + write_reg(par, 0x000C, 0x0000); /* RGB interface setting */ + write_reg(par, 0x000D, 0x0000); /* Frame marker Position */ + write_reg(par, 0x000F, 0x0000); /* RGB interface polarity */ + + /* ----------- Power On sequence ----------- */ + write_reg(par, 0x0010, 0x0000); /* SAP, BT[3:0], AP, DSTB, SLP, STB */ + write_reg(par, 0x0011, 0x0007); /* DC1[2:0], DC0[2:0], VC[2:0] */ + write_reg(par, 0x0012, 0x0000); /* VREG1OUT voltage */ + write_reg(par, 0x0013, 0x0000); /* VDV[4:0] for VCOM amplitude */ + mdelay(200); /* Dis-charge capacitor power voltage */ + write_reg(par, 0x0010, /* SAP, BT[3:0], AP, DSTB, SLP, STB */ + (1 << 12) | (bt << 8) | (1 << 7) | (0b001 << 4)); + write_reg(par, 0x0011, 0x220 | vc); /* DC1[2:0], DC0[2:0], VC[2:0] */ + mdelay(50); /* Delay 50ms */ + write_reg(par, 0x0012, vrh); /* Internal reference voltage= Vci; */ + mdelay(50); /* Delay 50ms */ + write_reg(par, 0x0013, vdv << 8); /* Set VDV[4:0] for VCOM amplitude */ + write_reg(par, 0x0029, vcm); /* Set VCM[5:0] for VCOMH */ + write_reg(par, 0x002B, 0x000C); /* Set Frame Rate */ + mdelay(50); /* Delay 50ms */ + write_reg(par, 0x0020, 0x0000); /* GRAM horizontal Address */ + write_reg(par, 0x0021, 0x0000); /* GRAM Vertical Address */ + + /*------------------ Set GRAM area --------------- */ + write_reg(par, 0x0050, 0x0000); /* Horizontal GRAM Start Address */ + write_reg(par, 0x0051, 0x00EF); /* Horizontal GRAM End Address */ + write_reg(par, 0x0052, 0x0000); /* Vertical GRAM Start Address */ + write_reg(par, 0x0053, 0x013F); /* Vertical GRAM Start Address */ + write_reg(par, 0x0060, 0xA700); /* Gate Scan Line */ + write_reg(par, 0x0061, 0x0001); /* NDL,VLE, REV */ + write_reg(par, 0x006A, 0x0000); /* set scrolling line */ + + /*-------------- Partial Display Control --------- */ + write_reg(par, 0x0080, 0x0000); + write_reg(par, 0x0081, 0x0000); + write_reg(par, 0x0082, 0x0000); + write_reg(par, 0x0083, 0x0000); + write_reg(par, 0x0084, 0x0000); + write_reg(par, 0x0085, 0x0000); + + /*-------------- Panel Control ------------------- */ + write_reg(par, 0x0090, 0x0010); + write_reg(par, 0x0092, 0x0600); + write_reg(par, 0x0007, 0x0133); /* 262K color and display ON */ + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + switch (par->info->var.rotate) { + /* R20h = Horizontal GRAM Start Address */ + /* R21h = Vertical GRAM Start Address */ + case 0: + write_reg(par, 0x0020, xs); + write_reg(par, 0x0021, ys); + break; + case 180: + write_reg(par, 0x0020, WIDTH - 1 - xs); + write_reg(par, 0x0021, HEIGHT - 1 - ys); + break; + case 270: + write_reg(par, 0x0020, WIDTH - 1 - ys); + write_reg(par, 0x0021, xs); + break; + case 90: + write_reg(par, 0x0020, ys); + write_reg(par, 0x0021, HEIGHT - 1 - xs); + break; + } + write_reg(par, 0x0022); /* Write Data to GRAM */ +} + +static int set_var(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + switch (par->info->var.rotate) { + /* AM: GRAM update direction */ + case 0: + write_reg(par, 0x03, 0x0030 | (par->bgr << 12)); + break; + case 180: + write_reg(par, 0x03, 0x0000 | (par->bgr << 12)); + break; + case 270: + write_reg(par, 0x03, 0x0028 | (par->bgr << 12)); + break; + case 90: + write_reg(par, 0x03, 0x0018 | (par->bgr << 12)); + break; + } + + return 0; +} + +/* + Gamma string format: + VRP0 VRP1 RP0 RP1 KP0 KP1 KP2 KP3 KP4 KP5 + VRN0 VRN1 RN0 RN1 KN0 KN1 KN2 KN3 KN4 KN5 +*/ +#define CURVE(num, idx) curves[num*par->gamma.num_values + idx] +static int set_gamma(struct fbtft_par *par, unsigned long *curves) +{ + unsigned long mask[] = { + 0b11111, 0b11111, 0b111, 0b111, 0b111, + 0b111, 0b111, 0b111, 0b111, 0b111, + 0b11111, 0b11111, 0b111, 0b111, 0b111, + 0b111, 0b111, 0b111, 0b111, 0b111 }; + int i, j; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* apply mask */ + for (i = 0; i < 2; i++) + for (j = 0; j < 10; j++) + CURVE(i, j) &= mask[i*par->gamma.num_values + j]; + + write_reg(par, 0x0030, CURVE(0, 5) << 8 | CURVE(0, 4)); + write_reg(par, 0x0031, CURVE(0, 7) << 8 | CURVE(0, 6)); + write_reg(par, 0x0032, CURVE(0, 9) << 8 | CURVE(0, 8)); + write_reg(par, 0x0035, CURVE(0, 3) << 8 | CURVE(0, 2)); + write_reg(par, 0x0036, CURVE(0, 1) << 8 | CURVE(0, 0)); + + write_reg(par, 0x0037, CURVE(1, 5) << 8 | CURVE(1, 4)); + write_reg(par, 0x0038, CURVE(1, 7) << 8 | CURVE(1, 6)); + write_reg(par, 0x0039, CURVE(1, 9) << 8 | CURVE(1, 8)); + write_reg(par, 0x003C, CURVE(1, 3) << 8 | CURVE(1, 2)); + write_reg(par, 0x003D, CURVE(1, 1) << 8 | CURVE(1, 0)); + + return 0; +} +#undef CURVE + + +static struct fbtft_display display = { + .regwidth = 16, + .width = WIDTH, + .height = HEIGHT, + .bpp = BPP, + .fps = FPS, + .gamma_num = 2, + .gamma_len = 10, + .gamma = DEFAULT_GAMMA, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_var = set_var, + .set_gamma = set_gamma, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "ilitek,ili9325", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:ili9325"); +MODULE_ALIAS("platform:ili9325"); + +MODULE_DESCRIPTION("FB driver for the ILI9325 LCD Controller"); +MODULE_AUTHOR("Noralf Tronnes"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_ili9340.c b/drivers/video/fbtft/fb_ili9340.c new file mode 100644 index 0000000..985687d --- /dev/null +++ b/drivers/video/fbtft/fb_ili9340.c @@ -0,0 +1,163 @@ +/* + * FB driver for the ILI9340 LCD Controller + * + * Copyright (C) 2013 Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_ili9340" +#define WIDTH 240 +#define HEIGHT 320 + + +/* Init sequence taken from: Arduino Library for the Adafruit 2.2" display */ +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + write_reg(par, 0xEF, 0x03, 0x80, 0x02); + write_reg(par, 0xCF, 0x00 , 0XC1 , 0X30); + write_reg(par, 0xED, 0x64 , 0x03 , 0X12 , 0X81); + write_reg(par, 0xE8, 0x85 , 0x00 , 0x78); + write_reg(par, 0xCB, 0x39 , 0x2C , 0x00 , 0x34 , 0x02); + write_reg(par, 0xF7, 0x20); + write_reg(par, 0xEA, 0x00 , 0x00); + + /* Power Control 1 */ + write_reg(par, 0xC0, 0x23); + + /* Power Control 2 */ + write_reg(par, 0xC1, 0x10); + + /* VCOM Control 1 */ + write_reg(par, 0xC5, 0x3e, 0x28); + + /* VCOM Control 2 */ + write_reg(par, 0xC7, 0x86); + + /* COLMOD: Pixel Format Set */ + /* 16 bits/pixel */ + write_reg(par, 0x3A, 0x55); + + /* Frame Rate Control */ + /* Division ratio = fosc, Frame Rate = 79Hz */ + write_reg(par, 0xB1, 0x00, 0x18); + + /* Display Function Control */ + write_reg(par, 0xB6, 0x08, 0x82, 0x27); + + /* Gamma Function Disable */ + write_reg(par, 0xF2, 0x00); + + /* Gamma curve selected */ + write_reg(par, 0x26, 0x01); + + /* Positive Gamma Correction */ + write_reg(par, 0xE0, + 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, + 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00); + + /* Negative Gamma Correction */ + write_reg(par, 0xE1, + 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, + 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F); + + /* Sleep OUT */ + write_reg(par, 0x11); + + mdelay(120); + + /* Display ON */ + write_reg(par, 0x29); + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* Column address */ + write_reg(par, 0x2A, xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF); + + /* Row adress */ + write_reg(par, 0x2B, ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF); + + /* Memory write */ + write_reg(par, 0x2C); +} + +#define ILI9340_MADCTL_MV 0x20 +#define ILI9340_MADCTL_MX 0x40 +#define ILI9340_MADCTL_MY 0x80 +static int set_var(struct fbtft_par *par) +{ + u8 val; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + switch (par->info->var.rotate) { + case 270: + val = ILI9340_MADCTL_MV; + break; + case 180: + val = ILI9340_MADCTL_MY; + break; + case 90: + val = ILI9340_MADCTL_MV | ILI9340_MADCTL_MY | ILI9340_MADCTL_MX; + break; + default: + val = ILI9340_MADCTL_MX; + break; + } + /* Memory Access Control */ + write_reg(par, 0x36, val | (par->bgr << 3)); + + return 0; +} + + +static struct fbtft_display display = { + .regwidth = 8, + .width = WIDTH, + .height = HEIGHT, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_var = set_var, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "ilitek,ili9340", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:ili9340"); +MODULE_ALIAS("platform:ili9340"); + +MODULE_DESCRIPTION("FB driver for the ILI9340 LCD Controller"); +MODULE_AUTHOR("Noralf Tronnes"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_ili9341.c b/drivers/video/fbtft/fb_ili9341.c new file mode 100644 index 0000000..225b2d8 --- /dev/null +++ b/drivers/video/fbtft/fb_ili9341.c @@ -0,0 +1,179 @@ +/* + * FB driver for the ILI9341 LCD display controller + * + * This display uses 9-bit SPI: Data/Command bit + 8 data bits + * For platforms that doesn't support 9-bit, the driver is capable + * of emulating this using 8-bit transfer. + * This is done by transfering eight 9-bit words in 9 bytes. + * + * Copyright (C) 2013 Christian Vogelgsang + * Based on adafruit22fb.c by Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_ili9341" +#define WIDTH 240 +#define HEIGHT 320 +#define TXBUFLEN (4 * PAGE_SIZE) +#define DEFAULT_GAMMA "1F 1A 18 0A 0F 06 45 87 32 0A 07 02 07 05 00\n" \ + "00 25 27 05 10 09 3A 78 4D 05 18 0D 38 3A 1F" + + +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + /* startup sequence for MI0283QT-9A */ + write_reg(par, 0x01); /* software reset */ + mdelay(5); + write_reg(par, 0x28); /* display off */ + /* --------------------------------------------------------- */ + write_reg(par, 0xCF, 0x00, 0x83, 0x30); + write_reg(par, 0xED, 0x64, 0x03, 0x12, 0x81); + write_reg(par, 0xE8, 0x85, 0x01, 0x79); + write_reg(par, 0xCB, 0x39, 0X2C, 0x00, 0x34, 0x02); + write_reg(par, 0xF7, 0x20); + write_reg(par, 0xEA, 0x00, 0x00); + /* ------------power control-------------------------------- */ + write_reg(par, 0xC0, 0x26); + write_reg(par, 0xC1, 0x11); + /* ------------VCOM --------- */ + write_reg(par, 0xC5, 0x35, 0x3E); + write_reg(par, 0xC7, 0xBE); + /* ------------memory access control------------------------ */ + write_reg(par, 0x3A, 0x55); /* 16bit pixel */ + /* ------------frame rate----------------------------------- */ + write_reg(par, 0xB1, 0x00, 0x1B); + /* ------------Gamma---------------------------------------- */ + /* write_reg(par, 0xF2, 0x08); */ /* Gamma Function Disable */ + write_reg(par, 0x26, 0x01); + /* ------------display-------------------------------------- */ + write_reg(par, 0xB7, 0x07); /* entry mode set */ + write_reg(par, 0xB6, 0x0A, 0x82, 0x27, 0x00); + write_reg(par, 0x11); /* sleep out */ + mdelay(100); + write_reg(par, 0x29); /* display on */ + mdelay(20); + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* Column address set */ + write_reg(par, 0x2A, + (xs >> 8) & 0xFF, xs & 0xFF, (xe >> 8) & 0xFF, xe & 0xFF); + + /* Row adress set */ + write_reg(par, 0x2B, + (ys >> 8) & 0xFF, ys & 0xFF, (ye >> 8) & 0xFF, ye & 0xFF); + + /* Memory write */ + write_reg(par, 0x2C); +} + +#define MEM_Y (7) /* MY row address order */ +#define MEM_X (6) /* MX column address order */ +#define MEM_V (5) /* MV row / column exchange */ +#define MEM_L (4) /* ML vertical refresh order */ +#define MEM_H (2) /* MH horizontal refresh order */ +#define MEM_BGR (3) /* RGB-BGR Order */ +static int set_var(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + switch (par->info->var.rotate) { + case 0: + write_reg(par, 0x36, (1 << MEM_X) | (par->bgr << MEM_BGR)); + break; + case 270: + write_reg(par, 0x36, + (1<bgr << MEM_BGR)); + break; + case 180: + write_reg(par, 0x36, (1 << MEM_Y) | (par->bgr << MEM_BGR)); + break; + case 90: + write_reg(par, 0x36, (1 << MEM_Y) | (1 << MEM_X) | + (1 << MEM_V) | (par->bgr << MEM_BGR)); + break; + } + + return 0; +} + +/* + Gamma string format: + Positive: Par1 Par2 [...] Par15 + Negative: Par1 Par2 [...] Par15 +*/ +#define CURVE(num, idx) curves[num*par->gamma.num_values + idx] +static int set_gamma(struct fbtft_par *par, unsigned long *curves) +{ + int i; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + for (i = 0; i < par->gamma.num_curves; i++) + write_reg(par, 0xE0 + i, + CURVE(i, 0), CURVE(i, 1), CURVE(i, 2), + CURVE(i, 3), CURVE(i, 4), CURVE(i, 5), + CURVE(i, 6), CURVE(i, 7), CURVE(i, 8), + CURVE(i, 9), CURVE(i, 10), CURVE(i, 11), + CURVE(i, 12), CURVE(i, 13), CURVE(i, 14)); + + return 0; +} +#undef CURVE + + +static struct fbtft_display display = { + .regwidth = 8, + .width = WIDTH, + .height = HEIGHT, + .txbuflen = TXBUFLEN, + .gamma_num = 2, + .gamma_len = 15, + .gamma = DEFAULT_GAMMA, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_var = set_var, + .set_gamma = set_gamma, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "ilitek,ili9341", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:ili9341"); +MODULE_ALIAS("platform:ili9341"); + +MODULE_DESCRIPTION("FB driver for the ILI9341 LCD display controller"); +MODULE_AUTHOR("Christian Vogelgsang"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_ili9481.c b/drivers/video/fbtft/fb_ili9481.c new file mode 100644 index 0000000..725157a --- /dev/null +++ b/drivers/video/fbtft/fb_ili9481.c @@ -0,0 +1,117 @@ +/* + * FB driver for the ILI9481 LCD Controller + * + * Copyright (c) 2014 Petr Olivka + * Copyright (c) 2013 Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_ili9481" +#define WIDTH 320 +#define HEIGHT 480 + +static int default_init_sequence[] = { + + /* SLP_OUT - Sleep out */ + -1, 0x11, + -2, 50, + /* Power setting */ + -1, 0xD0, 0x07, 0x42, 0x18, + /* VCOM */ + -1, 0xD1, 0x00, 0x07, 0x10, + /* Power setting for norm. mode */ + -1, 0xD2, 0x01, 0x02, + /* Panel driving setting */ + -1, 0xC0, 0x10, 0x3B, 0x00, 0x02, 0x11, + /* Frame rate & inv. */ + -1, 0xC5, 0x03, + /* Pixel format */ + -1, 0x3A, 0x55, + /* Gamma */ + -1, 0xC8, 0x00, 0x32, 0x36, 0x45, 0x06, 0x16, + 0x37, 0x75, 0x77, 0x54, 0x0C, 0x00, + /* DISP_ON */ + -1, 0x29, + -3 +}; + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* column address */ + write_reg(par, 0x2a, xs >> 8, xs & 0xff, xe >> 8, xe & 0xff); + + /* row adress */ + write_reg(par, 0x2b, ys >> 8, ys & 0xff, ye >> 8, ye & 0xff); + + /* memory write */ + write_reg(par, 0x2c); +} + +#define HFLIP 0x01 +#define VFLIP 0x02 +#define ROWxCOL 0x20 +static int set_var(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + switch (par->info->var.rotate) { + case 270: + write_reg(par, 0x36, ROWxCOL | HFLIP | VFLIP | (par->bgr << 3)); + break; + case 180: + write_reg(par, 0x36, VFLIP | (par->bgr << 3)); + break; + case 90: + write_reg(par, 0x36, ROWxCOL | (par->bgr << 3)); + break; + default: + write_reg(par, 0x36, HFLIP | (par->bgr << 3)); + break; + } + + return 0; +} + +static struct fbtft_display display = { + .regwidth = 8, + .width = WIDTH, + .height = HEIGHT, + .init_sequence = default_init_sequence, + .fbtftops = { + .set_addr_win = set_addr_win, + .set_var = set_var, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "ilitek,ili9481", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:ili9481"); +MODULE_ALIAS("platform:ili9481"); + +MODULE_DESCRIPTION("FB driver for the ILI9481 LCD Controller"); +MODULE_AUTHOR("Petr Olivka"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_ili9486.c b/drivers/video/fbtft/fb_ili9486.c new file mode 100644 index 0000000..95b8999 --- /dev/null +++ b/drivers/video/fbtft/fb_ili9486.c @@ -0,0 +1,121 @@ +/* + * FB driver for the ILI9486 LCD Controller + * + * Copyright (C) 2014 Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_ili9486" +#define WIDTH 320 +#define HEIGHT 480 + + +/* this init sequence matches PiScreen */ +static int default_init_sequence[] = { + /* Interface Mode Control */ + -1, 0xb0, 0x0, + /* Sleep OUT */ + -1, 0x11, + -2, 250, + /* Interface Pixel Format */ + -1, 0x3A, 0x55, + /* Power Control 3 */ + -1, 0xC2, 0x44, + /* VCOM Control 1 */ + -1, 0xC5, 0x00, 0x00, 0x00, 0x00, + /* PGAMCTRL(Positive Gamma Control) */ + -1, 0xE0, 0x0F, 0x1F, 0x1C, 0x0C, 0x0F, 0x08, 0x48, 0x98, + 0x37, 0x0A, 0x13, 0x04, 0x11, 0x0D, 0x00, + /* NGAMCTRL(Negative Gamma Control) */ + -1, 0xE1, 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75, + 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00, + /* Digital Gamma Control 1 */ + -1, 0xE2, 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75, + 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00, + /* Sleep OUT */ + -1, 0x11, + /* Display ON */ + -1, 0x29, + /* end marker */ + -3 +}; + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* Column address */ + write_reg(par, 0x2A, xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF); + + /* Row adress */ + write_reg(par, 0x2B, ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF); + + /* Memory write */ + write_reg(par, 0x2C); +} + +static int set_var(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + switch (par->info->var.rotate) { + case 0: + write_reg(par, 0x36, 0x80 | (par->bgr << 3)); + break; + case 90: + write_reg(par, 0x36, 0x20 | (par->bgr << 3)); + break; + case 180: + write_reg(par, 0x36, 0x40 | (par->bgr << 3)); + break; + case 270: + write_reg(par, 0x36, 0xE0 | (par->bgr << 3)); + break; + default: + break; + } + + return 0; +} + + +static struct fbtft_display display = { + .regwidth = 8, + .width = WIDTH, + .height = HEIGHT, + .init_sequence = default_init_sequence, + .fbtftops = { + .set_addr_win = set_addr_win, + .set_var = set_var, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "ilitek,ili9486", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:ili9486"); +MODULE_ALIAS("platform:ili9486"); + +MODULE_DESCRIPTION("FB driver for the ILI9486 LCD Controller"); +MODULE_AUTHOR("Noralf Tronnes"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_pcd8544.c b/drivers/video/fbtft/fb_pcd8544.c new file mode 100644 index 0000000..678ab8e --- /dev/null +++ b/drivers/video/fbtft/fb_pcd8544.c @@ -0,0 +1,177 @@ +/* + * FB driver for the PCD8544 LCD Controller + * + * The display is monochrome and the video memory is RGB565. + * Any pixel value except 0 turns the pixel on. + * + * Copyright (C) 2013 Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_pcd8544" +#define WIDTH 84 +#define HEIGHT 48 +#define TXBUFLEN 84*6 +#define DEFAULT_GAMMA "40" /* gamma is used to control contrast in this driver */ + +static unsigned tc = 0; +module_param(tc, uint, 0); +MODULE_PARM_DESC(tc, "TC[1:0] Temperature coefficient: 0-3 (default: 0)"); + +static unsigned bs = 4; +module_param(bs, uint, 0); +MODULE_PARM_DESC(bs, "BS[2:0] Bias voltage level: 0-7 (default: 4)"); + + +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + /* Function set */ + write_reg(par, 0x21); /* 5:1 1 + 2:0 PD - Powerdown control: chip is active + 1:0 V - Entry mode: horizontal addressing + 0:1 H - Extended instruction set control: extended + */ + + /* H=1 Temperature control */ + write_reg(par, 0x04 | (tc & 0x3)); /* + 2:1 1 + 1:x TC1 - Temperature Coefficient: 0x10 + 0:x TC0 + */ + + /* H=1 Bias system */ + write_reg(par, 0x10 | (bs & 0x7)); /* + 4:1 1 + 3:0 0 + 2:x BS2 - Bias System + 1:x BS1 + 0:x BS0 + */ + + /* Function set */ + write_reg(par, 0x22); /* 5:1 1 + 2:0 PD - Powerdown control: chip is active + 1:1 V - Entry mode: vertical addressing + 0:0 H - Extended instruction set control: basic + */ + + /* H=0 Display control */ + write_reg(par, 0x08 | 4); /* + 3:1 1 + 2:1 D - DE: 10=normal mode + 1:0 0 + 0:0 E + */ + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* H=0 Set X address of RAM */ + write_reg(par, 0x80); /* 7:1 1 + 6-0: X[6:0] - 0x00 + */ + + /* H=0 Set Y address of RAM */ + write_reg(par, 0x40); /* 7:0 0 + 6:1 1 + 2-0: Y[2:0] - 0x0 + */ +} + +static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) +{ + u16 *vmem16 = (u16 *)par->info->screen_base; + u8 *buf = par->txbuf.buf; + int x, y, i; + int ret = 0; + + fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s()\n", __func__); + + for (x=0;x<84;x++) { + for (y=0;y<6;y++) { + *buf = 0x00; + for (i=0;i<8;i++) { + *buf |= (vmem16[(y*8+i)*84+x] ? 1 : 0) << i; + } + buf++; + } + } + + /* Write data */ + gpio_set_value(par->gpio.dc, 1); + ret = par->fbtftops.write(par, par->txbuf.buf, 6*84); + if (ret < 0) + dev_err(par->info->device, "%s: write failed and returned: %d\n", __func__, ret); + + return ret; +} + +static int set_gamma(struct fbtft_par *par, unsigned long *curves) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* apply mask */ + curves[0] &= 0x7F; + + write_reg(par, 0x23); /* turn on extended instruction set */ + write_reg(par, 0x80 | curves[0]); + write_reg(par, 0x22); /* turn off extended instruction set */ + + return 0; +} + + +static struct fbtft_display display = { + .regwidth = 8, + .width = WIDTH, + .height = HEIGHT, + .txbuflen = TXBUFLEN, + .gamma_num = 1, + .gamma_len = 1, + .gamma = DEFAULT_GAMMA, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .write_vmem = write_vmem, + .set_gamma = set_gamma, + }, + .backlight = 1, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "philips,pdc8544", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("spi:pdc8544"); + +MODULE_DESCRIPTION("FB driver for the PCD8544 LCD Controller"); +MODULE_AUTHOR("Noralf Tronnes"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_ra8875.c b/drivers/video/fbtft/fb_ra8875.c new file mode 100644 index 0000000..c323c06 --- /dev/null +++ b/drivers/video/fbtft/fb_ra8875.c @@ -0,0 +1,331 @@ +/****************************************************************************** + + ProjectName: FBTFT driver ***** ***** + for the RA8875 LCD Controller * * ************ + * ** ** * * + Copyright © by Pf@nne & NOTRO * * * * * **** * + * * * * * * * + Last modification by: * * * * **** * + - Pf@nne (pf@nne-mail.de) * * ***** * + * * * ******* + ***** * * + Date : 10.06.2014 * * + Version : V1.13 ***** + Revison : 5 + +******************************************************************************* + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include +#include "fbtft.h" + +#define DRVNAME "fb_ra8875" + +static int write_spi(struct fbtft_par *par, void *buf, size_t len) +{ + struct spi_transfer t = { + .tx_buf = buf, + .len = len, + .speed_hz = 1000000, + }; + struct spi_message m; + + fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, + "%s(len=%d): ", __func__, len); + + if (!par->spi) { + dev_err(par->info->device, + "%s: par->spi is unexpectedly NULL\n", __func__); + return -1; + } + + spi_message_init(&m); + if (par->txbuf.dma && buf == par->txbuf.buf) { + t.tx_dma = par->txbuf.dma; + m.is_dma_mapped = 1; + } + spi_message_add_tail(&t, &m); + return spi_sync(par->spi, &m); +} + +static int init_display(struct fbtft_par *par) +{ + gpio_set_value(par->gpio.dc, 1); + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, + "%s()\n", __func__); + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, + "display size %dx%d\n", par->info->var.xres, par->info->var.yres); + + par->fbtftops.reset(par); + + if ((par->info->var.xres == 320) && (par->info->var.yres == 240)) { + /* PLL clock frequency */ + write_reg(par, 0x88 , 0x0A); + write_reg(par, 0x89 , 0x02); + mdelay(10); + /* color deep / MCU Interface */ + write_reg(par, 0x10 , 0x0C); + /* pixel clock period */ + write_reg(par, 0x04 , 0x03); + mdelay(1); + /* horizontal settings */ + write_reg(par, 0x14 , 0x27); + write_reg(par, 0x15 , 0x00); + write_reg(par, 0x16 , 0x05); + write_reg(par, 0x17 , 0x04); + write_reg(par, 0x18 , 0x03); + /* vertical settings */ + write_reg(par, 0x19 , 0xEF); + write_reg(par, 0x1A , 0x00); + write_reg(par, 0x1B , 0x05); + write_reg(par, 0x1C , 0x00); + write_reg(par, 0x1D , 0x0E); + write_reg(par, 0x1E , 0x00); + write_reg(par, 0x1F , 0x02); + } else if ((par->info->var.xres == 480) && (par->info->var.yres == 272)) { + /* PLL clock frequency */ + write_reg(par, 0x88 , 0x0A); + write_reg(par, 0x89 , 0x02); + mdelay(10); + /* color deep / MCU Interface */ + write_reg(par, 0x10 , 0x0C); + /* pixel clock period */ + write_reg(par, 0x04 , 0x82); + mdelay(1); + /* horizontal settings */ + write_reg(par, 0x14 , 0x3B); + write_reg(par, 0x15 , 0x00); + write_reg(par, 0x16 , 0x01); + write_reg(par, 0x17 , 0x00); + write_reg(par, 0x18 , 0x05); + /* vertical settings */ + write_reg(par, 0x19 , 0x0F); + write_reg(par, 0x1A , 0x01); + write_reg(par, 0x1B , 0x02); + write_reg(par, 0x1C , 0x00); + write_reg(par, 0x1D , 0x07); + write_reg(par, 0x1E , 0x00); + write_reg(par, 0x1F , 0x09); + } else if ((par->info->var.xres == 640) && (par->info->var.yres == 480)) { + /* PLL clock frequency */ + write_reg(par, 0x88 , 0x0B); + write_reg(par, 0x89 , 0x02); + mdelay(10); + /* color deep / MCU Interface */ + write_reg(par, 0x10 , 0x0C); + /* pixel clock period */ + write_reg(par, 0x04 , 0x01); + mdelay(1); + /* horizontal settings */ + write_reg(par, 0x14 , 0x4F); + write_reg(par, 0x15 , 0x05); + write_reg(par, 0x16 , 0x0F); + write_reg(par, 0x17 , 0x01); + write_reg(par, 0x18 , 0x00); + /* vertical settings */ + write_reg(par, 0x19 , 0xDF); + write_reg(par, 0x1A , 0x01); + write_reg(par, 0x1B , 0x0A); + write_reg(par, 0x1C , 0x00); + write_reg(par, 0x1D , 0x0E); + write_reg(par, 0x1E , 0x00); + write_reg(par, 0x1F , 0x01); + } else if ((par->info->var.xres == 800) && (par->info->var.yres == 480)) { + /* PLL clock frequency */ + write_reg(par, 0x88 , 0x0B); + write_reg(par, 0x89 , 0x02); + mdelay(10); + /* color deep / MCU Interface */ + write_reg(par, 0x10 , 0x0C); + /* pixel clock period */ + write_reg(par, 0x04 , 0x81); + mdelay(1); + /* horizontal settings */ + write_reg(par, 0x14 , 0x63); + write_reg(par, 0x15 , 0x03); + write_reg(par, 0x16 , 0x03); + write_reg(par, 0x17 , 0x02); + write_reg(par, 0x18 , 0x00); + /* vertical settings */ + write_reg(par, 0x19 , 0xDF); + write_reg(par, 0x1A , 0x01); + write_reg(par, 0x1B , 0x14); + write_reg(par, 0x1C , 0x00); + write_reg(par, 0x1D , 0x06); + write_reg(par, 0x1E , 0x00); + write_reg(par, 0x1F , 0x01); + } else { + dev_err(par->info->device, "display size is not supported!!"); + return -1; + } + + /* PWM clock */ + write_reg(par, 0x8a , 0x81); + write_reg(par, 0x8b , 0xFF); + mdelay(10); + + /* Display ON */ + write_reg(par, 0x01 , 0x80); + mdelay(10); + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* Set_Active_Window */ + write_reg(par, 0x30 , xs & 0x00FF); + write_reg(par, 0x31 , (xs & 0xFF00) >> 8); + write_reg(par, 0x32 , ys & 0x00FF); + write_reg(par, 0x33 , (ys & 0xFF00) >> 8); + write_reg(par, 0x34 , (xs+xe) & 0x00FF); + write_reg(par, 0x35 , ((xs+xe) & 0xFF00) >> 8); + write_reg(par, 0x36 , (ys+ye) & 0x00FF); + write_reg(par, 0x37 , ((ys+ye) & 0xFF00) >> 8); + + /* Set_Memory_Write_Cursor */ + write_reg(par, 0x46, xs & 0xff); + write_reg(par, 0x47, (xs >> 8) & 0x03); + write_reg(par, 0x48, ys & 0xff); + write_reg(par, 0x49, (ys >> 8) & 0x01); + + write_reg(par, 0x02); +} + +static void write_reg8_bus8(struct fbtft_par *par, int len, ...) +{ + va_list args; + int i, ret; + u8 *buf = (u8 *)par->buf; + + /* slow down spi-speed for writing registers */ + par->fbtftops.write = write_spi; + + if (unlikely(par->debug & DEBUG_WRITE_REGISTER)) { + va_start(args, len); + for (i = 0; i < len; i++) + buf[i] = (u8)va_arg(args, unsigned int); + va_end(args); + fbtft_par_dbg_hex(DEBUG_WRITE_REGISTER, par, par->info->device, + u8, buf, len, "%s: ", __func__); + } + + va_start(args, len); + *buf++ = 0x80; + *buf = (u8)va_arg(args, unsigned int); + ret = par->fbtftops.write(par, par->buf, 2); + if (ret < 0) { + va_end(args); + dev_err(par->info->device, "%s: write() failed and returned %dn", + __func__, ret); + return; + } + len--; + + udelay(100); + + if (len) { + buf = (u8 *)par->buf; + *buf++ = 0x00; + i = len; + while (i--) + *buf++ = (u8)va_arg(args, unsigned int); + + ret = par->fbtftops.write(par, par->buf, len + 1); + if (ret < 0) { + va_end(args); + dev_err(par->info->device, "%s: write() failed and returned %dn", + __func__, ret); + return; + } + } + va_end(args); + + /* restore user spi-speed */ + par->fbtftops.write = fbtft_write_spi; + udelay(100); +} + +static int write_vmem16_bus8(struct fbtft_par *par, size_t offset, size_t len) +{ + u16 *vmem16; + u16 *txbuf16 = (u16 *)par->txbuf.buf; + size_t remain; + size_t to_copy; + size_t tx_array_size; + int i; + int ret = 0; + size_t startbyte_size = 0; + + fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s(offset=%zu, len=%zu)\n", + __func__, offset, len); + + remain = len / 2; + vmem16 = (u16 *)(par->info->screen_base + offset); + tx_array_size = par->txbuf.len / 2; + txbuf16 = (u16 *)(par->txbuf.buf + 1); + tx_array_size -= 2; + *(u8 *)(par->txbuf.buf) = 0x00; + startbyte_size = 1; + + while (remain) { + to_copy = remain > tx_array_size ? tx_array_size : remain; + dev_dbg(par->info->device, " to_copy=%zu, remain=%zu\n", + to_copy, remain - to_copy); + + for (i = 0; i < to_copy; i++) + txbuf16[i] = cpu_to_be16(vmem16[i]); + + vmem16 = vmem16 + to_copy; + ret = par->fbtftops.write(par, par->txbuf.buf, + startbyte_size + to_copy * 2); + if (ret < 0) + return ret; + remain -= to_copy; + } + + return ret; +} + +static struct fbtft_display display = { + .regwidth = 8, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .write_register = write_reg8_bus8, + .write_vmem = write_vmem16_bus8, + .write = write_spi, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "raio,ra8875", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:ra8875"); +MODULE_ALIAS("platform:ra8875"); + +MODULE_DESCRIPTION("FB driver for the RA8875 LCD Controller"); +MODULE_AUTHOR("Pf@nne"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_s6d02a1.c b/drivers/video/fbtft/fb_s6d02a1.c new file mode 100644 index 0000000..e412a42 --- /dev/null +++ b/drivers/video/fbtft/fb_s6d02a1.c @@ -0,0 +1,168 @@ +/* + * FB driver for the S6D02A1 LCD Controller + * + * Based on fb_st7735r.c by Noralf Tronnes + * Init code from UTFT library by Henning Karlsen + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_s6d02a1" + +static int default_init_sequence[] = { + + -1, 0xf0, 0x5a, 0x5a, + + -1, 0xfc, 0x5a, 0x5a, + + -1, 0xfa, 0x02, 0x1f, 0x00, 0x10, 0x22, 0x30, 0x38, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3d, 0x02, 0x01, + + -1, 0xfb, 0x21, 0x00, 0x02, 0x04, 0x07, 0x0a, 0x0b, 0x0c, 0x0c, 0x16, 0x1e, 0x30, 0x3f, 0x01, 0x02, + + /* power setting sequence */ + -1, 0xfd, 0x00, 0x00, 0x00, 0x17, 0x10, 0x00, 0x01, 0x01, 0x00, 0x1f, 0x1f, + + -1, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x3f, 0x07, 0x00, 0x3C, 0x36, 0x00, 0x3C, 0x36, 0x00, + + -1, 0xf5, 0x00, 0x70, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x66, 0x06, + + -1, 0xf6, 0x02, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x02, 0x00, 0x06, 0x01, 0x00, + + -1, 0xf2, 0x00, 0x01, 0x03, 0x08, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x08, 0x08, + + -1, 0xf8, 0x11, + + -1, 0xf7, 0xc8, 0x20, 0x00, 0x00, + + -1, 0xf3, 0x00, 0x00, + + -1, 0x11, + -2, 50, + + -1, 0xf3, 0x00, 0x01, + -2, 50, + -1, 0xf3, 0x00, 0x03, + -2, 50, + -1, 0xf3, 0x00, 0x07, + -2, 50, + -1, 0xf3, 0x00, 0x0f, + -2, 50, + + -1, 0xf4, 0x00, 0x04, 0x00, 0x00, 0x00, 0x3f, 0x3f, 0x07, 0x00, 0x3C, 0x36, 0x00, 0x3C, 0x36, 0x00, + -2, 50, + + -1, 0xf3, 0x00, 0x1f, + -2, 50, + -1, 0xf3, 0x00, 0x7f, + -2, 50, + + -1, 0xf3, 0x00, 0xff, + -2, 50, + + -1, 0xfd, 0x00, 0x00, 0x00, 0x17, 0x10, 0x00, 0x00, 0x01, 0x00, 0x16, 0x16, + + -1, 0xf4, 0x00, 0x09, 0x00, 0x00, 0x00, 0x3f, 0x3f, 0x07, 0x00, 0x3C, 0x36, 0x00, 0x3C, 0x36, 0x00, + + /* initializing sequence */ + + -1, 0x36, 0x08, + + -1, 0x35, 0x00, + + -1, 0x3a, 0x05, + + /* gamma setting sequence */ + -1, 0x26, 0x01, /* preset gamma curves, possible values 0x01, 0x02, 0x04, 0x08 */ + + -2, 150, + -1, 0x29, + -1, 0x2c, + /* end marker */ + -3 + +}; + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* Column address */ + write_reg(par, 0x2A, xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF); + + /* Row adress */ + write_reg(par, 0x2B, ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF); + + /* Memory write */ + write_reg(par, 0x2C); +} + +#define MY (1 << 7) +#define MX (1 << 6) +#define MV (1 << 5) +static int set_var(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* MADCTL - Memory data access control + RGB/BGR: + 1. Mode selection pin SRGB + RGB H/W pin for color filter setting: 0=RGB, 1=BGR + 2. MADCTL RGB bit + RGB-BGR ORDER color filter panel: 0=RGB, 1=BGR */ + switch (par->info->var.rotate) { + case 0: + write_reg(par, 0x36, MX | MY | (par->bgr << 3)); + break; + case 270: + write_reg(par, 0x36, MY | MV | (par->bgr << 3)); + break; + case 180: + write_reg(par, 0x36, (par->bgr << 3)); + break; + case 90: + write_reg(par, 0x36, MX | MV | (par->bgr << 3)); + break; + } + + return 0; +} + +static struct fbtft_display display = { + .regwidth = 8, + .width = 128, + .height = 160, + .init_sequence = default_init_sequence, + .fbtftops = { + .set_addr_win = set_addr_win, + .set_var = set_var, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "samsung,s6d02a1", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:s6d02a1"); +MODULE_ALIAS("platform:s6d02a1"); + +MODULE_DESCRIPTION("FB driver for the S6D02A1 LCD Controller"); +MODULE_AUTHOR("WOLFGANG BUENING"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_s6d1121.c b/drivers/video/fbtft/fb_s6d1121.c new file mode 100644 index 0000000..1ef8c1a --- /dev/null +++ b/drivers/video/fbtft/fb_s6d1121.c @@ -0,0 +1,208 @@ +/* + * FB driver for the S6D1121 LCD Controller + * + * Copyright (C) 2013 Roman Rolinsky + * + * Based on fb_ili9325.c by Noralf Tronnes + * Based on ili9325.c by Jeroen Domburg + * Init code from UTFT library by Henning Karlsen + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_s6d1121" +#define WIDTH 240 +#define HEIGHT 320 +#define BPP 16 +#define FPS 20 +#define DEFAULT_GAMMA "26 09 24 2C 1F 23 24 25 22 26 25 23 0D 00\n" \ + "1C 1A 13 1D 0B 11 12 10 13 15 36 19 00 0D" + +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + if (par->gpio.cs != -1) + gpio_set_value(par->gpio.cs, 0); /* Activate chip */ + + /* Initialization sequence from Lib_UTFT */ + + write_reg(par, 0x0011, 0x2004); + write_reg(par, 0x0013, 0xCC00); + write_reg(par, 0x0015, 0x2600); + write_reg(par, 0x0014, 0x252A); + write_reg(par, 0x0012, 0x0033); + write_reg(par, 0x0013, 0xCC04); + write_reg(par, 0x0013, 0xCC06); + write_reg(par, 0x0013, 0xCC4F); + write_reg(par, 0x0013, 0x674F); + write_reg(par, 0x0011, 0x2003); + write_reg(par, 0x0016, 0x0007); + write_reg(par, 0x0002, 0x0013); + write_reg(par, 0x0003, 0x0003); + write_reg(par, 0x0001, 0x0127); + write_reg(par, 0x0008, 0x0303); + write_reg(par, 0x000A, 0x000B); + write_reg(par, 0x000B, 0x0003); + write_reg(par, 0x000C, 0x0000); + write_reg(par, 0x0041, 0x0000); + write_reg(par, 0x0050, 0x0000); + write_reg(par, 0x0060, 0x0005); + write_reg(par, 0x0070, 0x000B); + write_reg(par, 0x0071, 0x0000); + write_reg(par, 0x0078, 0x0000); + write_reg(par, 0x007A, 0x0000); + write_reg(par, 0x0079, 0x0007); + write_reg(par, 0x0007, 0x0051); + write_reg(par, 0x0007, 0x0053); + write_reg(par, 0x0079, 0x0000); + + write_reg(par, 0x0022); /* Write Data to GRAM */ + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + switch (par->info->var.rotate) { + /* R20h = Horizontal GRAM Start Address */ + /* R21h = Vertical GRAM Start Address */ + case 0: + write_reg(par, 0x0020, xs); + write_reg(par, 0x0021, ys); + break; + case 180: + write_reg(par, 0x0020, WIDTH - 1 - xs); + write_reg(par, 0x0021, HEIGHT - 1 - ys); + break; + case 270: + write_reg(par, 0x0020, WIDTH - 1 - ys); + write_reg(par, 0x0021, xs); + break; + case 90: + write_reg(par, 0x0020, ys); + write_reg(par, 0x0021, HEIGHT - 1 - xs); + break; + } + write_reg(par, 0x0022); /* Write Data to GRAM */ +} + +static int set_var(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + switch (par->info->var.rotate) { + /* AM: GRAM update direction */ + case 0: + write_reg(par, 0x03, 0x0003 | (par->bgr << 12)); + break; + case 180: + write_reg(par, 0x03, 0x0000 | (par->bgr << 12)); + break; + case 270: + write_reg(par, 0x03, 0x000A | (par->bgr << 12)); + break; + case 90: + write_reg(par, 0x03, 0x0009 | (par->bgr << 12)); + break; + } + + return 0; +} + +/* + Gamma string format: + PKP0 PKP1 PKP2 PKP3 PKP4 PKP5 PKP6 PKP7 PKP8 PKP9 PKP10 PKP11 VRP0 VRP1 + PKN0 PKN1 PKN2 PKN3 PKN4 PKN5 PKN6 PKN7 PRN8 PRN9 PRN10 PRN11 VRN0 VRN1 +*/ +#define CURVE(num, idx) curves[num*par->gamma.num_values + idx] +static int set_gamma(struct fbtft_par *par, unsigned long *curves) +{ + unsigned long mask[] = { + 0b111111, 0b111111, 0b111111, 0b111111, 0b111111, 0b111111, + 0b111111, 0b111111, 0b111111, 0b111111, 0b111111, 0b111111, + 0b11111, 0b11111, + 0b111111, 0b111111, 0b111111, 0b111111, 0b111111, 0b111111, + 0b111111, 0b111111, 0b111111, 0b111111, 0b111111, 0b111111, + 0b11111, 0b11111 }; + int i, j; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* apply mask */ + for (i = 0; i < 2; i++) + for (j = 0; j < 14; j++) + CURVE(i, j) &= mask[i*par->gamma.num_values + j]; + + write_reg(par, 0x0030, CURVE(0, 1) << 8 | CURVE(0, 0)); + write_reg(par, 0x0031, CURVE(0, 3) << 8 | CURVE(0, 2)); + write_reg(par, 0x0032, CURVE(0, 5) << 8 | CURVE(0, 3)); + write_reg(par, 0x0033, CURVE(0, 7) << 8 | CURVE(0, 6)); + write_reg(par, 0x0034, CURVE(0, 9) << 8 | CURVE(0, 8)); + write_reg(par, 0x0035, CURVE(0, 11) << 8 | CURVE(0, 10)); + + write_reg(par, 0x0036, CURVE(1, 1) << 8 | CURVE(1, 0)); + write_reg(par, 0x0037, CURVE(1, 3) << 8 | CURVE(1, 2)); + write_reg(par, 0x0038, CURVE(1, 5) << 8 | CURVE(1, 4)); + write_reg(par, 0x0039, CURVE(1, 7) << 8 | CURVE(1, 6)); + write_reg(par, 0x003A, CURVE(1, 9) << 8 | CURVE(1, 8)); + write_reg(par, 0x003B, CURVE(1, 11) << 8 | CURVE(1, 10)); + + write_reg(par, 0x003C, CURVE(0, 13) << 8 | CURVE(0, 12)); + write_reg(par, 0x003D, CURVE(1, 13) << 8 | CURVE(1, 12)); + + return 0; +} +#undef CURVE + + +static struct fbtft_display display = { + .regwidth = 16, + .width = WIDTH, + .height = HEIGHT, + .bpp = BPP, + .fps = FPS, + .gamma_num = 2, + .gamma_len = 14, + .gamma = DEFAULT_GAMMA, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_var = set_var, + .set_gamma = set_gamma, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "samsung,s6d1121", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:s6d1121"); +MODULE_ALIAS("platform:s6d1121"); + +MODULE_DESCRIPTION("FB driver for the S6D1121 LCD Controller"); +MODULE_AUTHOR("Roman Rolinsky"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_ssd1289.c b/drivers/video/fbtft/fb_ssd1289.c new file mode 100644 index 0000000..ef46fbc --- /dev/null +++ b/drivers/video/fbtft/fb_ssd1289.c @@ -0,0 +1,206 @@ +/* + * FB driver for the SSD1289 LCD Controller + * + * Copyright (C) 2013 Noralf Tronnes + * + * Init sequence taken from ITDB02_Graph16.cpp - (C)2010-2011 Henning Karlsen + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_ssd1289" +#define WIDTH 240 +#define HEIGHT 320 +#define DEFAULT_GAMMA "02 03 2 5 7 7 4 2 4 2\n" \ + "02 03 2 5 7 5 4 2 4 2" + +static unsigned reg11 = 0x6040; +module_param(reg11, uint, 0); +MODULE_PARM_DESC(reg11, "Register 11h value"); + + +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + if (par->gpio.cs != -1) + gpio_set_value(par->gpio.cs, 0); /* Activate chip */ + + write_reg(par, 0x00, 0x0001); + write_reg(par, 0x03, 0xA8A4); + write_reg(par, 0x0C, 0x0000); + write_reg(par, 0x0D, 0x080C); + write_reg(par, 0x0E, 0x2B00); + write_reg(par, 0x1E, 0x00B7); + write_reg(par, 0x01, + (1 << 13) | (par->bgr << 11) | (1 << 9) | (HEIGHT - 1)); + write_reg(par, 0x02, 0x0600); + write_reg(par, 0x10, 0x0000); + write_reg(par, 0x05, 0x0000); + write_reg(par, 0x06, 0x0000); + write_reg(par, 0x16, 0xEF1C); + write_reg(par, 0x17, 0x0003); + write_reg(par, 0x07, 0x0233); + write_reg(par, 0x0B, 0x0000); + write_reg(par, 0x0F, 0x0000); + write_reg(par, 0x41, 0x0000); + write_reg(par, 0x42, 0x0000); + write_reg(par, 0x48, 0x0000); + write_reg(par, 0x49, 0x013F); + write_reg(par, 0x4A, 0x0000); + write_reg(par, 0x4B, 0x0000); + write_reg(par, 0x44, 0xEF00); + write_reg(par, 0x45, 0x0000); + write_reg(par, 0x46, 0x013F); + write_reg(par, 0x23, 0x0000); + write_reg(par, 0x24, 0x0000); + write_reg(par, 0x25, 0x8000); + write_reg(par, 0x4f, 0x0000); + write_reg(par, 0x4e, 0x0000); + write_reg(par, 0x22); + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + switch (par->info->var.rotate) { + /* R4Eh - Set GDDRAM X address counter */ + /* R4Fh - Set GDDRAM Y address counter */ + case 0: + write_reg(par, 0x4e, xs); + write_reg(par, 0x4f, ys); + break; + case 180: + write_reg(par, 0x4e, par->info->var.xres - 1 - xs); + write_reg(par, 0x4f, par->info->var.yres - 1 - ys); + break; + case 270: + write_reg(par, 0x4e, par->info->var.yres - 1 - ys); + write_reg(par, 0x4f, xs); + break; + case 90: + write_reg(par, 0x4e, ys); + write_reg(par, 0x4f, par->info->var.xres - 1 - xs); + break; + } + + /* R22h - RAM data write */ + write_reg(par, 0x22); +} + +static int set_var(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + if (par->fbtftops.init_display != init_display) { + /* don't risk messing up register 11h */ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, + "%s: skipping since custom init_display() is used\n", + __func__); + return 0; + } + + switch (par->info->var.rotate) { + case 0: + write_reg(par, 0x11, reg11 | 0b110000); + break; + case 270: + write_reg(par, 0x11, reg11 | 0b101000); + break; + case 180: + write_reg(par, 0x11, reg11 | 0b000000); + break; + case 90: + write_reg(par, 0x11, reg11 | 0b011000); + break; + } + + return 0; +} + +/* + Gamma string format: + VRP0 VRP1 PRP0 PRP1 PKP0 PKP1 PKP2 PKP3 PKP4 PKP5 + VRN0 VRN1 PRN0 PRN1 PKN0 PKN1 PKN2 PKN3 PKN4 PKN5 +*/ +#define CURVE(num, idx) curves[num*par->gamma.num_values + idx] +static int set_gamma(struct fbtft_par *par, unsigned long *curves) +{ + unsigned long mask[] = { + 0b11111, 0b11111, 0b111, 0b111, 0b111, + 0b111, 0b111, 0b111, 0b111, 0b111, + 0b11111, 0b11111, 0b111, 0b111, 0b111, + 0b111, 0b111, 0b111, 0b111, 0b111 }; + int i, j; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* apply mask */ + for (i = 0; i < 2; i++) + for (j = 0; j < 10; j++) + CURVE(i, j) &= mask[i*par->gamma.num_values + j]; + + write_reg(par, 0x0030, CURVE(0, 5) << 8 | CURVE(0, 4)); + write_reg(par, 0x0031, CURVE(0, 7) << 8 | CURVE(0, 6)); + write_reg(par, 0x0032, CURVE(0, 9) << 8 | CURVE(0, 8)); + write_reg(par, 0x0033, CURVE(0, 3) << 8 | CURVE(0, 2)); + write_reg(par, 0x0034, CURVE(1, 5) << 8 | CURVE(1, 4)); + write_reg(par, 0x0035, CURVE(1, 7) << 8 | CURVE(1, 6)); + write_reg(par, 0x0036, CURVE(1, 9) << 8 | CURVE(1, 8)); + write_reg(par, 0x0037, CURVE(1, 3) << 8 | CURVE(1, 2)); + write_reg(par, 0x003A, CURVE(0, 1) << 8 | CURVE(0, 0)); + write_reg(par, 0x003B, CURVE(1, 1) << 8 | CURVE(1, 0)); + + return 0; +} +#undef CURVE + + +static struct fbtft_display display = { + .regwidth = 16, + .width = WIDTH, + .height = HEIGHT, + .gamma_num = 2, + .gamma_len = 10, + .gamma = DEFAULT_GAMMA, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_var = set_var, + .set_gamma = set_gamma, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1289", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:ssd1289"); +MODULE_ALIAS("platform:ssd1289"); + +MODULE_DESCRIPTION("FB driver for the SSD1289 LCD Controller"); +MODULE_AUTHOR("Noralf Tronnes"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_ssd1306.c b/drivers/video/fbtft/fb_ssd1306.c new file mode 100644 index 0000000..5ea195b --- /dev/null +++ b/drivers/video/fbtft/fb_ssd1306.c @@ -0,0 +1,229 @@ +/* + * FB driver for the SSD1306 OLED Controller + * + * Copyright (C) 2013 Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_ssd1306" +#define WIDTH 128 +#define HEIGHT 64 + + +/* + write_reg() caveat: + + This doesn't work because D/C has to be LOW for both values: + write_reg(par, val1, val2); + + Do it like this: + write_reg(par, val1); + write_reg(par, val2); +*/ + +/* Init sequence taken from the Adafruit SSD1306 Arduino library */ +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + if (par->gamma.curves[0] == 0) { + mutex_lock(&par->gamma.lock); + if (par->info->var.yres == 64) + par->gamma.curves[0] = 0xCF; + else + par->gamma.curves[0] = 0x8F; + mutex_unlock(&par->gamma.lock); + } + + /* Set Display OFF */ + write_reg(par, 0xAE); + + /* Set Display Clock Divide Ratio/ Oscillator Frequency */ + write_reg(par, 0xD5); + write_reg(par, 0x80); + + /* Set Multiplex Ratio */ + write_reg(par, 0xA8); + if (par->info->var.yres == 64) + write_reg(par, 0x3F); + else + write_reg(par, 0x1F); + + /* Set Display Offset */ + write_reg(par, 0xD3); + write_reg(par, 0x0); + + /* Set Display Start Line */ + write_reg(par, 0x40 | 0x0); + + /* Charge Pump Setting */ + write_reg(par, 0x8D); + /* A[2] = 1b, Enable charge pump during display on */ + write_reg(par, 0x14); + + /* Set Memory Addressing Mode */ + write_reg(par, 0x20); + /* Vertical addressing mode */ + write_reg(par, 0x01); + + /*Set Segment Re-map */ + /* column address 127 is mapped to SEG0 */ + write_reg(par, 0xA0 | 0x1); + + /* Set COM Output Scan Direction */ + /* remapped mode. Scan from COM[N-1] to COM0 */ + write_reg(par, 0xC8); + + /* Set COM Pins Hardware Configuration */ + write_reg(par, 0xDA); + if (par->info->var.yres == 64) + /* A[4]=1b, Alternative COM pin configuration */ + write_reg(par, 0x12); + else + /* A[4]=0b, Sequential COM pin configuration */ + write_reg(par, 0x02); + + /* Set Pre-charge Period */ + write_reg(par, 0xD9); + write_reg(par, 0xF1); + + /* Set VCOMH Deselect Level */ + write_reg(par, 0xDB); + /* according to the datasheet, this value is out of bounds */ + write_reg(par, 0x40); + + /* Entire Display ON */ + /* Resume to RAM content display. Output follows RAM content */ + write_reg(par, 0xA4); + + /* Set Normal Display + 0 in RAM: OFF in display panel + 1 in RAM: ON in display panel */ + write_reg(par, 0xA6); + + /* Set Display ON */ + write_reg(par, 0xAF); + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* Set Lower Column Start Address for Page Addressing Mode */ + write_reg(par, 0x00 | 0x0); + /* Set Higher Column Start Address for Page Addressing Mode */ + write_reg(par, 0x10 | 0x0); + /* Set Display Start Line */ + write_reg(par, 0x40 | 0x0); +} + +static int blank(struct fbtft_par *par, bool on) +{ + fbtft_par_dbg(DEBUG_BLANK, par, "%s(blank=%s)\n", + __func__, on ? "true" : "false"); + + if (on) + write_reg(par, 0xAE); + else + write_reg(par, 0xAF); + return 0; +} + +/* Gamma is used to control Contrast */ +static int set_gamma(struct fbtft_par *par, unsigned long *curves) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* apply mask */ + curves[0] &= 0xFF; + + /* Set Contrast Control for BANK0 */ + write_reg(par, 0x81); + write_reg(par, curves[0]); + + return 0; +} + +static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) +{ + u16 *vmem16 = (u16 *)par->info->screen_base; + u8 *buf = par->txbuf.buf; + int x, y, i; + int ret = 0; + + fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s()\n", __func__); + + for (x = 0; x < par->info->var.xres; x++) { + for (y = 0; y < par->info->var.yres/8; y++) { + *buf = 0x00; + for (i = 0; i < 8; i++) + *buf |= (vmem16[(y*8+i)*par->info->var.xres+x] ? 1 : 0) << i; + buf++; + } + } + + /* Write data */ + gpio_set_value(par->gpio.dc, 1); + ret = par->fbtftops.write(par, par->txbuf.buf, + par->info->var.xres*par->info->var.yres/8); + if (ret < 0) + dev_err(par->info->device, + "%s: write failed and returned: %d\n", __func__, ret); + + return ret; +} + + +static struct fbtft_display display = { + .regwidth = 8, + .width = WIDTH, + .height = HEIGHT, + .gamma_num = 1, + .gamma_len = 1, + .gamma = "00", + .fbtftops = { + .write_vmem = write_vmem, + .init_display = init_display, + .set_addr_win = set_addr_win, + .blank = blank, + .set_gamma = set_gamma, + }, +}; + + +FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1306", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:ssd1306"); +MODULE_ALIAS("platform:ssd1306"); + +MODULE_DESCRIPTION("SSD1306 OLED Driver"); +MODULE_AUTHOR("Noralf Tronnes"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_ssd1331.c b/drivers/video/fbtft/fb_ssd1331.c new file mode 100644 index 0000000..da7464f --- /dev/null +++ b/drivers/video/fbtft/fb_ssd1331.c @@ -0,0 +1,205 @@ +#include +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_ssd1331" +#define WIDTH 96 +#define HEIGHT 64 +#define GAMMA_NUM 1 +#define GAMMA_LEN 63 +#define DEFAULT_GAMMA "0 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2" \ + +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + write_reg(par, 0xae); /* Display Off */ + write_reg(par, 0xa0, 0x70 | (par->bgr << 2)); /* Set Colour Depth */ + write_reg(par, 0x72); // RGB colour + write_reg(par, 0xa1, 0x00); /* Set Display Start Line */ + write_reg(par, 0xa2, 0x00); /* Set Display Offset */ + write_reg(par, 0xa4); /* NORMALDISPLAY */ + write_reg(par, 0xa8, 0x3f); // Set multiplex + write_reg(par, 0xad, 0x8e); // Set master + // write_reg(par, 0xb0, 0x0b); // Set power mode + write_reg(par, 0xb1, 0x31); // Precharge + write_reg(par, 0xb3, 0xf0); // Clock div + write_reg(par, 0x8a, 0x64); // Precharge A + write_reg(par, 0x8b, 0x78); // Precharge B + write_reg(par, 0x8c, 0x64); // Precharge C + write_reg(par, 0xbb, 0x3a); // Precharge level + write_reg(par, 0xbe, 0x3e); // vcomh + write_reg(par, 0x87, 0x06); // Master current + write_reg(par, 0x81, 0x91); // Contrast A + write_reg(par, 0x82, 0x50); // Contrast B + write_reg(par, 0x83, 0x7d); // Contrast C + write_reg(par, 0xaf); /* Set Sleep Mode Display On */ + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + write_reg(par, 0x15, xs, xe); + write_reg(par, 0x75, ys, ye); +} + +static void write_reg8_bus8(struct fbtft_par *par, int len, ...) +{ + va_list args; + int i, ret; + u8 *buf = (u8 *)par->buf; + + if (unlikely(par->debug & DEBUG_WRITE_REGISTER)) { + va_start(args, len); + for (i = 0; i < len; i++) { + buf[i] = (u8)va_arg(args, unsigned int); + } + va_end(args); + fbtft_par_dbg_hex(DEBUG_WRITE_REGISTER, par, par->info->device, u8, buf, len, "%s: ", __func__); + } + + va_start(args, len); + + *buf = (u8)va_arg(args, unsigned int); + if (par->gpio.dc != -1) + gpio_set_value(par->gpio.dc, 0); + ret = par->fbtftops.write(par, par->buf, sizeof(u8)); + if (ret < 0) { + va_end(args); + dev_err(par->info->device, "%s: write() failed and returned %d\n", __func__, ret); + return; + } + len--; + + if (len) { + i = len; + while (i--) { + *buf++ = (u8)va_arg(args, unsigned int); + } + ret = par->fbtftops.write(par, par->buf, len * (sizeof(u8))); + if (ret < 0) { + va_end(args); + dev_err(par->info->device, "%s: write() failed and returned %d\n", __func__, ret); + return; + } + } + if (par->gpio.dc != -1) + gpio_set_value(par->gpio.dc, 1); + va_end(args); +} + +/* + Grayscale Lookup Table + GS1 - GS63 + The driver Gamma curve contains the relative values between the entries + in the Lookup table. + + From datasheet: + 8.8 Gray Scale Decoder + + there are total 180 Gamma Settings (Setting 0 to Setting 180) + available for the Gray Scale table. + + The gray scale is defined in incremental way, with reference + to the length of previous table entry: + Setting of GS1 has to be >= 0 + Setting of GS2 has to be > Setting of GS1 +1 + Setting of GS3 has to be > Setting of GS2 +1 + : + Setting of GS63 has to be > Setting of GS62 +1 + + +*/ +static int set_gamma(struct fbtft_par *par, unsigned long *curves) +{ + unsigned long tmp[GAMMA_NUM * GAMMA_LEN]; + int i, acc = 0; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + for (i = 0; i < 63; i++) { + if (i > 0 && curves[i] < 2) { + dev_err(par->info->device, + "Illegal value in Grayscale Lookup Table at index %d. " \ + "Must be greater than 1\n", i); + return -EINVAL; + } + acc += curves[i]; + tmp[i] = acc; + if (acc > 180) { + dev_err(par->info->device, + "Illegal value(s) in Grayscale Lookup Table. " \ + "At index=%d, the accumulated value has exceeded 180\n", i); + return -EINVAL; + } + } + + write_reg(par, 0xB8, + tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], + tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14], tmp[15], + tmp[16], tmp[17], tmp[18], tmp[19], tmp[20], tmp[21], tmp[22], tmp[23], + tmp[24], tmp[25], tmp[26], tmp[27], tmp[28], tmp[29], tmp[30], tmp[31], + tmp[32], tmp[33], tmp[34], tmp[35], tmp[36], tmp[37], tmp[38], tmp[39], + tmp[40], tmp[41], tmp[42], tmp[43], tmp[44], tmp[45], tmp[46], tmp[47], + tmp[48], tmp[49], tmp[50], tmp[51], tmp[52], tmp[53], tmp[54], tmp[55], + tmp[56], tmp[57], tmp[58], tmp[59], tmp[60], tmp[61], tmp[62]); + + return 0; +} + +static int blank(struct fbtft_par *par, bool on) +{ + fbtft_par_dbg(DEBUG_BLANK, par, "%s(blank=%s)\n", + __func__, on ? "true" : "false"); + if (on) + write_reg(par, 0xAE); + else + write_reg(par, 0xAF); + return 0; +} + + +static struct fbtft_display display = { + .regwidth = 8, + .width = WIDTH, + .height = HEIGHT, + .gamma_num = GAMMA_NUM, + .gamma_len = GAMMA_LEN, + .gamma = DEFAULT_GAMMA, + .fbtftops = { + .write_register = write_reg8_bus8, + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_gamma = set_gamma, + .blank = blank, + }, +}; + +FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1331", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:ssd1331"); +MODULE_ALIAS("platform:ssd1331"); + +MODULE_DESCRIPTION("SSD1331 OLED Driver"); +MODULE_AUTHOR("Alec Smecher (adapted from SSD1351 by James Davies)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_ssd1351.c b/drivers/video/fbtft/fb_ssd1351.c new file mode 100644 index 0000000..062d986 --- /dev/null +++ b/drivers/video/fbtft/fb_ssd1351.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_ssd1351" +#define WIDTH 128 +#define HEIGHT 128 +#define GAMMA_NUM 1 +#define GAMMA_LEN 63 +#define DEFAULT_GAMMA "0 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2" \ + +static void register_onboard_backlight(struct fbtft_par *par); + +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + if (par->pdata + && par->pdata->display.backlight == FBTFT_ONBOARD_BACKLIGHT) { + /* module uses onboard GPIO for panel power */ + par->fbtftops.register_backlight = register_onboard_backlight; + } + + par->fbtftops.reset(par); + + write_reg(par, 0xfd, 0x12); /* Command Lock */ + write_reg(par, 0xfd, 0xb1); /* Command Lock */ + write_reg(par, 0xae); /* Display Off */ + write_reg(par, 0xb3, 0xf1); /* Front Clock Div */ + write_reg(par, 0xca, 0x7f); /* Set Mux Ratio */ + write_reg(par, 0x15, 0x00, 0x7f); /* Set Column Address */ + write_reg(par, 0x75, 0x00, 0x7f); /* Set Row Address */ + write_reg(par, 0xa1, 0x00); /* Set Display Start Line */ + write_reg(par, 0xa2, 0x00); /* Set Display Offset */ + write_reg(par, 0xb5, 0x00); /* Set GPIO */ + write_reg(par, 0xab, 0x01); /* Set Function Selection */ + write_reg(par, 0xb1, 0x32); /* Set Phase Length */ + write_reg(par, 0xb4, 0xa0, 0xb5, 0x55); /* Set Segment Low Voltage */ + write_reg(par, 0xbb, 0x17); /* Set Precharge Voltage */ + write_reg(par, 0xbe, 0x05); /* Set VComH Voltage */ + write_reg(par, 0xc1, 0xc8, 0x80, 0xc8); /* Set Contrast */ + write_reg(par, 0xc7, 0x0f); /* Set Master Contrast */ + write_reg(par, 0xb6, 0x01); /* Set Second Precharge Period */ + write_reg(par, 0xa6); /* Set Display Mode Reset */ + write_reg(par, 0xaf); /* Set Sleep Mode Display On */ + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + write_reg(par, 0x15, xs, xe); + write_reg(par, 0x75, ys, ye); + write_reg(par, 0x5c); +} + +static int set_var(struct fbtft_par *par) +{ + unsigned remap; + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + if (par->fbtftops.init_display != init_display) { + /* don't risk messing up register A0h */ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, + "%s: skipping since custom init_display() is used\n", + __func__); + return 0; + } + + remap = 0x60 | (par->bgr << 2); /* Set Colour Depth */ + + switch (par->info->var.rotate) { + case 0: + write_reg(par, 0xA0, remap | 0b00 | 1<<4); + break; + case 270: + write_reg(par, 0xA0, remap | 0b11 | 1<<4); + break; + case 180: + write_reg(par, 0xA0, remap | 0b10); + break; + case 90: + write_reg(par, 0xA0, remap | 0b01); + break; + } + + return 0; +} + +/* + Grayscale Lookup Table + GS1 - GS63 + The driver Gamma curve contains the relative values between the entries + in the Lookup table. + + From datasheet: + 8.8 Gray Scale Decoder + + there are total 180 Gamma Settings (Setting 0 to Setting 180) + available for the Gray Scale table. + + The gray scale is defined in incremental way, with reference + to the length of previous table entry: + Setting of GS1 has to be >= 0 + Setting of GS2 has to be > Setting of GS1 +1 + Setting of GS3 has to be > Setting of GS2 +1 + : + Setting of GS63 has to be > Setting of GS62 +1 + + +*/ +static int set_gamma(struct fbtft_par *par, unsigned long *curves) +{ + unsigned long tmp[GAMMA_NUM * GAMMA_LEN]; + int i, acc = 0; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + for (i = 0; i < 63; i++) { + if (i > 0 && curves[i] < 2) { + dev_err(par->info->device, + "Illegal value in Grayscale Lookup Table at index %d. " \ + "Must be greater than 1\n", i); + return -EINVAL; + } + acc += curves[i]; + tmp[i] = acc; + if (acc > 180) { + dev_err(par->info->device, + "Illegal value(s) in Grayscale Lookup Table. " \ + "At index=%d, the accumulated value has exceeded 180\n", i); + return -EINVAL; + } + } + + write_reg(par, 0xB8, + tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], + tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14], tmp[15], + tmp[16], tmp[17], tmp[18], tmp[19], tmp[20], tmp[21], tmp[22], tmp[23], + tmp[24], tmp[25], tmp[26], tmp[27], tmp[28], tmp[29], tmp[30], tmp[31], + tmp[32], tmp[33], tmp[34], tmp[35], tmp[36], tmp[37], tmp[38], tmp[39], + tmp[40], tmp[41], tmp[42], tmp[43], tmp[44], tmp[45], tmp[46], tmp[47], + tmp[48], tmp[49], tmp[50], tmp[51], tmp[52], tmp[53], tmp[54], tmp[55], + tmp[56], tmp[57], tmp[58], tmp[59], tmp[60], tmp[61], tmp[62]); + + return 0; +} + +static int blank(struct fbtft_par *par, bool on) +{ + fbtft_par_dbg(DEBUG_BLANK, par, "%s(blank=%s)\n", + __func__, on ? "true" : "false"); + if (on) + write_reg(par, 0xAE); + else + write_reg(par, 0xAF); + return 0; +} + + +static struct fbtft_display display = { + .regwidth = 8, + .width = WIDTH, + .height = HEIGHT, + .gamma_num = GAMMA_NUM, + .gamma_len = GAMMA_LEN, + .gamma = DEFAULT_GAMMA, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_var = set_var, + .set_gamma = set_gamma, + .blank = blank, + }, +}; + +#ifdef CONFIG_FB_BACKLIGHT +static int update_onboard_backlight(struct backlight_device *bd) +{ + struct fbtft_par *par = bl_get_data(bd); + bool on; + + fbtft_par_dbg(DEBUG_BACKLIGHT, par, + "%s: power=%d, fb_blank=%d\n", + __func__, bd->props.power, bd->props.fb_blank); + + on = (bd->props.power == FB_BLANK_UNBLANK) + && (bd->props.fb_blank == FB_BLANK_UNBLANK); + /* Onboard backlight connected to GPIO0 on SSD1351, GPIO1 unused */ + write_reg(par, 0xB5, on ? 0x03 : 0x02); + + return 0; +} + +static void register_onboard_backlight(struct fbtft_par *par) +{ + struct backlight_device *bd; + struct backlight_properties bl_props = { 0, }; + struct backlight_ops *bl_ops; + + fbtft_par_dbg(DEBUG_BACKLIGHT, par, "%s()\n", __func__); + + bl_ops = devm_kzalloc(par->info->device, sizeof(struct backlight_ops), + GFP_KERNEL); + if (!bl_ops) { + dev_err(par->info->device, + "%s: could not allocate memory for backlight operations.\n", + __func__); + return; + } + + bl_ops->update_status = update_onboard_backlight; + bl_props.type = BACKLIGHT_RAW; + bl_props.power = FB_BLANK_POWERDOWN; + + bd = backlight_device_register(dev_driver_string(par->info->device), + par->info->device, par, bl_ops, &bl_props); + if (IS_ERR(bd)) { + dev_err(par->info->device, + "cannot register backlight device (%ld)\n", + PTR_ERR(bd)); + return; + } + par->info->bl_dev = bd; + + if (!par->fbtftops.unregister_backlight) + par->fbtftops.unregister_backlight = fbtft_unregister_backlight; +} +#else +static void register_onboard_backlight(struct fbtft_par *par) { }; +#endif + + +FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1351", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:ssd1351"); +MODULE_ALIAS("platform:ssd1351"); + +MODULE_DESCRIPTION("SSD1351 OLED Driver"); +MODULE_AUTHOR("James Davies"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_st7735r.c b/drivers/video/fbtft/fb_st7735r.c new file mode 100644 index 0000000..b63aa38 --- /dev/null +++ b/drivers/video/fbtft/fb_st7735r.c @@ -0,0 +1,195 @@ +/* + * FB driver for the ST7735R LCD Controller + * + * Copyright (C) 2013 Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_st7735r" +#define DEFAULT_GAMMA "0F 1A 0F 18 2F 28 20 22 1F 1B 23 37 00 07 02 10\n" \ + "0F 1B 0F 17 33 2C 29 2E 30 30 39 3F 00 07 03 10" + + +static int default_init_sequence[] = { + /* SWRESET - Software reset */ + -1, 0x01, + -2, 150, /* delay */ + + /* SLPOUT - Sleep out & booster on */ + -1, 0x11, + -2, 500, /* delay */ + + /* FRMCTR1 - frame rate control: normal mode + frame rate = fosc / (1 x 2 + 40) * (LINE + 2C + 2D) */ + -1, 0xB1, 0x01, 0x2C, 0x2D, + + /* FRMCTR2 - frame rate control: idle mode + frame rate = fosc / (1 x 2 + 40) * (LINE + 2C + 2D) */ + -1, 0xB2, 0x01, 0x2C, 0x2D, + + /* FRMCTR3 - frame rate control - partial mode + dot inversion mode, line inversion mode */ + -1, 0xB3, 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D, + + /* INVCTR - display inversion control + no inversion */ + -1, 0xB4, 0x07, + + /* PWCTR1 - Power Control + -4.6V, AUTO mode */ + -1, 0xC0, 0xA2, 0x02, 0x84, + + /* PWCTR2 - Power Control + VGH25 = 2.4C VGSEL = -10 VGH = 3 * AVDD */ + -1, 0xC1, 0xC5, + + /* PWCTR3 - Power Control + Opamp current small, Boost frequency */ + -1, 0xC2, 0x0A, 0x00, + + /* PWCTR4 - Power Control + BCLK/2, Opamp current small & Medium low */ + -1, 0xC3,0x8A,0x2A, + + /* PWCTR5 - Power Control */ + -1, 0xC4, 0x8A, 0xEE, + + /* VMCTR1 - Power Control */ + -1, 0xC5, 0x0E, + + /* INVOFF - Display inversion off */ + -1, 0x20, + + /* COLMOD - Interface pixel format */ + -1, 0x3A, 0x05, + + /* DISPON - Display On */ + -1, 0x29, + -2, 100, /* delay */ + + /* NORON - Partial off (Normal) */ + -1, 0x13, + -2, 10, /* delay */ + + /* end marker */ + -3 +}; + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* Column address */ + write_reg(par, 0x2A, xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF); + + /* Row adress */ + write_reg(par, 0x2B, ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF); + + /* Memory write */ + write_reg(par, 0x2C); +} + +#define MY (1 << 7) +#define MX (1 << 6) +#define MV (1 << 5) +static int set_var(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* MADCTL - Memory data access control + RGB/BGR: + 1. Mode selection pin SRGB + RGB H/W pin for color filter setting: 0=RGB, 1=BGR + 2. MADCTL RGB bit + RGB-BGR ORDER color filter panel: 0=RGB, 1=BGR */ + switch (par->info->var.rotate) { + case 0: + write_reg(par, 0x36, MX | MY | (par->bgr << 3)); + break; + case 270: + write_reg(par, 0x36, MY | MV | (par->bgr << 3)); + break; + case 180: + write_reg(par, 0x36, (par->bgr << 3)); + break; + case 90: + write_reg(par, 0x36, MX | MV | (par->bgr << 3)); + break; + } + + return 0; +} + +/* + Gamma string format: + VRF0P VOS0P PK0P PK1P PK2P PK3P PK4P PK5P PK6P PK7P PK8P PK9P SELV0P SELV1P SELV62P SELV63P + VRF0N VOS0N PK0N PK1N PK2N PK3N PK4N PK5N PK6N PK7N PK8N PK9N SELV0N SELV1N SELV62N SELV63N +*/ +#define CURVE(num, idx) curves[num*par->gamma.num_values + idx] +static int set_gamma(struct fbtft_par *par, unsigned long *curves) +{ + int i,j; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* apply mask */ + for (i = 0; i < par->gamma.num_curves; i++) + for (j = 0; j < par->gamma.num_values; j++) + CURVE(i,j) &= 0b111111; + + for (i = 0; i < par->gamma.num_curves; i++) + write_reg(par, 0xE0 + i, + CURVE(i, 0), CURVE(i, 1), CURVE(i, 2), CURVE(i, 3), + CURVE(i, 4), CURVE(i, 5), CURVE(i, 6), CURVE(i, 7), + CURVE(i, 8), CURVE(i, 9), CURVE(i, 10), CURVE(i, 11), + CURVE(i, 12), CURVE(i, 13), CURVE(i, 14), CURVE(i,15)); + + return 0; +} +#undef CURVE + + +static struct fbtft_display display = { + .regwidth = 8, + .width = 128, + .height = 160, + .init_sequence = default_init_sequence, + .gamma_num = 2, + .gamma_len = 16, + .gamma = DEFAULT_GAMMA, + .fbtftops = { + .set_addr_win = set_addr_win, + .set_var = set_var, + .set_gamma = set_gamma, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "sitronix,st7735r", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:st7735r"); +MODULE_ALIAS("platform:st7735r"); + +MODULE_DESCRIPTION("FB driver for the ST7735R LCD Controller"); +MODULE_AUTHOR("Noralf Tronnes"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_tinylcd.c b/drivers/video/fbtft/fb_tinylcd.c new file mode 100644 index 0000000..ca98bfb --- /dev/null +++ b/drivers/video/fbtft/fb_tinylcd.c @@ -0,0 +1,124 @@ +/* + * Custom FB driver for tinylcd.com display + * + * Copyright (C) 2013 Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_tinylcd" +#define WIDTH 320 +#define HEIGHT 480 + + +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + write_reg(par, 0xB0, 0x80); + write_reg(par, 0xC0, 0x0A, 0x0A); + write_reg(par, 0xC1, 0x45, 0x07); + write_reg(par, 0xC2, 0x33); + write_reg(par, 0xC5, 0x00, 0x42, 0x80); + write_reg(par, 0xB1, 0xD0, 0x11); + write_reg(par, 0xB4, 0x02); + write_reg(par, 0xB6, 0x00, 0x22, 0x3B); + write_reg(par, 0xB7, 0x07); + write_reg(par, 0x36, 0x58); + write_reg(par, 0xF0, 0x36, 0xA5, 0xD3); + write_reg(par, 0xE5, 0x80); + write_reg(par, 0xE5, 0x01); + write_reg(par, 0xB3, 0x00); + write_reg(par, 0xE5, 0x00); + write_reg(par, 0xF0, 0x36, 0xA5, 0x53); + write_reg(par, 0xE0, 0x00, 0x35, 0x33, 0x00, 0x00, 0x00, + 0x00, 0x35, 0x33, 0x00, 0x00, 0x00); + write_reg(par, 0x3A, 0x55); + write_reg(par, 0x11); + udelay(250); + write_reg(par, 0x29); + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* Column address */ + write_reg(par, 0x2A, xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF); + + /* Row adress */ + write_reg(par, 0x2B, ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF); + + /* Memory write */ + write_reg(par, 0x2C); +} + +static int set_var(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + switch (par->info->var.rotate) { + case 270: + write_reg(par, 0xB6, 0x00, 0x02, 0x3B); + write_reg(par, 0x36, 0x28); + break; + case 180: + write_reg(par, 0xB6, 0x00, 0x22, 0x3B); + write_reg(par, 0x36, 0x58); + break; + case 90: + write_reg(par, 0xB6, 0x00, 0x22, 0x3B); + write_reg(par, 0x36, 0x38); + break; + default: + write_reg(par, 0xB6, 0x00, 0x22, 0x3B); + write_reg(par, 0x36, 0x08); + break; + } + + return 0; +} + + +static struct fbtft_display display = { + .regwidth = 8, + .width = WIDTH, + .height = HEIGHT, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_var = set_var, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "neosec,tinylcd", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("spi:tinylcd"); + +MODULE_DESCRIPTION("Custom FB driver for tinylcd.com display"); +MODULE_AUTHOR("Noralf Tronnes"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_tls8204.c b/drivers/video/fbtft/fb_tls8204.c new file mode 100644 index 0000000..8738c7a --- /dev/null +++ b/drivers/video/fbtft/fb_tls8204.c @@ -0,0 +1,176 @@ +/* + * FB driver for the TLS8204 LCD Controller + * + * The display is monochrome and the video memory is RGB565. + * Any pixel value except 0 turns the pixel on. + * + * Copyright (C) 2013 Noralf Tronnes + * Copyright (C) 2014 Michael Hope (adapted for the TLS8204) + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_tls8204" +#define WIDTH 84 +#define HEIGHT 48 +#define TXBUFLEN WIDTH +#define DEFAULT_GAMMA "40" /* gamma is used to control contrast in this driver */ + +static unsigned bs = 4; +module_param(bs, uint, 0); +MODULE_PARM_DESC(bs, "BS[2:0] Bias voltage level: 0-7 (default: 4)"); + +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + /* Enter extended command mode */ + write_reg(par, 0x21); /* 5:1 1 + 2:0 PD - Powerdown control: chip is active + 1:0 V - Entry mode: horizontal addressing + 0:1 H - Extended instruction set control: extended + */ + + /* H=1 Bias system */ + write_reg(par, 0x10 | (bs & 0x7)); /* + 4:1 1 + 3:0 0 + 2:x BS2 - Bias System + 1:x BS1 + 0:x BS0 + */ + + /* Set the address of the first display line. */ + write_reg(par, 0x04 | (64 >> 6)); + write_reg(par, 0x40 | (64 & 0x3F)); + + /* Enter H=0 standard command mode */ + write_reg(par, 0x20); + + /* H=0 Display control */ + write_reg(par, 0x08 | 4); /* + 3:1 1 + 2:1 D - DE: 10=normal mode + 1:0 0 + 0:0 E + */ + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* H=0 Set X address of RAM */ + write_reg(par, 0x80); /* 7:1 1 + 6-0: X[6:0] - 0x00 + */ + + /* H=0 Set Y address of RAM */ + write_reg(par, 0x40); /* 7:0 0 + 6:1 1 + 2-0: Y[2:0] - 0x0 + */ +} + +static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) +{ + u16 *vmem16 = (u16 *)par->info->screen_base; + int x, y, i; + int ret = 0; + + fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s()\n", __func__); + + for (y = 0; y < HEIGHT/8; y++) { + u8 *buf = par->txbuf.buf; + /* The display is 102x68 but the LCD is 84x48. Set + the write pointer at the start of each row. */ + gpio_set_value(par->gpio.dc, 0); + write_reg(par, 0x80 | 0); + write_reg(par, 0x40 | y); + + for (x = 0; x < WIDTH; x++) { + u8 ch = 0; + for (i = 0; i < 8*WIDTH; i += WIDTH) { + ch >>= 1; + if (vmem16[(y*8*WIDTH)+i+x]) + ch |= 0x80; + } + *buf++ = ch; + } + /* Write the row */ + gpio_set_value(par->gpio.dc, 1); + ret = par->fbtftops.write(par, par->txbuf.buf, WIDTH); + if (ret < 0) { + dev_err(par->info->device, + "%s: write failed and returned: %d\n", __func__, ret); + break; + } + } + + return ret; +} + +static int set_gamma(struct fbtft_par *par, unsigned long *curves) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* apply mask */ + curves[0] &= 0x7F; + + write_reg(par, 0x21); /* turn on extended instruction set */ + write_reg(par, 0x80 | curves[0]); + write_reg(par, 0x20); /* turn off extended instruction set */ + + return 0; +} + + +static struct fbtft_display display = { + .regwidth = 8, + .width = WIDTH, + .height = HEIGHT, + .txbuflen = TXBUFLEN, + .gamma_num = 1, + .gamma_len = 1, + .gamma = DEFAULT_GAMMA, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .write_vmem = write_vmem, + .set_gamma = set_gamma, + }, + .backlight = 1, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "teralane,tls8204", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("spi:tls8204"); + +MODULE_DESCRIPTION("FB driver for the TLS8204 LCD Controller"); +MODULE_AUTHOR("Michael Hope"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_uc1701.c b/drivers/video/fbtft/fb_uc1701.c new file mode 100644 index 0000000..d70ac52 --- /dev/null +++ b/drivers/video/fbtft/fb_uc1701.c @@ -0,0 +1,210 @@ +/* + * FB driver for the UC1701 LCD Controller + * + * The display is monochrome and the video memory is RGB565. + * Any pixel value except 0 turns the pixel on. + * + * Copyright (C) 2014 Juergen Holzmann + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_uc1701" +#define WIDTH 102 +#define HEIGHT 64 +#define PAGES (HEIGHT/8) + +/* 1: Display on/off */ +#define LCD_DISPLAY_ENABLE 0xAE +/* 2: display start line set */ +#define LCD_START_LINE 0x40 +/* 3: Page address set (lower 4 bits select one of the pages) */ +#define LCD_PAGE_ADDRESS 0xB0 +/* 4: column address */ +#define LCD_COL_ADDRESS 0x10 +/* 8: select orientation */ +#define LCD_BOTTOMVIEW 0xA0 +/* 9: inverted display */ +#define LCD_DISPLAY_INVERT 0xA6 +/* 10: show memory content or switch all pixels on */ +#define LCD_ALL_PIXEL 0xA4 +/* 11: lcd bias set */ +#define LCD_BIAS 0xA2 +/* 14: Reset Controller */ +#define LCD_RESET_CMD 0xE2 +/* 15: output mode select (turns display upside-down) */ +#define LCD_SCAN_DIR 0xC0 +/* 16: power control set */ +#define LCD_POWER_CONTROL 0x28 +/* 17: voltage regulator resistor ratio set */ +#define LCD_VOLTAGE 0x20 +/* 18: Volume mode set */ +#define LCD_VOLUME_MODE 0x81 +/* 22: NOP command */ +#define LCD_NO_OP 0xE3 +/* 25: advanced program control */ +#define LCD_ADV_PROG_CTRL 0xFA +/* 25: advanced program control2 */ +#define LCD_ADV_PROG_CTRL2 0x10 +#define LCD_TEMPCOMP_HIGH 0x80 +/* column offset for normal orientation */ +#define SHIFT_ADDR_NORMAL 0 +/* column offset for bottom view orientation */ +#define SHIFT_ADDR_TOPVIEW 30 + + +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + /* softreset of LCD */ + write_reg(par, LCD_RESET_CMD); + mdelay(10); + + /* set startpoint */ + /* LCD_START_LINE | (pos & 0x3F) */ + write_reg(par, LCD_START_LINE); + + /* select orientation BOTTOMVIEW */ + write_reg(par, LCD_BOTTOMVIEW | 1); + /* output mode select (turns display upside-down) */ + write_reg(par, LCD_SCAN_DIR | 0x00); + + /* Normal Pixel mode */ + write_reg(par, LCD_ALL_PIXEL | 0); + + /* positive display */ + write_reg(par, LCD_DISPLAY_INVERT | 0); + + /* bias 1/9 */ + write_reg(par, LCD_BIAS | 0); + + /* power control mode: all features on */ + /* LCD_POWER_CONTROL | (val&0x07) */ + write_reg(par, LCD_POWER_CONTROL | 0x07); + + /* set voltage regulator R/R */ + /* LCD_VOLTAGE | (val&0x07) */ + write_reg(par, LCD_VOLTAGE | 0x07); + + /* volume mode set */ + /* LCD_VOLUME_MODE,val&0x3f,LCD_NO_OP */ + write_reg(par, LCD_VOLUME_MODE); + /* LCD_VOLUME_MODE,val&0x3f,LCD_NO_OP */ + write_reg(par, 0x09); + /* ???? */ + /* LCD_VOLUME_MODE,val&0x3f,LCD_NO_OP */ + write_reg(par, LCD_NO_OP); + + /* advanced program control */ + write_reg(par, LCD_ADV_PROG_CTRL); + write_reg(par, LCD_ADV_PROG_CTRL2|LCD_TEMPCOMP_HIGH); + + /* enable display */ + write_reg(par, LCD_DISPLAY_ENABLE | 1); + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* goto address */ + /* LCD_PAGE_ADDRESS | ((page) & 0x1F), + (((col)+SHIFT_ADDR_NORMAL) & 0x0F), + LCD_COL_ADDRESS | ((((col)+SHIFT_ADDR_NORMAL)>>4) & 0x0F) */ + write_reg(par, LCD_PAGE_ADDRESS); + /* LCD_PAGE_ADDRESS | ((page) & 0x1F), + (((col)+SHIFT_ADDR_NORMAL) & 0x0F), + LCD_COL_ADDRESS | ((((col)+SHIFT_ADDR_NORMAL)>>4) & 0x0F) */ + write_reg(par, 0x00); + /* LCD_PAGE_ADDRESS | ((page) & 0x1F), + (((col)+SHIFT_ADDR_NORMAL) & 0x0F), + LCD_COL_ADDRESS | ((((col)+SHIFT_ADDR_NORMAL)>>4) & 0x0F) */ + write_reg(par, LCD_COL_ADDRESS); +} + +static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) +{ + u16 *vmem16 = (u16 *)par->info->screen_base; + u8 *buf = par->txbuf.buf; + int x, y, i; + int ret = 0; + + fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s()\n", __func__); + + for (y = 0; y < PAGES; y++) { + buf = par->txbuf.buf; + for (x = 0; x < WIDTH; x++) { + *buf = 0x00; + for (i = 0; i < 8; i++) + *buf |= (vmem16[((y*8*WIDTH)+(i*WIDTH))+x] ? 1 : 0) << i; + buf++; + } + /* LCD_PAGE_ADDRESS | ((page) & 0x1F), + (((col)+SHIFT_ADDR_NORMAL) & 0x0F), + LCD_COL_ADDRESS | ((((col)+SHIFT_ADDR_NORMAL)>>4) & 0x0F) */ + write_reg(par, LCD_PAGE_ADDRESS|(u8)y); + /* LCD_PAGE_ADDRESS | ((page) & 0x1F), + (((col)+SHIFT_ADDR_NORMAL) & 0x0F), + LCD_COL_ADDRESS | ((((col)+SHIFT_ADDR_NORMAL)>>4) & 0x0F) */ + write_reg(par, 0x00); + /* LCD_PAGE_ADDRESS | ((page) & 0x1F), + (((col)+SHIFT_ADDR_NORMAL) & 0x0F), + LCD_COL_ADDRESS | ((((col)+SHIFT_ADDR_NORMAL)>>4) & 0x0F) */ + write_reg(par, LCD_COL_ADDRESS); + gpio_set_value(par->gpio.dc, 1); + ret = par->fbtftops.write(par, par->txbuf.buf, WIDTH); + gpio_set_value(par->gpio.dc, 0); + } + + if (ret < 0) + dev_err(par->info->device, "%s: write failed and returned: %d\n", __func__, ret); + + return ret; +} + + +static struct fbtft_display display = { + .regwidth = 8, + .width = WIDTH, + .height = HEIGHT, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .write_vmem = write_vmem, + }, + .backlight = 1, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "UltraChip,uc1701", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("spi:uc1701"); + +MODULE_DESCRIPTION("FB driver for the UC1701 LCD Controller"); +MODULE_AUTHOR("Juergen Holzmann"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_upd161704.c b/drivers/video/fbtft/fb_upd161704.c new file mode 100644 index 0000000..fff57b3 --- /dev/null +++ b/drivers/video/fbtft/fb_upd161704.c @@ -0,0 +1,206 @@ +/* + * FB driver for the uPD161704 LCD Controller + * + * Copyright (C) 2014 Seong-Woo Kim + * + * Based on fb_ili9325.c by Noralf Tronnes + * Based on ili9325.c by Jeroen Domburg + * Init code from UTFT library by Henning Karlsen + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_upd161704" +#define WIDTH 240 +#define HEIGHT 320 +#define BPP 16 + +static int init_display(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + par->fbtftops.reset(par); + + if (par->gpio.cs != -1) + gpio_set_value(par->gpio.cs, 0); /* Activate chip */ + + /* Initialization sequence from Lib_UTFT */ + + /* register reset */ + write_reg(par, 0x0003,0x0001); /* Soft reset */ + + /* oscillator start */ + write_reg(par, 0x003A,0x0001); /*Oscillator 0: stop, 1: operation */ + udelay(100); + + /* y-setting */ + write_reg(par, 0x0024,0x007B); /* amplitude setting */ + udelay(10); + write_reg(par, 0x0025,0x003B); /* amplitude setting */ + write_reg(par, 0x0026,0x0034); /* amplitude setting */ + udelay(10); + write_reg(par, 0x0027,0x0004); /* amplitude setting */ + write_reg(par, 0x0052,0x0025); /* circuit setting 1 */ + udelay(10); + write_reg(par, 0x0053,0x0033); /* circuit setting 2 */ + write_reg(par, 0x0061,0x001C); /* adjustment V10 positive polarity */ + udelay(10); + write_reg(par, 0x0062,0x002C); /* adjustment V9 negative polarity */ + write_reg(par, 0x0063,0x0022); /* adjustment V34 positive polarity */ + udelay(10); + write_reg(par, 0x0064,0x0027); /* adjustment V31 negative polarity */ + udelay(10); + write_reg(par, 0x0065,0x0014); /* adjustment V61 negative polarity */ + udelay(10); + write_reg(par, 0x0066,0x0010); /* adjustment V61 negative polarity */ + + /* Basical clock for 1 line (BASECOUNT[7:0]) number specified */ + write_reg(par, 0x002E,0x002D); + + /* Power supply setting */ + write_reg(par, 0x0019,0x0000); /* DC/DC output setting */ + udelay(200); + write_reg(par, 0x001A,0x1000); /* DC/DC frequency setting */ + write_reg(par, 0x001B,0x0023); /* DC/DC rising setting */ + write_reg(par, 0x001C,0x0C01); /* Regulator voltage setting */ + write_reg(par, 0x001D,0x0000); /* Regulator current setting */ + write_reg(par, 0x001E,0x0009); /* VCOM output setting */ + write_reg(par, 0x001F,0x0035); /* VCOM amplitude setting */ + write_reg(par, 0x0020,0x0015); /* VCOMM cencter setting */ + write_reg(par, 0x0018,0x1E7B); /* DC/DC operation setting */ + + /* windows setting */ + write_reg(par, 0x0008,0x0000); /* Minimum X address */ + write_reg(par, 0x0009,0x00EF); /* Maximum X address */ + write_reg(par, 0x000a,0x0000); /* Minimum Y address */ + write_reg(par, 0x000b,0x013F); /* Maximum Y address */ + + /* LCD display area setting */ + write_reg(par, 0x0029,0x0000); /* [LCDSIZE] X MIN. size set */ + write_reg(par, 0x002A,0x0000); /* [LCDSIZE] Y MIN. size set */ + write_reg(par, 0x002B,0x00EF); /* [LCDSIZE] X MAX. size set */ + write_reg(par, 0x002C,0x013F); /* [LCDSIZE] Y MAX. size set */ + + /* Gate scan setting */ + write_reg(par, 0x0032,0x0002); + + /* n line inversion line number */ + write_reg(par, 0x0033,0x0000); + + /* Line inversion/frame inversion/interlace setting */ + write_reg(par, 0x0037,0x0000); + + /* Gate scan operation setting register */ + write_reg(par, 0x003B,0x0001); + + /* Color mode */ + /*GS = 0: 260-k color (64 gray scale), GS = 1: 8 color (2 gray scale) */ + write_reg(par, 0x0004,0x0000); + + /* RAM control register */ + write_reg(par, 0x0005,0x0000); /*Window access 00:Normal, 10:Window */ + + /* Display setting register 2 */ + write_reg(par, 0x0001,0x0000); + + /* display setting */ + write_reg(par, 0x0000,0x0000); /* display on */ + + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + switch (par->info->var.rotate) { + /* R20h = Horizontal GRAM Start Address */ + /* R21h = Vertical GRAM Start Address */ + case 0: + write_reg(par, 0x0006, xs); + write_reg(par, 0x0007, ys); + break; + case 180: + write_reg(par, 0x0006, WIDTH - 1 - xs); + write_reg(par, 0x0007, HEIGHT - 1 - ys); + break; + case 270: + write_reg(par, 0x0006, WIDTH - 1 - ys); + write_reg(par, 0x0007, xs); + break; + case 90: + write_reg(par, 0x0006, ys); + write_reg(par, 0x0007, HEIGHT - 1 - xs); + break; + } + + write_reg(par, 0x0e); /* Write Data to GRAM */ +} + +static int set_var(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + switch (par->info->var.rotate) { + /* AM: GRAM update direction */ + case 0: + write_reg(par, 0x01, 0x0000); + write_reg(par, 0x05, 0x0000); + break; + case 180: + write_reg(par, 0x01, 0x00C0); + write_reg(par, 0x05, 0x0000); + break; + case 270: + write_reg(par, 0x01, 0x0080); + write_reg(par, 0x05, 0x0001); + break; + case 90: + write_reg(par, 0x01, 0x0040); + write_reg(par, 0x05, 0x0001); + break; + } + + return 0; +} + +static struct fbtft_display display = { + .regwidth = 16, + .width = WIDTH, + .height = HEIGHT, + .fbtftops = { + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_var = set_var, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "nec,upd161704", &display); + +MODULE_ALIAS("spi:" DRVNAME); +MODULE_ALIAS("platform:" DRVNAME); +MODULE_ALIAS("spi:upd161704"); +MODULE_ALIAS("platform:upd161704"); + +MODULE_DESCRIPTION("FB driver for the uPD161704 LCD Controller"); +MODULE_AUTHOR("Seong-Woo Kim"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fb_watterott.c b/drivers/video/fbtft/fb_watterott.c new file mode 100644 index 0000000..975b579 --- /dev/null +++ b/drivers/video/fbtft/fb_watterott.c @@ -0,0 +1,324 @@ +/* + * FB driver for the Watterott LCD Controller + * + * Copyright (C) 2013 Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fb_watterott" +#define WIDTH 320 +#define HEIGHT 240 +#define FPS 5 +#define TXBUFLEN 1024 +#define DEFAULT_BRIGHTNESS 50 + +#define CMD_VERSION 0x01 +#define CMD_LCD_LED 0x10 +#define CMD_LCD_RESET 0x11 +#define CMD_LCD_ORIENTATION 0x20 +#define CMD_LCD_DRAWIMAGE 0x27 +#define COLOR_RGB323 8 +#define COLOR_RGB332 9 +#define COLOR_RGB233 10 +#define COLOR_RGB565 16 + + +static short mode = 565; +module_param(mode, short, 0); +MODULE_PARM_DESC(mode, "RGB color transfer mode: 332, 565 (default)"); + +static void write_reg8_bus8(struct fbtft_par *par, int len, ...) +{ + va_list args; + int i, ret; + u8 *buf = par->buf; + + va_start(args, len); + for (i = 0; i < len; i++) + *buf++ = (u8)va_arg(args, unsigned int); + va_end(args); + + fbtft_par_dbg_hex(DEBUG_WRITE_REGISTER, par, + par->info->device, u8, par->buf, len, "%s: ", __func__); + + ret = par->fbtftops.write(par, par->buf, len); + if (ret < 0) { + dev_err(par->info->device, + "%s: write() failed and returned %d\n", __func__, ret); + return; + } +} + +static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) +{ + unsigned start_line, end_line; + u16 *vmem16 = (u16 *)(par->info->screen_base + offset); + u16 *pos = par->txbuf.buf + 1; + u16 *buf16 = par->txbuf.buf + 10; + int i, j; + int ret = 0; + + fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s()\n", __func__); + + start_line = offset / par->info->fix.line_length; + end_line = start_line + (len / par->info->fix.line_length) - 1; + + /* Set command header. pos: x, y, w, h */ + ((u8 *)par->txbuf.buf)[0] = CMD_LCD_DRAWIMAGE; + pos[0] = 0; + pos[2] = cpu_to_be16(par->info->var.xres); + pos[3] = cpu_to_be16(1); + ((u8 *)par->txbuf.buf)[9] = COLOR_RGB565; + + for (i = start_line; i <= end_line; i++) { + pos[1] = cpu_to_be16(i); + for (j = 0; j < par->info->var.xres; j++) + buf16[j] = cpu_to_be16(*vmem16++); + ret = par->fbtftops.write(par, + par->txbuf.buf, 10 + par->info->fix.line_length); + if (ret < 0) + return ret; + udelay(300); + } + + return 0; +} + +#define RGB565toRGB323(c) (((c&0xE000)>>8) | ((c&0600)>>6) | ((c&0x001C)>>2)) +#define RGB565toRGB332(c) (((c&0xE000)>>8) | ((c&0700)>>6) | ((c&0x0018)>>3)) +#define RGB565toRGB233(c) (((c&0xC000)>>8) | ((c&0700)>>5) | ((c&0x001C)>>2)) + +static int write_vmem_8bit(struct fbtft_par *par, size_t offset, size_t len) +{ + unsigned start_line, end_line; + u16 *vmem16 = (u16 *)(par->info->screen_base + offset); + u16 *pos = par->txbuf.buf + 1; + u8 *buf8 = par->txbuf.buf + 10; + int i, j; + int ret = 0; + + fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s()\n", __func__); + + start_line = offset / par->info->fix.line_length; + end_line = start_line + (len / par->info->fix.line_length) - 1; + + /* Set command header. pos: x, y, w, h */ + ((u8 *)par->txbuf.buf)[0] = CMD_LCD_DRAWIMAGE; + pos[0] = 0; + pos[2] = cpu_to_be16(par->info->var.xres); + pos[3] = cpu_to_be16(1); + ((u8 *)par->txbuf.buf)[9] = COLOR_RGB332; + + for (i = start_line; i <= end_line; i++) { + pos[1] = cpu_to_be16(i); + for (j = 0; j < par->info->var.xres; j++) { + buf8[j] = RGB565toRGB332(*vmem16); + vmem16++; + } + ret = par->fbtftops.write(par, + par->txbuf.buf, 10 + par->info->var.xres); + if (ret < 0) + return ret; + udelay(700); + } + + return 0; +} + +static unsigned firmware_version(struct fbtft_par *par) +{ + u8 rxbuf[4] = {0, }; + + write_reg(par, CMD_VERSION); + par->fbtftops.read(par, rxbuf, 4); + if (rxbuf[1] != '.') + return 0; + + return (rxbuf[0] - '0') << 8 | (rxbuf[2] - '0') << 4 | (rxbuf[3] - '0'); +} + +static int init_display(struct fbtft_par *par) +{ + int ret; + unsigned version; + u8 save_mode; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* enable SPI interface by having CS and MOSI low during reset */ + save_mode = par->spi->mode; + par->spi->mode |= SPI_CS_HIGH; + ret = par->spi->master->setup(par->spi); /* set CS inactive low */ + if (ret) { + dev_err(par->info->device, "Could not set SPI_CS_HIGH\n"); + return ret; + } + write_reg(par, 0x00); /* make sure mode is set */ + + mdelay(50); + par->fbtftops.reset(par); + mdelay(1000); + par->spi->mode = save_mode; + ret = par->spi->master->setup(par->spi); + if (ret) { + dev_err(par->info->device, "Could not restore SPI mode\n"); + return ret; + } + write_reg(par, 0x00); + + version = firmware_version(par); + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "Firmware version: %x.%02x\n", + version >> 8, version & 0xFF); + + if (mode == 332) + par->fbtftops.write_vmem = write_vmem_8bit; + return 0; +} + +static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + /* not used on this controller */ +} + +static int set_var(struct fbtft_par *par) +{ + u8 rotate; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* this controller rotates clock wise */ + switch (par->info->var.rotate) { + case 90: + rotate = 27; + break; + case 180: + rotate = 18; + break; + case 270: + rotate = 9; + break; + default: + rotate = 0; + } + write_reg(par, CMD_LCD_ORIENTATION, rotate); + + return 0; +} + +static int verify_gpios(struct fbtft_par *par) +{ + if (par->gpio.reset < 0) { + dev_err(par->info->device, "Missing 'reset' gpio. Aborting.\n"); + return -EINVAL; + } + return 0; +} + +#ifdef CONFIG_FB_BACKLIGHT +static int backlight_chip_update_status(struct backlight_device *bd) +{ + struct fbtft_par *par = bl_get_data(bd); + int brightness = bd->props.brightness; + + fbtft_par_dbg(DEBUG_BACKLIGHT, par, + "%s: brightness=%d, power=%d, fb_blank=%d\n", + __func__, bd->props.brightness, bd->props.power, + bd->props.fb_blank); + + if (bd->props.power != FB_BLANK_UNBLANK) + brightness = 0; + + if (bd->props.fb_blank != FB_BLANK_UNBLANK) + brightness = 0; + + write_reg(par, CMD_LCD_LED, brightness); + + return 0; +} + +static void register_chip_backlight(struct fbtft_par *par) +{ + struct backlight_device *bd; + struct backlight_properties bl_props = { 0, }; + struct backlight_ops *bl_ops; + + fbtft_par_dbg(DEBUG_BACKLIGHT, par, "%s()\n", __func__); + + bl_ops = devm_kzalloc(par->info->device, sizeof(struct backlight_ops), + GFP_KERNEL); + if (!bl_ops) { + dev_err(par->info->device, + "%s: could not allocate memory for backlight operations.\n", + __func__); + return; + } + + bl_ops->update_status = backlight_chip_update_status; + bl_props.type = BACKLIGHT_RAW; + bl_props.power = FB_BLANK_POWERDOWN; + bl_props.max_brightness = 100; + bl_props.brightness = DEFAULT_BRIGHTNESS; + + bd = backlight_device_register(dev_driver_string(par->info->device), + par->info->device, par, bl_ops, &bl_props); + if (IS_ERR(bd)) { + dev_err(par->info->device, + "cannot register backlight device (%ld)\n", + PTR_ERR(bd)); + return; + } + par->info->bl_dev = bd; + + if (!par->fbtftops.unregister_backlight) + par->fbtftops.unregister_backlight = fbtft_unregister_backlight; +} +#else +#define register_chip_backlight NULL +#endif + + +static struct fbtft_display display = { + .regwidth = 8, + .buswidth = 8, + .width = WIDTH, + .height = HEIGHT, + .fps = FPS, + .txbuflen = TXBUFLEN, + .fbtftops = { + .write_register = write_reg8_bus8, + .write_vmem = write_vmem, + .init_display = init_display, + .set_addr_win = set_addr_win, + .set_var = set_var, + .verify_gpios = verify_gpios, + .register_backlight = register_chip_backlight, + }, +}; +FBTFT_REGISTER_DRIVER(DRVNAME, "watterott,openlcd", &display); + +MODULE_ALIAS("spi:" DRVNAME); + +MODULE_DESCRIPTION("FB driver for the Watterott LCD Controller"); +MODULE_AUTHOR("Noralf Tronnes"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fbtft-bus.c b/drivers/video/fbtft/fbtft-bus.c new file mode 100644 index 0000000..b3cddb0 --- /dev/null +++ b/drivers/video/fbtft/fbtft-bus.c @@ -0,0 +1,256 @@ +#include +#include +#include +#include +#include "fbtft.h" + + + + +/***************************************************************************** + * + * void (*write_reg)(struct fbtft_par *par, int len, ...); + * + *****************************************************************************/ + +#define define_fbtft_write_reg(func, type, modifier) \ +void func(struct fbtft_par *par, int len, ...) \ +{ \ + va_list args; \ + int i, ret; \ + int offset = 0; \ + type *buf = (type *)par->buf; \ + \ + if (unlikely(par->debug & DEBUG_WRITE_REGISTER)) { \ + va_start(args, len); \ + for (i = 0; i < len; i++) { \ + buf[i] = (type)va_arg(args, unsigned int); \ + } \ + va_end(args); \ + fbtft_par_dbg_hex(DEBUG_WRITE_REGISTER, par, par->info->device, type, buf, len, "%s: ", __func__); \ + } \ + \ + va_start(args, len); \ + \ + if (par->startbyte) { \ + *(u8 *)par->buf = par->startbyte; \ + buf = (type *)(par->buf + 1); \ + offset = 1; \ + } \ + \ + *buf = modifier((type)va_arg(args, unsigned int)); \ + if (par->gpio.dc != -1) \ + gpio_set_value(par->gpio.dc, 0); \ + ret = par->fbtftops.write(par, par->buf, sizeof(type)+offset); \ + if (ret < 0) { \ + va_end(args); \ + dev_err(par->info->device, "%s: write() failed and returned %d\n", __func__, ret); \ + return; \ + } \ + len--; \ + \ + if (par->startbyte) \ + *(u8 *)par->buf = par->startbyte | 0x2; \ + \ + if (len) { \ + i = len; \ + while (i--) { \ + *buf++ = modifier((type)va_arg(args, unsigned int)); \ + } \ + if (par->gpio.dc != -1) \ + gpio_set_value(par->gpio.dc, 1); \ + ret = par->fbtftops.write(par, par->buf, len * (sizeof(type)+offset)); \ + if (ret < 0) { \ + va_end(args); \ + dev_err(par->info->device, "%s: write() failed and returned %d\n", __func__, ret); \ + return; \ + } \ + } \ + va_end(args); \ +} \ +EXPORT_SYMBOL(func); + +define_fbtft_write_reg(fbtft_write_reg8_bus8, u8, ) +define_fbtft_write_reg(fbtft_write_reg16_bus8, u16, cpu_to_be16) +define_fbtft_write_reg(fbtft_write_reg16_bus16, u16, ) + +void fbtft_write_reg8_bus9(struct fbtft_par *par, int len, ...) +{ + va_list args; + int i, ret; + int pad = 0; + u16 *buf = (u16 *)par->buf; + + if (unlikely(par->debug & DEBUG_WRITE_REGISTER)) { + va_start(args, len); + for (i = 0; i < len; i++) + *(((u8 *)buf) + i) = (u8)va_arg(args, unsigned int); + va_end(args); + fbtft_par_dbg_hex(DEBUG_WRITE_REGISTER, par, + par->info->device, u8, buf, len, "%s: ", __func__); + } + if (len <= 0) + return; + + if (par->spi && (par->spi->bits_per_word == 8)) { + /* we're emulating 9-bit, pad start of buffer with no-ops + (assuming here that zero is a no-op) */ + pad = (len % 4) ? 4 - (len % 4) : 0; + for (i = 0; i < pad; i++) + *buf++ = 0x000; + } + + va_start(args, len); + *buf++ = (u8)va_arg(args, unsigned int); + i = len - 1; + while (i--) { + *buf = (u8)va_arg(args, unsigned int); + *buf++ |= 0x100; /* dc=1 */ + } + va_end(args); + ret = par->fbtftops.write(par, par->buf, (len + pad) * sizeof(u16)); + if (ret < 0) { + dev_err(par->info->device, + "%s: write() failed and returned %d\n", __func__, ret); + return; + } +} +EXPORT_SYMBOL(fbtft_write_reg8_bus9); + + + + +/***************************************************************************** + * + * int (*write_vmem)(struct fbtft_par *par); + * + *****************************************************************************/ + +/* 16 bit pixel over 8-bit databus */ +int fbtft_write_vmem16_bus8(struct fbtft_par *par, size_t offset, size_t len) +{ + u16 *vmem16; + u16 *txbuf16 = (u16 *)par->txbuf.buf; + size_t remain; + size_t to_copy; + size_t tx_array_size; + int i; + int ret = 0; + size_t startbyte_size = 0; + + fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s(offset=%zu, len=%zu)\n", + __func__, offset, len); + + remain = len / 2; + vmem16 = (u16 *)(par->info->screen_base + offset); + + if (par->gpio.dc != -1) + gpio_set_value(par->gpio.dc, 1); + + /* non buffered write */ + if (!par->txbuf.buf) + return par->fbtftops.write(par, vmem16, len); + + /* buffered write */ + tx_array_size = par->txbuf.len / 2; + + if (par->startbyte) { + txbuf16 = (u16 *)(par->txbuf.buf + 1); + tx_array_size -= 2; + *(u8 *)(par->txbuf.buf) = par->startbyte | 0x2; + startbyte_size = 1; + } + + while (remain) { + to_copy = remain > tx_array_size ? tx_array_size : remain; + dev_dbg(par->info->device, " to_copy=%zu, remain=%zu\n", + to_copy, remain - to_copy); + + for (i = 0; i < to_copy; i++) + txbuf16[i] = cpu_to_be16(vmem16[i]); + + vmem16 = vmem16 + to_copy; + ret = par->fbtftops.write(par, par->txbuf.buf, + startbyte_size + to_copy * 2); + if (ret < 0) + return ret; + remain -= to_copy; + } + + return ret; +} +EXPORT_SYMBOL(fbtft_write_vmem16_bus8); + +/* 16 bit pixel over 9-bit SPI bus: dc + high byte, dc + low byte */ +int fbtft_write_vmem16_bus9(struct fbtft_par *par, size_t offset, size_t len) +{ + u8 *vmem8; + u16 *txbuf16 = par->txbuf.buf; + size_t remain; + size_t to_copy; + size_t tx_array_size; + int i; + int ret = 0; + + fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s(offset=%zu, len=%zu)\n", + __func__, offset, len); + + if (!par->txbuf.buf) { + dev_err(par->info->device, "%s: txbuf.buf is NULL\n", __func__); + return -1; + } + + remain = len; + vmem8 = par->info->screen_base + offset; + + tx_array_size = par->txbuf.len / 2; + + while (remain) { + to_copy = remain > tx_array_size ? tx_array_size : remain; + dev_dbg(par->info->device, " to_copy=%zu, remain=%zu\n", + to_copy, remain - to_copy); + +#ifdef __LITTLE_ENDIAN + for (i = 0; i < to_copy; i += 2) { + txbuf16[i] = 0x0100 | vmem8[i+1]; + txbuf16[i+1] = 0x0100 | vmem8[i]; + } +#else + for (i = 0; i < to_copy; i++) + txbuf16[i] = 0x0100 | vmem8[i]; +#endif + vmem8 = vmem8 + to_copy; + ret = par->fbtftops.write(par, par->txbuf.buf, to_copy*2); + if (ret < 0) + return ret; + remain -= to_copy; + } + + return ret; +} +EXPORT_SYMBOL(fbtft_write_vmem16_bus9); + +int fbtft_write_vmem8_bus8(struct fbtft_par *par, size_t offset, size_t len) +{ + dev_err(par->info->device, "%s: function not implemented\n", __func__); + return -1; +} +EXPORT_SYMBOL(fbtft_write_vmem8_bus8); + +/* 16 bit pixel over 16-bit databus */ +int fbtft_write_vmem16_bus16(struct fbtft_par *par, size_t offset, size_t len) +{ + u16 *vmem16; + + fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s(offset=%zu, len=%zu)\n", + __func__, offset, len); + + vmem16 = (u16 *)(par->info->screen_base + offset); + + if (par->gpio.dc != -1) + gpio_set_value(par->gpio.dc, 1); + + /* no need for buffered write with 16-bit bus */ + return par->fbtftops.write(par, vmem16, len); +} +EXPORT_SYMBOL(fbtft_write_vmem16_bus16); diff --git a/drivers/video/fbtft/fbtft-core.c b/drivers/video/fbtft/fbtft-core.c new file mode 100644 index 0000000..873e2c7 --- /dev/null +++ b/drivers/video/fbtft/fbtft-core.c @@ -0,0 +1,1516 @@ +/* + * Copyright (C) 2013 Noralf Tronnes + * + * This driver is inspired by: + * st7735fb.c, Copyright (C) 2011, Matt Porter + * broadsheetfb.c, Copyright (C) 2008, Jaya Kumar + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fbtft.h" + +extern void fbtft_sysfs_init(struct fbtft_par *par); +extern void fbtft_sysfs_exit(struct fbtft_par *par); +extern void fbtft_expand_debug_value(unsigned long *debug); +extern int fbtft_gamma_parse_str(struct fbtft_par *par, unsigned long *curves, + const char *str, int size); + +static unsigned long debug; +module_param(debug, ulong , 0); +MODULE_PARM_DESC(debug, "override device debug level"); + +static bool dma = true; +module_param(dma, bool, 0); +MODULE_PARM_DESC(dma, "Use DMA buffer"); + + +void fbtft_dbg_hex(const struct device *dev, int groupsize, + void *buf, size_t len, const char *fmt, ...) +{ + va_list args; + static char textbuf[512]; + char *text = textbuf; + size_t text_len; + + va_start(args, fmt); + text_len = vscnprintf(text, sizeof(textbuf), fmt, args); + va_end(args); + + hex_dump_to_buffer(buf, len, 32, groupsize, text + text_len, + 512 - text_len, false); + + if (len > 32) + dev_info(dev, "%s ...\n", text); + else + dev_info(dev, "%s\n", text); +} +EXPORT_SYMBOL(fbtft_dbg_hex); + +unsigned long fbtft_request_gpios_match(struct fbtft_par *par, + const struct fbtft_gpio *gpio) +{ + int ret; + long val; + + fbtft_par_dbg(DEBUG_REQUEST_GPIOS_MATCH, par, "%s('%s')\n", + __func__, gpio->name); + + if (strcasecmp(gpio->name, "reset") == 0) { + par->gpio.reset = gpio->gpio; + return GPIOF_OUT_INIT_HIGH; + } else if (strcasecmp(gpio->name, "dc") == 0) { + par->gpio.dc = gpio->gpio; + return GPIOF_OUT_INIT_LOW; + } else if (strcasecmp(gpio->name, "cs") == 0) { + par->gpio.cs = gpio->gpio; + return GPIOF_OUT_INIT_HIGH; + } else if (strcasecmp(gpio->name, "wr") == 0) { + par->gpio.wr = gpio->gpio; + return GPIOF_OUT_INIT_HIGH; + } else if (strcasecmp(gpio->name, "rd") == 0) { + par->gpio.rd = gpio->gpio; + return GPIOF_OUT_INIT_HIGH; + } else if (strcasecmp(gpio->name, "latch") == 0) { + par->gpio.latch = gpio->gpio; + return GPIOF_OUT_INIT_LOW; + } else if (gpio->name[0] == 'd' && gpio->name[1] == 'b') { + ret = kstrtol(&gpio->name[2], 10, &val); + if (ret == 0 && val < 16) { + par->gpio.db[val] = gpio->gpio; + return GPIOF_OUT_INIT_LOW; + } + } else if (strcasecmp(gpio->name, "led") == 0) { + par->gpio.led[0] = gpio->gpio; + return GPIOF_OUT_INIT_LOW; + } else if (strcasecmp(gpio->name, "led_") == 0) { + par->gpio.led[0] = gpio->gpio; + return GPIOF_OUT_INIT_HIGH; + } + + return FBTFT_GPIO_NO_MATCH; +} + +int fbtft_request_gpios(struct fbtft_par *par) +{ + struct fbtft_platform_data *pdata = par->pdata; + const struct fbtft_gpio *gpio; + unsigned long flags; + int ret; + + if (pdata && pdata->gpios) { + gpio = pdata->gpios; + while (gpio->name[0]) { + flags = FBTFT_GPIO_NO_MATCH; + /* if driver provides match function, try it first, + if no match use our own */ + if (par->fbtftops.request_gpios_match) + flags = par->fbtftops.request_gpios_match(par, gpio); + if (flags == FBTFT_GPIO_NO_MATCH) + flags = fbtft_request_gpios_match(par, gpio); + if (flags != FBTFT_GPIO_NO_MATCH) { + ret = devm_gpio_request_one(par->info->device, + gpio->gpio, flags, + par->info->device->driver->name); + if (ret < 0) { + dev_err(par->info->device, + "%s: gpio_request_one('%s'=%d) failed with %d\n", + __func__, gpio->name, + gpio->gpio, ret); + return ret; + } + fbtft_par_dbg(DEBUG_REQUEST_GPIOS, par, + "%s: '%s' = GPIO%d\n", + __func__, gpio->name, gpio->gpio); + } + gpio++; + } + } + + return 0; +} + +#ifdef CONFIG_OF +static int fbtft_request_one_gpio(struct fbtft_par *par, + const char *name, int index, int *gpiop) +{ + struct device *dev = par->info->device; + struct device_node *node = dev->of_node; + int gpio, flags, ret = 0; + enum of_gpio_flags of_flags; + + if (of_find_property(node, name, NULL)) { + gpio = of_get_named_gpio_flags(node, name, index, &of_flags); + if (gpio == -ENOENT) + return 0; + if (gpio == -EPROBE_DEFER) + return gpio; + if (gpio < 0) { + dev_err(dev, + "failed to get '%s' from DT\n", name); + return gpio; + } + + /* active low translates to initially low */ + flags = (of_flags & OF_GPIO_ACTIVE_LOW) ? GPIOF_OUT_INIT_LOW : + GPIOF_OUT_INIT_HIGH; + ret = devm_gpio_request_one(dev, gpio, flags, + dev->driver->name); + if (ret) { + dev_err(dev, + "gpio_request_one('%s'=%d) failed with %d\n", + name, gpio, ret); + return ret; + } + if (gpiop) + *gpiop = gpio; + fbtft_par_dbg(DEBUG_REQUEST_GPIOS, par, "%s: '%s' = GPIO%d\n", + __func__, name, gpio); + } + + return ret; +} + +static int fbtft_request_gpios_dt(struct fbtft_par *par) +{ + int i; + int ret; + + if (!par->info->device->of_node) + return -EINVAL; + + ret = fbtft_request_one_gpio(par, "reset-gpios", 0, &par->gpio.reset); + if (ret) + return ret; + ret = fbtft_request_one_gpio(par, "dc-gpios", 0, &par->gpio.dc); + if (ret) + return ret; + ret = fbtft_request_one_gpio(par, "rd-gpios", 0, &par->gpio.rd); + if (ret) + return ret; + ret = fbtft_request_one_gpio(par, "wr-gpios", 0, &par->gpio.wr); + if (ret) + return ret; + ret = fbtft_request_one_gpio(par, "cs-gpios", 0, &par->gpio.cs); + if (ret) + return ret; + ret = fbtft_request_one_gpio(par, "latch-gpios", 0, &par->gpio.latch); + if (ret) + return ret; + for (i = 0; i < 16; i++) { + ret = fbtft_request_one_gpio(par, "db-gpios", i, + &par->gpio.db[i]); + if (ret) + return ret; + ret = fbtft_request_one_gpio(par, "led-gpios", i, + &par->gpio.led[i]); + if (ret) + return ret; + ret = fbtft_request_one_gpio(par, "aux-gpios", i, + &par->gpio.aux[i]); + if (ret) + return ret; + } + + return 0; +} +#endif + +#ifdef CONFIG_FB_BACKLIGHT +int fbtft_backlight_update_status(struct backlight_device *bd) +{ + struct fbtft_par *par = bl_get_data(bd); + bool polarity = !!(bd->props.state & BL_CORE_DRIVER1); + + fbtft_par_dbg(DEBUG_BACKLIGHT, par, + "%s: polarity=%d, power=%d, fb_blank=%d\n", + __func__, polarity, bd->props.power, bd->props.fb_blank); + + if ((bd->props.power == FB_BLANK_UNBLANK) && (bd->props.fb_blank == FB_BLANK_UNBLANK)) + gpio_set_value(par->gpio.led[0], polarity); + else + gpio_set_value(par->gpio.led[0], !polarity); + + return 0; +} + +int fbtft_backlight_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +void fbtft_unregister_backlight(struct fbtft_par *par) +{ + const struct backlight_ops *bl_ops; + + fbtft_par_dbg(DEBUG_BACKLIGHT, par, "%s()\n", __func__); + + if (par->info->bl_dev) { + par->info->bl_dev->props.power = FB_BLANK_POWERDOWN; + backlight_update_status(par->info->bl_dev); + bl_ops = par->info->bl_dev->ops; + backlight_device_unregister(par->info->bl_dev); + par->info->bl_dev = NULL; + } +} + +void fbtft_register_backlight(struct fbtft_par *par) +{ + struct backlight_device *bd; + struct backlight_properties bl_props = { 0, }; + struct backlight_ops *bl_ops; + + fbtft_par_dbg(DEBUG_BACKLIGHT, par, "%s()\n", __func__); + + if (par->gpio.led[0] == -1) { + fbtft_par_dbg(DEBUG_BACKLIGHT, par, + "%s(): led pin not set, exiting.\n", __func__); + return; + } + + bl_ops = devm_kzalloc(par->info->device, sizeof(struct backlight_ops), + GFP_KERNEL); + if (!bl_ops) { + dev_err(par->info->device, + "%s: could not allocate memeory for backlight operations.\n", + __func__); + return; + } + + bl_ops->get_brightness = fbtft_backlight_get_brightness; + bl_ops->update_status = fbtft_backlight_update_status; + bl_props.type = BACKLIGHT_RAW; + /* Assume backlight is off, get polarity from current state of pin */ + bl_props.power = FB_BLANK_POWERDOWN; + if (!gpio_get_value(par->gpio.led[0])) + bl_props.state |= BL_CORE_DRIVER1; + + bd = backlight_device_register(dev_driver_string(par->info->device), + par->info->device, par, bl_ops, &bl_props); + if (IS_ERR(bd)) { + dev_err(par->info->device, + "cannot register backlight device (%ld)\n", + PTR_ERR(bd)); + return; + } + par->info->bl_dev = bd; + + if (!par->fbtftops.unregister_backlight) + par->fbtftops.unregister_backlight = fbtft_unregister_backlight; +} +#else +void fbtft_register_backlight(struct fbtft_par *par) { }; +void fbtft_unregister_backlight(struct fbtft_par *par) { }; +#endif +EXPORT_SYMBOL(fbtft_register_backlight); +EXPORT_SYMBOL(fbtft_unregister_backlight); + +void fbtft_set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + /* Column address set */ + write_reg(par, 0x2A, + (xs >> 8) & 0xFF, xs & 0xFF, (xe >> 8) & 0xFF, xe & 0xFF); + + /* Row adress set */ + write_reg(par, 0x2B, + (ys >> 8) & 0xFF, ys & 0xFF, (ye >> 8) & 0xFF, ye & 0xFF); + + /* Memory write */ + write_reg(par, 0x2C); +} + + +void fbtft_reset(struct fbtft_par *par) +{ + if (par->gpio.reset == -1) + return; + fbtft_par_dbg(DEBUG_RESET, par, "%s()\n", __func__); + gpio_set_value(par->gpio.reset, 0); + udelay(20); + gpio_set_value(par->gpio.reset, 1); + mdelay(120); +} + + +void fbtft_update_display(struct fbtft_par *par, unsigned start_line, unsigned end_line) +{ + size_t offset, len; + struct timespec ts_start, ts_end, ts_fps, ts_duration; + long fps_ms, fps_us, duration_ms, duration_us; + long fps, throughput; + bool timeit = false; + int ret = 0; + + if (unlikely(par->debug & (DEBUG_TIME_FIRST_UPDATE | DEBUG_TIME_EACH_UPDATE))) { + if ((par->debug & DEBUG_TIME_EACH_UPDATE) || \ + ((par->debug & DEBUG_TIME_FIRST_UPDATE) && !par->first_update_done)) { + getnstimeofday(&ts_start); + timeit = true; + } + } + + /* Sanity checks */ + if (start_line > end_line) { + dev_warn(par->info->device, + "%s: start_line=%u is larger than end_line=%u. Shouldn't happen, will do full display update\n", + __func__, start_line, end_line); + start_line = 0; + end_line = par->info->var.yres - 1; + } + if (start_line > par->info->var.yres - 1 || end_line > par->info->var.yres - 1) { + dev_warn(par->info->device, + "%s: start_line=%u or end_line=%u is larger than max=%d. Shouldn't happen, will do full display update\n", + __func__, start_line, end_line, par->info->var.yres - 1); + start_line = 0; + end_line = par->info->var.yres - 1; + } + + fbtft_par_dbg(DEBUG_UPDATE_DISPLAY, par, "%s(start_line=%u, end_line=%u)\n", + __func__, start_line, end_line); + + if (par->fbtftops.set_addr_win) + par->fbtftops.set_addr_win(par, 0, start_line, + par->info->var.xres-1, end_line); + + offset = start_line * par->info->fix.line_length; + len = (end_line - start_line + 1) * par->info->fix.line_length; + ret = par->fbtftops.write_vmem(par, offset, len); + if (ret < 0) + dev_err(par->info->device, + "%s: write_vmem failed to update display buffer\n", + __func__); + + if (unlikely(timeit)) { + getnstimeofday(&ts_end); + if (par->update_time.tv_nsec == 0 && par->update_time.tv_sec == 0) { + par->update_time.tv_sec = ts_start.tv_sec; + par->update_time.tv_nsec = ts_start.tv_nsec; + } + ts_fps = timespec_sub(ts_start, par->update_time); + par->update_time.tv_sec = ts_start.tv_sec; + par->update_time.tv_nsec = ts_start.tv_nsec; + fps_ms = (ts_fps.tv_sec * 1000) + ((ts_fps.tv_nsec / 1000000) % 1000); + fps_us = (ts_fps.tv_nsec / 1000) % 1000; + fps = fps_ms * 1000 + fps_us; + fps = fps ? 1000000 / fps : 0; + + ts_duration = timespec_sub(ts_end, ts_start); + duration_ms = (ts_duration.tv_sec * 1000) + ((ts_duration.tv_nsec / 1000000) % 1000); + duration_us = (ts_duration.tv_nsec / 1000) % 1000; + throughput = duration_ms * 1000 + duration_us; + throughput = throughput ? (len * 1000) / throughput : 0; + throughput = throughput * 1000 / 1024; + + dev_info(par->info->device, + "Display update: %ld kB/s (%ld.%.3ld ms), fps=%ld (%ld.%.3ld ms)\n", + throughput, duration_ms, duration_us, + fps, fps_ms, fps_us); + par->first_update_done = true; + } +} + + +void fbtft_mkdirty(struct fb_info *info, int y, int height) +{ + struct fbtft_par *par = info->par; + struct fb_deferred_io *fbdefio = info->fbdefio; + + /* special case, needed ? */ + if (y == -1) { + y = 0; + height = info->var.yres - 1; + } + + /* Mark display lines/area as dirty */ + spin_lock(&par->dirty_lock); + if (y < par->dirty_lines_start) + par->dirty_lines_start = y; + if (y + height - 1 > par->dirty_lines_end) + par->dirty_lines_end = y + height - 1; + spin_unlock(&par->dirty_lock); + + /* Schedule deferred_io to update display (no-op if already on queue)*/ + schedule_delayed_work(&info->deferred_work, fbdefio->delay); +} + +void fbtft_deferred_io(struct fb_info *info, struct list_head *pagelist) +{ + struct fbtft_par *par = info->par; + unsigned dirty_lines_start, dirty_lines_end; + struct page *page; + unsigned long index; + unsigned y_low = 0, y_high = 0; + int count = 0; + + spin_lock(&par->dirty_lock); + dirty_lines_start = par->dirty_lines_start; + dirty_lines_end = par->dirty_lines_end; + /* set display line markers as clean */ + par->dirty_lines_start = par->info->var.yres - 1; + par->dirty_lines_end = 0; + spin_unlock(&par->dirty_lock); + + /* Mark display lines as dirty */ + list_for_each_entry(page, pagelist, lru) { + count++; + index = page->index << PAGE_SHIFT; + y_low = index / info->fix.line_length; + y_high = (index + PAGE_SIZE - 1) / info->fix.line_length; + fbtft_dev_dbg(DEBUG_DEFERRED_IO, par, info->device, + "page->index=%lu y_low=%d y_high=%d\n", + page->index, y_low, y_high); + if (y_high > info->var.yres - 1) + y_high = info->var.yres - 1; + if (y_low < dirty_lines_start) + dirty_lines_start = y_low; + if (y_high > dirty_lines_end) + dirty_lines_end = y_high; + } + + par->fbtftops.update_display(info->par, + dirty_lines_start, dirty_lines_end); +} + + +void fbtft_fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct fbtft_par *par = info->par; + + fbtft_dev_dbg(DEBUG_FB_FILLRECT, par, info->dev, + "%s: dx=%d, dy=%d, width=%d, height=%d\n", + __func__, rect->dx, rect->dy, rect->width, rect->height); + sys_fillrect(info, rect); + + par->fbtftops.mkdirty(info, rect->dy, rect->height); +} + +void fbtft_fb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct fbtft_par *par = info->par; + + fbtft_dev_dbg(DEBUG_FB_COPYAREA, par, info->dev, + "%s: dx=%d, dy=%d, width=%d, height=%d\n", + __func__, area->dx, area->dy, area->width, area->height); + sys_copyarea(info, area); + + par->fbtftops.mkdirty(info, area->dy, area->height); +} + +void fbtft_fb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct fbtft_par *par = info->par; + + fbtft_dev_dbg(DEBUG_FB_IMAGEBLIT, par, info->dev, + "%s: dx=%d, dy=%d, width=%d, height=%d\n", + __func__, image->dx, image->dy, image->width, image->height); + sys_imageblit(info, image); + + par->fbtftops.mkdirty(info, image->dy, image->height); +} + +ssize_t fbtft_fb_write(struct fb_info *info, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct fbtft_par *par = info->par; + ssize_t res; + + fbtft_dev_dbg(DEBUG_FB_WRITE, par, info->dev, + "%s: count=%zd, ppos=%llu\n", __func__, count, *ppos); + res = fb_sys_write(info, buf, count, ppos); + + /* TODO: only mark changed area + update all for now */ + par->fbtftops.mkdirty(info, -1, 0); + + return res; +} + +/* from pxafb.c */ +unsigned int chan_to_field(unsigned chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +int fbtft_fb_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct fbtft_par *par = info->par; + unsigned val; + int ret = 1; + + fbtft_dev_dbg(DEBUG_FB_SETCOLREG, par, info->dev, + "%s(regno=%u, red=0x%X, green=0x%X, blue=0x%X, trans=0x%X)\n", + __func__, regno, red, green, blue, transp); + + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + val = chan_to_field(red, &info->var.red); + val |= chan_to_field(green, &info->var.green); + val |= chan_to_field(blue, &info->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + } + return ret; +} + +int fbtft_fb_blank(int blank, struct fb_info *info) +{ + struct fbtft_par *par = info->par; + int ret = -EINVAL; + + fbtft_dev_dbg(DEBUG_FB_BLANK, par, info->dev, "%s(blank=%d)\n", + __func__, blank); + + if (!par->fbtftops.blank) + return ret; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + ret = par->fbtftops.blank(par, true); + break; + case FB_BLANK_UNBLANK: + ret = par->fbtftops.blank(par, false); + break; + } + return ret; +} + +void fbtft_merge_fbtftops(struct fbtft_ops *dst, struct fbtft_ops *src) +{ + if (src->write) + dst->write = src->write; + if (src->read) + dst->read = src->read; + if (src->write_vmem) + dst->write_vmem = src->write_vmem; + if (src->write_register) + dst->write_register = src->write_register; + if (src->set_addr_win) + dst->set_addr_win = src->set_addr_win; + if (src->reset) + dst->reset = src->reset; + if (src->mkdirty) + dst->mkdirty = src->mkdirty; + if (src->update_display) + dst->update_display = src->update_display; + if (src->init_display) + dst->init_display = src->init_display; + if (src->blank) + dst->blank = src->blank; + if (src->request_gpios_match) + dst->request_gpios_match = src->request_gpios_match; + if (src->request_gpios) + dst->request_gpios = src->request_gpios; + if (src->verify_gpios) + dst->verify_gpios = src->verify_gpios; + if (src->register_backlight) + dst->register_backlight = src->register_backlight; + if (src->unregister_backlight) + dst->unregister_backlight = src->unregister_backlight; + if (src->set_var) + dst->set_var = src->set_var; + if (src->set_gamma) + dst->set_gamma = src->set_gamma; +} + +/** + * fbtft_framebuffer_alloc - creates a new frame buffer info structure + * + * @display: pointer to structure describing the display + * @dev: pointer to the device for this fb, this can be NULL + * + * Creates a new frame buffer info structure. + * + * Also creates and populates the following structures: + * info->fbops + * info->fbdefio + * info->pseudo_palette + * par->fbtftops + * par->txbuf + * + * Returns the new structure, or NULL if an error occurred. + * + */ +struct fb_info *fbtft_framebuffer_alloc(struct fbtft_display *display, + struct device *dev) +{ + struct fb_info *info; + struct fbtft_par *par; + struct fb_ops *fbops = NULL; + struct fb_deferred_io *fbdefio = NULL; + struct fbtft_platform_data *pdata = dev->platform_data; + u8 *vmem = NULL; + void *txbuf = NULL; + void *buf = NULL; + unsigned width; + unsigned height; + int txbuflen = display->txbuflen; + unsigned bpp = display->bpp; + unsigned fps = display->fps; + int vmem_size, i; + int *init_sequence = display->init_sequence; + char *gamma = display->gamma; + unsigned long *gamma_curves = NULL; + + /* sanity check */ + if (display->gamma_num * display->gamma_len > FBTFT_GAMMA_MAX_VALUES_TOTAL) { + dev_err(dev, + "%s: FBTFT_GAMMA_MAX_VALUES_TOTAL=%d is exceeded\n", + __func__, FBTFT_GAMMA_MAX_VALUES_TOTAL); + return NULL; + } + + /* defaults */ + if (!fps) + fps = 20; + if (!bpp) + bpp = 16; + + if (!pdata) { + dev_err(dev, "platform data is missing\n"); + return NULL; + } + + /* override driver values? */ + if (pdata->fps) + fps = pdata->fps; + if (pdata->txbuflen) + txbuflen = pdata->txbuflen; + if (pdata->display.init_sequence) + init_sequence = pdata->display.init_sequence; + if (pdata->gamma) + gamma = pdata->gamma; + if (pdata->display.debug) + display->debug = pdata->display.debug; + if (pdata->display.backlight) + display->backlight = pdata->display.backlight; + if (pdata->display.width) + display->width = pdata->display.width; + if (pdata->display.height) + display->height = pdata->display.height; + if (pdata->display.buswidth) + display->buswidth = pdata->display.buswidth; + if (pdata->display.regwidth) + display->regwidth = pdata->display.regwidth; + + display->debug |= debug; + fbtft_expand_debug_value(&display->debug); + + switch (pdata->rotate) { + case 90: + case 270: + width = display->height; + height = display->width; + break; + default: + width = display->width; + height = display->height; + } + + vmem_size = display->width * display->height * bpp / 8; + vmem = vzalloc(vmem_size); + if (!vmem) + goto alloc_fail; + + fbops = devm_kzalloc(dev, sizeof(struct fb_ops), GFP_KERNEL); + if (!fbops) + goto alloc_fail; + + fbdefio = devm_kzalloc(dev, sizeof(struct fb_deferred_io), GFP_KERNEL); + if (!fbdefio) + goto alloc_fail; + + buf = devm_kzalloc(dev, 128, GFP_KERNEL); + if (!buf) + goto alloc_fail; + + if (display->gamma_num && display->gamma_len) { + gamma_curves = devm_kzalloc(dev, display->gamma_num * display->gamma_len * sizeof(gamma_curves[0]), + GFP_KERNEL); + if (!gamma_curves) + goto alloc_fail; + } + + info = framebuffer_alloc(sizeof(struct fbtft_par), dev); + if (!info) + goto alloc_fail; + + info->screen_base = (u8 __force __iomem *)vmem; + info->fbops = fbops; + info->fbdefio = fbdefio; + + fbops->owner = dev->driver->owner; + fbops->fb_read = fb_sys_read; + fbops->fb_write = fbtft_fb_write; + fbops->fb_fillrect = fbtft_fb_fillrect; + fbops->fb_copyarea = fbtft_fb_copyarea; + fbops->fb_imageblit = fbtft_fb_imageblit; + fbops->fb_setcolreg = fbtft_fb_setcolreg; + fbops->fb_blank = fbtft_fb_blank; + + fbdefio->delay = HZ/fps; + fbdefio->deferred_io = fbtft_deferred_io; + fb_deferred_io_init(info); + + strncpy(info->fix.id, dev->driver->name, 16); + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.xpanstep = 0; + info->fix.ypanstep = 0; + info->fix.ywrapstep = 0; + info->fix.line_length = width*bpp/8; + info->fix.accel = FB_ACCEL_NONE; + info->fix.smem_len = vmem_size; + + info->var.rotate = pdata->rotate; + info->var.xres = width; + info->var.yres = height; + info->var.xres_virtual = info->var.xres; + info->var.yres_virtual = info->var.yres; + info->var.bits_per_pixel = bpp; + info->var.nonstd = 1; + + /* RGB565 */ + info->var.red.offset = 11; + info->var.red.length = 5; + info->var.green.offset = 5; + info->var.green.length = 6; + info->var.blue.offset = 0; + info->var.blue.length = 5; + info->var.transp.offset = 0; + info->var.transp.length = 0; + + info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB; + + par = info->par; + par->info = info; + par->pdata = dev->platform_data; + par->debug = display->debug; + par->buf = buf; + spin_lock_init(&par->dirty_lock); + par->bgr = pdata->bgr; + par->startbyte = pdata->startbyte; + par->init_sequence = init_sequence; + par->gamma.curves = gamma_curves; + par->gamma.num_curves = display->gamma_num; + par->gamma.num_values = display->gamma_len; + mutex_init(&par->gamma.lock); + info->pseudo_palette = par->pseudo_palette; + + if (par->gamma.curves && gamma) { + if (fbtft_gamma_parse_str(par, + par->gamma.curves, gamma, strlen(gamma))) + goto alloc_fail; + } + + /* Transmit buffer */ + if (txbuflen == -1) + txbuflen = vmem_size + 2; /* add in case startbyte is used */ + +#ifdef __LITTLE_ENDIAN + if ((!txbuflen) && (bpp > 8)) + txbuflen = PAGE_SIZE; /* need buffer for byteswapping */ +#endif + + if (txbuflen > 0) { + if (dma) { + dev->coherent_dma_mask = ~0; + txbuf = dmam_alloc_coherent(dev, txbuflen, &par->txbuf.dma, GFP_DMA); + } else { + txbuf = devm_kzalloc(par->info->device, txbuflen, GFP_KERNEL); + } + if (!txbuf) + goto alloc_fail; + par->txbuf.buf = txbuf; + par->txbuf.len = txbuflen; + } + + /* Initialize gpios to disabled */ + par->gpio.reset = -1; + par->gpio.dc = -1; + par->gpio.rd = -1; + par->gpio.wr = -1; + par->gpio.cs = -1; + par->gpio.latch = -1; + for (i = 0; i < 16; i++) { + par->gpio.db[i] = -1; + par->gpio.led[i] = -1; + par->gpio.aux[i] = -1; + } + + /* default fbtft operations */ + par->fbtftops.write = fbtft_write_spi; + par->fbtftops.read = fbtft_read_spi; + par->fbtftops.write_vmem = fbtft_write_vmem16_bus8; + par->fbtftops.write_register = fbtft_write_reg8_bus8; + par->fbtftops.set_addr_win = fbtft_set_addr_win; + par->fbtftops.reset = fbtft_reset; + par->fbtftops.mkdirty = fbtft_mkdirty; + par->fbtftops.update_display = fbtft_update_display; + par->fbtftops.request_gpios = fbtft_request_gpios; + if (display->backlight) + par->fbtftops.register_backlight = fbtft_register_backlight; + + /* use driver provided functions */ + fbtft_merge_fbtftops(&par->fbtftops, &display->fbtftops); + + return info; + +alloc_fail: + vfree(vmem); + + return NULL; +} +EXPORT_SYMBOL(fbtft_framebuffer_alloc); + +/** + * fbtft_framebuffer_release - frees up all memory used by the framebuffer + * + * @info: frame buffer info structure + * + */ +void fbtft_framebuffer_release(struct fb_info *info) +{ + fb_deferred_io_cleanup(info); + vfree(info->screen_base); + framebuffer_release(info); +} +EXPORT_SYMBOL(fbtft_framebuffer_release); + +/** + * fbtft_register_framebuffer - registers a tft frame buffer device + * @fb_info: frame buffer info structure + * + * Sets SPI driverdata if needed + * Requests needed gpios. + * Initializes display + * Updates display. + * Registers a frame buffer device @fb_info. + * + * Returns negative errno on error, or zero for success. + * + */ +int fbtft_register_framebuffer(struct fb_info *fb_info) +{ + int ret; + char text1[50] = ""; + char text2[50] = ""; + struct fbtft_par *par = fb_info->par; + struct spi_device *spi = par->spi; + + /* sanity checks */ + if (!par->fbtftops.init_display) { + dev_err(fb_info->device, "missing fbtftops.init_display()\n"); + return -EINVAL; + } + + if (spi) + spi_set_drvdata(spi, fb_info); + if (par->pdev) + platform_set_drvdata(par->pdev, fb_info); + + ret = par->fbtftops.request_gpios(par); + if (ret < 0) + goto reg_fail; + + if (par->fbtftops.verify_gpios) { + ret = par->fbtftops.verify_gpios(par); + if (ret < 0) + goto reg_fail; + } + + ret = par->fbtftops.init_display(par); + if (ret < 0) + goto reg_fail; + if (par->fbtftops.set_var) { + ret = par->fbtftops.set_var(par); + if (ret < 0) + goto reg_fail; + } + + /* update the entire display */ + par->fbtftops.update_display(par, 0, par->info->var.yres - 1); + + if (par->fbtftops.set_gamma && par->gamma.curves) { + ret = par->fbtftops.set_gamma(par, par->gamma.curves); + if (ret) + goto reg_fail; + } + + if (par->fbtftops.register_backlight) + par->fbtftops.register_backlight(par); + + ret = register_framebuffer(fb_info); + if (ret < 0) + goto reg_fail; + + fbtft_sysfs_init(par); + + if (par->txbuf.buf) + sprintf(text1, ", %d KiB %sbuffer memory", + par->txbuf.len >> 10, par->txbuf.dma ? "DMA " : ""); + if (spi) + sprintf(text2, ", spi%d.%d at %d MHz", spi->master->bus_num, + spi->chip_select, spi->max_speed_hz/1000000); + dev_info(fb_info->dev, + "%s frame buffer, %dx%d, %d KiB video memory%s, fps=%lu%s\n", + fb_info->fix.id, fb_info->var.xres, fb_info->var.yres, + fb_info->fix.smem_len >> 10, text1, + HZ/fb_info->fbdefio->delay, text2); + +#ifdef CONFIG_FB_BACKLIGHT + /* Turn on backlight if available */ + if (fb_info->bl_dev) { + fb_info->bl_dev->props.power = FB_BLANK_UNBLANK; + fb_info->bl_dev->ops->update_status(fb_info->bl_dev); + } +#endif + + return 0; + +reg_fail: + if (par->fbtftops.unregister_backlight) + par->fbtftops.unregister_backlight(par); + if (spi) + spi_set_drvdata(spi, NULL); + if (par->pdev) + platform_set_drvdata(par->pdev, NULL); + + return ret; +} +EXPORT_SYMBOL(fbtft_register_framebuffer); + +/** + * fbtft_unregister_framebuffer - releases a tft frame buffer device + * @fb_info: frame buffer info structure + * + * Frees SPI driverdata if needed + * Frees gpios. + * Unregisters frame buffer device. + * + */ +int fbtft_unregister_framebuffer(struct fb_info *fb_info) +{ + struct fbtft_par *par = fb_info->par; + struct spi_device *spi = par->spi; + int ret; + + if (spi) + spi_set_drvdata(spi, NULL); + if (par->pdev) + platform_set_drvdata(par->pdev, NULL); + if (par->fbtftops.unregister_backlight) + par->fbtftops.unregister_backlight(par); + fbtft_sysfs_exit(par); + ret = unregister_framebuffer(fb_info); + return ret; +} +EXPORT_SYMBOL(fbtft_unregister_framebuffer); + +#ifdef CONFIG_OF +/** + * fbtft_init_display_dt() - Device Tree init_display() function + * @par: Driver data + * + * Return: 0 if successful, negative if error + */ +static int fbtft_init_display_dt(struct fbtft_par *par) +{ + struct device_node *node = par->info->device->of_node; + struct property *prop; + const __be32 *p; + u32 val; + int buf[64], i, j; + char msg[128]; + char str[16]; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + if (!node) + return -EINVAL; + + prop = of_find_property(node, "init", NULL); + p = of_prop_next_u32(prop, NULL, &val); + if (!p) + return -EINVAL; + while (p) { + if (val & FBTFT_OF_INIT_CMD) { + val &= 0xFFFF; + i = 0; + while (p && !(val & 0xFFFF0000)) { + if (i > 63) { + dev_err(par->info->device, + "%s: Maximum register values exceeded\n", + __func__); + return -EINVAL; + } + buf[i++] = val; + p = of_prop_next_u32(prop, p, &val); + } + /* make debug message */ + msg[0] = '\0'; + for (j = 0; j < i; j++) { + snprintf(str, 128, " %02X", buf[j]); + strcat(msg, str); + } + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, + "init: write_register:%s\n", msg); + + par->fbtftops.write_register(par, i, + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7], + buf[8], buf[9], buf[10], buf[11], + buf[12], buf[13], buf[14], buf[15], + buf[16], buf[17], buf[18], buf[19], + buf[20], buf[21], buf[22], buf[23], + buf[24], buf[25], buf[26], buf[27], + buf[28], buf[29], buf[30], buf[31], + buf[32], buf[33], buf[34], buf[35], + buf[36], buf[37], buf[38], buf[39], + buf[40], buf[41], buf[42], buf[43], + buf[44], buf[45], buf[46], buf[47], + buf[48], buf[49], buf[50], buf[51], + buf[52], buf[53], buf[54], buf[55], + buf[56], buf[57], buf[58], buf[59], + buf[60], buf[61], buf[62], buf[63]); + } else if (val & FBTFT_OF_INIT_DELAY) { + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, + "init: msleep(%u)\n", val & 0xFFFF); + msleep(val & 0xFFFF); + p = of_prop_next_u32(prop, p, &val); + } else { + dev_err(par->info->device, "illegal init value 0x%X\n", + val); + return -EINVAL; + } + } + + return 0; +} +#endif + +/** + * fbtft_init_display() - Generic init_display() function + * @par: Driver data + * + * Uses par->init_sequence to do the initialization + * + * Return: 0 if successful, negative if error + */ +int fbtft_init_display(struct fbtft_par *par) +{ + int buf[64]; + char msg[128]; + char str[16]; + int i = 0; + int j; + + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); + + /* sanity check */ + if (!par->init_sequence) { + dev_err(par->info->device, + "error: init_sequence is not set\n"); + return -EINVAL; + } + + /* make sure stop marker exists */ + for (i = 0; i < FBTFT_MAX_INIT_SEQUENCE; i++) + if (par->init_sequence[i] == -3) + break; + if (i == FBTFT_MAX_INIT_SEQUENCE) { + dev_err(par->info->device, + "missing stop marker at end of init sequence\n"); + return -EINVAL; + } + + par->fbtftops.reset(par); + if (par->gpio.cs != -1) + gpio_set_value(par->gpio.cs, 0); /* Activate chip */ + + i = 0; + while (i < FBTFT_MAX_INIT_SEQUENCE) { + if (par->init_sequence[i] == -3) { + /* done */ + return 0; + } + if (par->init_sequence[i] >= 0) { + dev_err(par->info->device, + "missing delimiter at position %d\n", i); + return -EINVAL; + } + if (par->init_sequence[i+1] < 0) { + dev_err(par->info->device, + "missing value after delimiter %d at position %d\n", + par->init_sequence[i], i); + return -EINVAL; + } + switch (par->init_sequence[i]) { + case -1: + i++; + /* make debug message */ + strcpy(msg, ""); + j = i + 1; + while (par->init_sequence[j] >= 0) { + sprintf(str, "0x%02X ", par->init_sequence[j]); + strcat(msg, str); + j++; + } + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, + "init: write(0x%02X) %s\n", + par->init_sequence[i], msg); + + /* Write */ + j = 0; + while (par->init_sequence[i] >= 0) { + if (j > 63) { + dev_err(par->info->device, + "%s: Maximum register values exceeded\n", + __func__); + return -EINVAL; + } + buf[j++] = par->init_sequence[i++]; + } + par->fbtftops.write_register(par, j, + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7], + buf[8], buf[9], buf[10], buf[11], + buf[12], buf[13], buf[14], buf[15], + buf[16], buf[17], buf[18], buf[19], + buf[20], buf[21], buf[22], buf[23], + buf[24], buf[25], buf[26], buf[27], + buf[28], buf[29], buf[30], buf[31], + buf[32], buf[33], buf[34], buf[35], + buf[36], buf[37], buf[38], buf[39], + buf[40], buf[41], buf[42], buf[43], + buf[44], buf[45], buf[46], buf[47], + buf[48], buf[49], buf[50], buf[51], + buf[52], buf[53], buf[54], buf[55], + buf[56], buf[57], buf[58], buf[59], + buf[60], buf[61], buf[62], buf[63]); + break; + case -2: + i++; + fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, + "init: mdelay(%d)\n", par->init_sequence[i]); + mdelay(par->init_sequence[i++]); + break; + default: + dev_err(par->info->device, + "unknown delimiter %d at position %d\n", + par->init_sequence[i], i); + return -EINVAL; + } + } + + dev_err(par->info->device, + "%s: something is wrong. Shouldn't get here.\n", __func__); + return -EINVAL; +} +EXPORT_SYMBOL(fbtft_init_display); + +/** + * fbtft_verify_gpios() - Generic verify_gpios() function + * @par: Driver data + * + * Uses @spi, @pdev and @buswidth to determine which GPIOs is needed + * + * Return: 0 if successful, negative if error + */ +int fbtft_verify_gpios(struct fbtft_par *par) +{ + struct fbtft_platform_data *pdata; + int i; + + fbtft_par_dbg(DEBUG_VERIFY_GPIOS, par, "%s()\n", __func__); + + pdata = par->info->device->platform_data; + if (pdata->display.buswidth != 9 && par->startbyte == 0 && \ + par->gpio.dc < 0) { + dev_err(par->info->device, + "Missing info about 'dc' gpio. Aborting.\n"); + return -EINVAL; + } + + if (!par->pdev) + return 0; + + if (par->gpio.wr < 0) { + dev_err(par->info->device, "Missing 'wr' gpio. Aborting.\n"); + return -EINVAL; + } + for (i = 0; i < pdata->display.buswidth; i++) { + if (par->gpio.db[i] < 0) { + dev_err(par->info->device, + "Missing 'db%02d' gpio. Aborting.\n", i); + return -EINVAL; + } + } + + return 0; +} + +#ifdef CONFIG_OF +/* returns 0 if the property is not present */ +static u32 fbtft_of_value(struct device_node *node, const char *propname) +{ + int ret; + u32 val = 0; + + ret = of_property_read_u32(node, propname, &val); + if (ret == 0) + pr_info("%s: %s = %u\n", __func__, propname, val); + + return val; +} + +static struct fbtft_platform_data *fbtft_probe_dt(struct device *dev) +{ + struct device_node *node = dev->of_node; + struct fbtft_platform_data *pdata; + + if (!node) { + dev_err(dev, "Missing platform data or DT\n"); + return ERR_PTR(-EINVAL); + } + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->display.width = fbtft_of_value(node, "width"); + pdata->display.height = fbtft_of_value(node, "height"); + pdata->display.regwidth = fbtft_of_value(node, "regwidth"); + pdata->display.buswidth = fbtft_of_value(node, "buswidth"); + pdata->display.backlight = fbtft_of_value(node, "backlight"); + pdata->display.bpp = fbtft_of_value(node, "bpp"); + pdata->display.debug = fbtft_of_value(node, "debug"); + pdata->rotate = fbtft_of_value(node, "rotate"); + pdata->bgr = of_property_read_bool(node, "bgr"); + pdata->fps = fbtft_of_value(node, "fps"); + pdata->txbuflen = fbtft_of_value(node, "txbuflen"); + pdata->startbyte = fbtft_of_value(node, "startbyte"); + of_property_read_string(node, "gamma", (const char **)&pdata->gamma); + + if (of_find_property(node, "led-gpios", NULL)) + pdata->display.backlight = 1; + if (of_find_property(node, "init", NULL)) + pdata->display.fbtftops.init_display = fbtft_init_display_dt; + pdata->display.fbtftops.request_gpios = fbtft_request_gpios_dt; + + return pdata; +} +#else +static struct fbtft_platform_data *fbtft_probe_dt(struct device *dev) +{ + dev_err(dev, "Missing platform data\n"); + return ERR_PTR(-EINVAL); +} +#endif + +/** + * fbtft_probe_common() - Generic device probe() helper function + * @display: Display properties + * @sdev: SPI device + * @pdev: Platform device + * + * Allocates, initializes and registers a framebuffer + * + * Either @sdev or @pdev should be NULL + * + * Return: 0 if successful, negative if error + */ +int fbtft_probe_common(struct fbtft_display *display, + struct spi_device *sdev, struct platform_device *pdev) +{ + struct device *dev; + struct fb_info *info; + struct fbtft_par *par; + struct fbtft_platform_data *pdata; + int ret; + + if (sdev) + dev = &sdev->dev; + else + dev = &pdev->dev; + + if (unlikely(display->debug & DEBUG_DRIVER_INIT_FUNCTIONS)) + dev_info(dev, "%s()\n", __func__); + + pdata = dev->platform_data; + if (!pdata) { + pdata = fbtft_probe_dt(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + dev->platform_data = pdata; + } + + info = fbtft_framebuffer_alloc(display, dev); + if (!info) + return -ENOMEM; + + par = info->par; + par->spi = sdev; + par->pdev = pdev; + + if (display->buswidth == 0) { + dev_err(dev, "buswidth is not set\n"); + return -EINVAL; + } + + /* write register functions */ + if (display->regwidth == 8 && display->buswidth == 8) { + par->fbtftops.write_register = fbtft_write_reg8_bus8; + } else + if (display->regwidth == 8 && display->buswidth == 9 && par->spi) { + par->fbtftops.write_register = fbtft_write_reg8_bus9; + } else if (display->regwidth == 16 && display->buswidth == 8) { + par->fbtftops.write_register = fbtft_write_reg16_bus8; + } else if (display->regwidth == 16 && display->buswidth == 16) { + par->fbtftops.write_register = fbtft_write_reg16_bus16; + } else { + dev_warn(dev, + "no default functions for regwidth=%d and buswidth=%d\n", + display->regwidth, display->buswidth); + } + + /* write_vmem() functions */ + if (display->buswidth == 8) + par->fbtftops.write_vmem = fbtft_write_vmem16_bus8; + else if (display->buswidth == 9) + par->fbtftops.write_vmem = fbtft_write_vmem16_bus9; + else if (display->buswidth == 16) + par->fbtftops.write_vmem = fbtft_write_vmem16_bus16; + + /* GPIO write() functions */ + if (par->pdev) { + if (display->buswidth == 8) + par->fbtftops.write = fbtft_write_gpio8_wr; + else if (display->buswidth == 16) + par->fbtftops.write = fbtft_write_gpio16_wr; + } + + /* 9-bit SPI setup */ + if (par->spi && display->buswidth == 9) { + par->spi->bits_per_word = 9; + ret = par->spi->master->setup(par->spi); + if (ret) { + dev_warn(&par->spi->dev, + "9-bit SPI not available, emulating using 8-bit.\n"); + par->spi->bits_per_word = 8; + ret = par->spi->master->setup(par->spi); + if (ret) + goto out_release; + /* allocate buffer with room for dc bits */ + par->extra = devm_kzalloc(par->info->device, + par->txbuf.len + (par->txbuf.len / 8) + 8, + GFP_KERNEL); + if (!par->extra) { + ret = -ENOMEM; + goto out_release; + } + par->fbtftops.write = fbtft_write_spi_emulate_9; + } + } + + if (!par->fbtftops.verify_gpios) + par->fbtftops.verify_gpios = fbtft_verify_gpios; + + /* make sure we still use the driver provided functions */ + fbtft_merge_fbtftops(&par->fbtftops, &display->fbtftops); + + /* use init_sequence if provided */ + if (par->init_sequence) + par->fbtftops.init_display = fbtft_init_display; + + /* use platform_data provided functions above all */ + fbtft_merge_fbtftops(&par->fbtftops, &pdata->display.fbtftops); + + ret = fbtft_register_framebuffer(info); + if (ret < 0) + goto out_release; + + return 0; + +out_release: + fbtft_framebuffer_release(info); + + return ret; +} +EXPORT_SYMBOL(fbtft_probe_common); + +/** + * fbtft_remove_common() - Generic device remove() helper function + * @dev: Device + * @info: Framebuffer + * + * Unregisters and releases the framebuffer + * + * Return: 0 if successful, negative if error + */ +int fbtft_remove_common(struct device *dev, struct fb_info *info) +{ + struct fbtft_par *par; + + if (!info) + return -EINVAL; + par = info->par; + if (par) + fbtft_par_dbg(DEBUG_DRIVER_INIT_FUNCTIONS, par, + "%s()\n", __func__); + fbtft_unregister_framebuffer(info); + fbtft_framebuffer_release(info); + + return 0; +} +EXPORT_SYMBOL(fbtft_remove_common); + +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/fbtft-io.c b/drivers/video/fbtft/fbtft-io.c new file mode 100644 index 0000000..dfa2c46 --- /dev/null +++ b/drivers/video/fbtft/fbtft-io.c @@ -0,0 +1,409 @@ +#include +#include +#include +#include +#ifdef CONFIG_ARCH_BCM2708 +#include +#endif +#include "fbtft.h" + +int fbtft_write_spi(struct fbtft_par *par, void *buf, size_t len) +{ + struct spi_transfer t = { + .tx_buf = buf, + .len = len, + }; + struct spi_message m; + + fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, + "%s(len=%d): ", __func__, len); + + if (!par->spi) { + dev_err(par->info->device, + "%s: par->spi is unexpectedly NULL\n", __func__); + return -1; + } + + spi_message_init(&m); + if (par->txbuf.dma && buf == par->txbuf.buf) { + t.tx_dma = par->txbuf.dma; + m.is_dma_mapped = 1; + } + spi_message_add_tail(&t, &m); + return spi_sync(par->spi, &m); +} +EXPORT_SYMBOL(fbtft_write_spi); + +/** + * fbtft_write_spi_emulate_9() - write SPI emulating 9-bit + * @par: Driver data + * @buf: Buffer to write + * @len: Length of buffer (must be divisible by 8) + * + * When 9-bit SPI is not available, this function can be used to emulate that. + * par->extra must hold a transformation buffer used for transfer. + */ +int fbtft_write_spi_emulate_9(struct fbtft_par *par, void *buf, size_t len) +{ + u16 *src = buf; + u8 *dst = par->extra; + size_t size = len / 2; + size_t added = 0; + int bits, i, j; + u64 val, dc, tmp; + + fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, + "%s(len=%d): ", __func__, len); + + if (!par->extra) { + dev_err(par->info->device, "%s: error: par->extra is NULL\n", + __func__); + return -EINVAL; + } + if ((len % 8) != 0) { + dev_err(par->info->device, + "%s: error: len=%d must be divisible by 8\n", + __func__, len); + return -EINVAL; + } + + for (i = 0; i < size; i += 8) { + tmp = 0; + bits = 63; + for (j = 0; j < 7; j++) { + dc = (*src & 0x0100) ? 1 : 0; + val = *src & 0x00FF; + tmp |= dc << bits; + bits -= 8; + tmp |= val << bits--; + src++; + } + tmp |= ((*src & 0x0100) ? 1 : 0); + *(u64 *)dst = cpu_to_be64(tmp); + dst += 8; + *dst++ = (u8)(*src++ & 0x00FF); + added++; + } + + return spi_write(par->spi, par->extra, size + added); +} +EXPORT_SYMBOL(fbtft_write_spi_emulate_9); + +int fbtft_read_spi(struct fbtft_par *par, void *buf, size_t len) +{ + int ret; + u8 txbuf[32] = { 0, }; + struct spi_transfer t = { + .speed_hz = 2000000, + .rx_buf = buf, + .len = len, + }; + struct spi_message m; + + if (!par->spi) { + dev_err(par->info->device, + "%s: par->spi is unexpectedly NULL\n", __func__); + return -ENODEV; + } + + if (par->startbyte) { + if (len > 32) { + dev_err(par->info->device, + "%s: len=%d can't be larger than 32 when using 'startbyte'\n", + __func__, len); + return -EINVAL; + } + txbuf[0] = par->startbyte | 0x3; + t.tx_buf = txbuf; + fbtft_par_dbg_hex(DEBUG_READ, par, par->info->device, u8, + txbuf, len, "%s(len=%d) txbuf => ", __func__, len); + } + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(par->spi, &m); + fbtft_par_dbg_hex(DEBUG_READ, par, par->info->device, u8, buf, len, + "%s(len=%d) buf <= ", __func__, len); + + return ret; +} +EXPORT_SYMBOL(fbtft_read_spi); + + +#ifdef CONFIG_ARCH_BCM2708 + +/* + * Raspberry Pi + * - writing directly to the registers is 40-50% faster than + * optimized use of gpiolib + */ + +#define GPIOSET(no, ishigh) \ +do { \ + if (ishigh) \ + set |= (1 << (no)); \ + else \ + reset |= (1 << (no)); \ +} while (0) + +int fbtft_write_gpio8_wr(struct fbtft_par *par, void *buf, size_t len) +{ + unsigned int set = 0; + unsigned int reset = 0; + u8 data; + + fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, + "%s(len=%d): ", __func__, len); + + while (len--) { + data = *(u8 *) buf; + buf++; + + /* Set data */ + GPIOSET(par->gpio.db[0], (data&0x01)); + GPIOSET(par->gpio.db[1], (data&0x02)); + GPIOSET(par->gpio.db[2], (data&0x04)); + GPIOSET(par->gpio.db[3], (data&0x08)); + GPIOSET(par->gpio.db[4], (data&0x10)); + GPIOSET(par->gpio.db[5], (data&0x20)); + GPIOSET(par->gpio.db[6], (data&0x40)); + GPIOSET(par->gpio.db[7], (data&0x80)); + writel(set, __io_address(GPIO_BASE+0x1C)); + writel(reset, __io_address(GPIO_BASE+0x28)); + + /* Pulse /WR low */ + writel((1<gpio.wr), __io_address(GPIO_BASE+0x28)); + writel(0, __io_address(GPIO_BASE+0x28)); /* used as a delay */ + writel((1<gpio.wr), __io_address(GPIO_BASE+0x1C)); + + set = 0; + reset = 0; + } + + return 0; +} +EXPORT_SYMBOL(fbtft_write_gpio8_wr); + +int fbtft_write_gpio16_wr(struct fbtft_par *par, void *buf, size_t len) +{ + unsigned int set = 0; + unsigned int reset = 0; + u16 data; + + fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, + "%s(len=%d): ", __func__, len); + + while (len) { + len -= 2; + data = *(u16 *) buf; + buf += 2; + + /* Start writing by pulling down /WR */ + gpio_set_value(par->gpio.wr, 0); + + /* Set data */ + GPIOSET(par->gpio.db[0], (data&0x0001)); + GPIOSET(par->gpio.db[1], (data&0x0002)); + GPIOSET(par->gpio.db[2], (data&0x0004)); + GPIOSET(par->gpio.db[3], (data&0x0008)); + GPIOSET(par->gpio.db[4], (data&0x0010)); + GPIOSET(par->gpio.db[5], (data&0x0020)); + GPIOSET(par->gpio.db[6], (data&0x0040)); + GPIOSET(par->gpio.db[7], (data&0x0080)); + + GPIOSET(par->gpio.db[8], (data&0x0100)); + GPIOSET(par->gpio.db[9], (data&0x0200)); + GPIOSET(par->gpio.db[10], (data&0x0400)); + GPIOSET(par->gpio.db[11], (data&0x0800)); + GPIOSET(par->gpio.db[12], (data&0x1000)); + GPIOSET(par->gpio.db[13], (data&0x2000)); + GPIOSET(par->gpio.db[14], (data&0x4000)); + GPIOSET(par->gpio.db[15], (data&0x8000)); + + writel(set, __io_address(GPIO_BASE+0x1C)); + writel(reset, __io_address(GPIO_BASE+0x28)); + + /* Pullup /WR */ + gpio_set_value(par->gpio.wr, 1); + + set = 0; + reset = 0; + } + + return 0; +} +EXPORT_SYMBOL(fbtft_write_gpio16_wr); + +int fbtft_write_gpio16_wr_latched(struct fbtft_par *par, void *buf, size_t len) +{ + unsigned int set = 0; + unsigned int reset = 0; + u16 data; + + fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, + "%s(len=%d): ", __func__, len); + + while (len) { + len -= 2; + data = *(u16 *) buf; + buf += 2; + + /* Start writing by pulling down /WR */ + gpio_set_value(par->gpio.wr, 0); + + /* Low byte */ + GPIOSET(par->gpio.db[0], (data&0x0001)); + GPIOSET(par->gpio.db[1], (data&0x0002)); + GPIOSET(par->gpio.db[2], (data&0x0004)); + GPIOSET(par->gpio.db[3], (data&0x0008)); + GPIOSET(par->gpio.db[4], (data&0x0010)); + GPIOSET(par->gpio.db[5], (data&0x0020)); + GPIOSET(par->gpio.db[6], (data&0x0040)); + GPIOSET(par->gpio.db[7], (data&0x0080)); + writel(set, __io_address(GPIO_BASE+0x1C)); + writel(reset, __io_address(GPIO_BASE+0x28)); + + /* Pulse 'latch' high */ + gpio_set_value(par->gpio.latch, 1); + gpio_set_value(par->gpio.latch, 0); + + /* High byte */ + GPIOSET(par->gpio.db[0], (data&0x0100)); + GPIOSET(par->gpio.db[1], (data&0x0200)); + GPIOSET(par->gpio.db[2], (data&0x0400)); + GPIOSET(par->gpio.db[3], (data&0x0800)); + GPIOSET(par->gpio.db[4], (data&0x1000)); + GPIOSET(par->gpio.db[5], (data&0x2000)); + GPIOSET(par->gpio.db[6], (data&0x4000)); + GPIOSET(par->gpio.db[7], (data&0x8000)); + writel(set, __io_address(GPIO_BASE+0x1C)); + writel(reset, __io_address(GPIO_BASE+0x28)); + + /* Pullup /WR */ + gpio_set_value(par->gpio.wr, 1); + + set = 0; + reset = 0; + } + + return 0; +} +EXPORT_SYMBOL(fbtft_write_gpio16_wr_latched); + +#undef GPIOSET + +#else + +/* + * Optimized use of gpiolib is twice as fast as no optimization + * only one driver can use the optimized version at a time + */ +int fbtft_write_gpio8_wr(struct fbtft_par *par, void *buf, size_t len) +{ + u8 data; + int i; +#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO + static u8 prev_data; +#endif + + fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, + "%s(len=%d): ", __func__, len); + + while (len--) { + data = *(u8 *) buf; + + /* Start writing by pulling down /WR */ + gpio_set_value(par->gpio.wr, 0); + + /* Set data */ +#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO + if (data == prev_data) { + gpio_set_value(par->gpio.wr, 0); /* used as delay */ + } else { + for (i = 0; i < 8; i++) { + if ((data & 1) != (prev_data & 1)) + gpio_set_value(par->gpio.db[i], + (data & 1)); + data >>= 1; + prev_data >>= 1; + } + } +#else + for (i = 0; i < 8; i++) { + gpio_set_value(par->gpio.db[i], (data & 1)); + data >>= 1; + } +#endif + + /* Pullup /WR */ + gpio_set_value(par->gpio.wr, 1); + +#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO + prev_data = *(u8 *) buf; +#endif + buf++; + } + + return 0; +} +EXPORT_SYMBOL(fbtft_write_gpio8_wr); + +int fbtft_write_gpio16_wr(struct fbtft_par *par, void *buf, size_t len) +{ + u16 data; + int i; +#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO + static u16 prev_data; +#endif + + fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, + "%s(len=%d): ", __func__, len); + + while (len) { + data = *(u16 *) buf; + + /* Start writing by pulling down /WR */ + gpio_set_value(par->gpio.wr, 0); + + /* Set data */ +#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO + if (data == prev_data) { + gpio_set_value(par->gpio.wr, 0); /* used as delay */ + } else { + for (i = 0; i < 16; i++) { + if ((data & 1) != (prev_data & 1)) + gpio_set_value(par->gpio.db[i], + (data & 1)); + data >>= 1; + prev_data >>= 1; + } + } +#else + for (i = 0; i < 16; i++) { + gpio_set_value(par->gpio.db[i], (data & 1)); + data >>= 1; + } +#endif + + /* Pullup /WR */ + gpio_set_value(par->gpio.wr, 1); + +#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO + prev_data = *(u16 *) buf; +#endif + buf += 2; + len -= 2; + } + + return 0; +} +EXPORT_SYMBOL(fbtft_write_gpio16_wr); + +int fbtft_write_gpio16_wr_latched(struct fbtft_par *par, void *buf, size_t len) +{ + dev_err(par->info->device, "%s: function not implemented\n", __func__); + return -1; +} +EXPORT_SYMBOL(fbtft_write_gpio16_wr_latched); + +#endif /* CONFIG_ARCH_BCM2708 */ diff --git a/drivers/video/fbtft/fbtft-sysfs.c b/drivers/video/fbtft/fbtft-sysfs.c new file mode 100644 index 0000000..45f8de3 --- /dev/null +++ b/drivers/video/fbtft/fbtft-sysfs.c @@ -0,0 +1,222 @@ +#include "fbtft.h" + + +static int get_next_ulong(char **str_p, unsigned long *val, char *sep, int base) +{ + char *p_val; + int ret; + + if (!str_p || !(*str_p)) + return -EINVAL; + + p_val = strsep(str_p, sep); + + if (!p_val) + return -EINVAL; + + ret = kstrtoul(p_val, base, val); + if (ret) + return -EINVAL; + + return 0; +} + +int fbtft_gamma_parse_str(struct fbtft_par *par, unsigned long *curves, + const char *str, int size) +{ + char *str_p, *curve_p = NULL; + char *tmp; + unsigned long val = 0; + int ret = 0; + int curve_counter, value_counter; + + fbtft_par_dbg(DEBUG_SYSFS, par, "%s() str=\n", __func__); + + if (!str || !curves) + return -EINVAL; + + fbtft_par_dbg(DEBUG_SYSFS, par, "%s\n", str); + + tmp = kmalloc(size+1, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + memcpy(tmp, str, size+1); + + /* replace optional separators */ + str_p = tmp; + while (*str_p) { + if (*str_p == ',') + *str_p = ' '; + if (*str_p == ';') + *str_p = '\n'; + str_p++; + } + + str_p = strim(tmp); + + curve_counter = 0; + while (str_p) { + if (curve_counter == par->gamma.num_curves) { + dev_err(par->info->device, "Gamma: Too many curves\n"); + ret = -EINVAL; + goto out; + } + curve_p = strsep(&str_p, "\n"); + value_counter = 0; + while (curve_p) { + if (value_counter == par->gamma.num_values) { + dev_err(par->info->device, + "Gamma: Too many values\n"); + ret = -EINVAL; + goto out; + } + ret = get_next_ulong(&curve_p, &val, " ", 16); + if (ret) + goto out; + curves[curve_counter * par->gamma.num_values + value_counter] = val; + value_counter++; + } + if (value_counter != par->gamma.num_values) { + dev_err(par->info->device, "Gamma: Too few values\n"); + ret = -EINVAL; + goto out; + } + curve_counter++; + } + if (curve_counter != par->gamma.num_curves) { + dev_err(par->info->device, "Gamma: Too few curves\n"); + ret = -EINVAL; + goto out; + } + +out: + kfree(tmp); + return ret; +} + +static ssize_t +sprintf_gamma(struct fbtft_par *par, unsigned long *curves, char *buf) +{ + ssize_t len = 0; + unsigned int i, j; + + mutex_lock(&par->gamma.lock); + for (i = 0; i < par->gamma.num_curves; i++) { + for (j = 0; j < par->gamma.num_values; j++) + len += scnprintf(&buf[len], PAGE_SIZE, + "%04lx ", curves[i*par->gamma.num_values + j]); + buf[len-1] = '\n'; + } + mutex_unlock(&par->gamma.lock); + + return len; +} + +static ssize_t store_gamma_curve(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + struct fbtft_par *par = fb_info->par; + unsigned long tmp_curves[FBTFT_GAMMA_MAX_VALUES_TOTAL]; + int ret; + + ret = fbtft_gamma_parse_str(par, tmp_curves, buf, count); + if (ret) + return ret; + + ret = par->fbtftops.set_gamma(par, tmp_curves); + if (ret) + return ret; + + mutex_lock(&par->gamma.lock); + memcpy(par->gamma.curves, tmp_curves, + par->gamma.num_curves * par->gamma.num_values * sizeof(tmp_curves[0])); + mutex_unlock(&par->gamma.lock); + + return count; +} + +static ssize_t show_gamma_curve(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + struct fbtft_par *par = fb_info->par; + + return sprintf_gamma(par, par->gamma.curves, buf); +} + +static struct device_attribute gamma_device_attrs[] = { + __ATTR(gamma, 0660, show_gamma_curve, store_gamma_curve), +}; + + +void fbtft_expand_debug_value(unsigned long *debug) +{ + switch (*debug & 0b111) { + case 1: + *debug |= DEBUG_LEVEL_1; + break; + case 2: + *debug |= DEBUG_LEVEL_2; + break; + case 3: + *debug |= DEBUG_LEVEL_3; + break; + case 4: + *debug |= DEBUG_LEVEL_4; + break; + case 5: + *debug |= DEBUG_LEVEL_5; + break; + case 6: + *debug |= DEBUG_LEVEL_6; + break; + case 7: + *debug = 0xFFFFFFFF; + break; + } +} + +static ssize_t store_debug(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + struct fbtft_par *par = fb_info->par; + int ret; + + ret = kstrtoul(buf, 10, &par->debug); + if (ret) + return ret; + fbtft_expand_debug_value(&par->debug); + + return count; +} + +static ssize_t show_debug(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *fb_info = dev_get_drvdata(device); + struct fbtft_par *par = fb_info->par; + + return snprintf(buf, PAGE_SIZE, "%lu\n", par->debug); +} + +static struct device_attribute debug_device_attr = \ + __ATTR(debug, 0660, show_debug, store_debug); + + +void fbtft_sysfs_init(struct fbtft_par *par) +{ + device_create_file(par->info->dev, &debug_device_attr); + if (par->gamma.curves && par->fbtftops.set_gamma) + device_create_file(par->info->dev, &gamma_device_attrs[0]); +} + +void fbtft_sysfs_exit(struct fbtft_par *par) +{ + device_remove_file(par->info->dev, &debug_device_attr); + if (par->gamma.curves && par->fbtftops.set_gamma) + device_remove_file(par->info->dev, &gamma_device_attrs[0]); +} diff --git a/drivers/video/fbtft/fbtft.h b/drivers/video/fbtft/fbtft.h new file mode 100644 index 0000000..0dbf3f9 --- /dev/null +++ b/drivers/video/fbtft/fbtft.h @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2013 Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __LINUX_FBTFT_H +#define __LINUX_FBTFT_H + +#include +#include +#include +#include + + +#define FBTFT_NOP 0x00 +#define FBTFT_SWRESET 0x01 +#define FBTFT_RDDID 0x04 +#define FBTFT_RDDST 0x09 +#define FBTFT_CASET 0x2A +#define FBTFT_RASET 0x2B +#define FBTFT_RAMWR 0x2C + +#define FBTFT_ONBOARD_BACKLIGHT 2 + +#define FBTFT_GPIO_NO_MATCH 0xFFFF +#define FBTFT_GPIO_NAME_SIZE 32 +#define FBTFT_MAX_INIT_SEQUENCE 512 +#define FBTFT_GAMMA_MAX_VALUES_TOTAL 128 + +#define FBTFT_OF_INIT_CMD BIT(24) +#define FBTFT_OF_INIT_DELAY BIT(25) + +/** + * struct fbtft_gpio - Structure that holds one pinname to gpio mapping + * @name: pinname (reset, dc, etc.) + * @gpio: GPIO number + * + */ +struct fbtft_gpio { + char name[FBTFT_GPIO_NAME_SIZE]; + unsigned gpio; +}; + +struct fbtft_par; + +/** + * struct fbtft_ops - FBTFT operations structure + * @write: Writes to interface bus + * @read: Reads from interface bus + * @write_vmem: Writes video memory to display + * @write_reg: Writes to controller register + * @set_addr_win: Set the GRAM update window + * @reset: Reset the LCD controller + * @mkdirty: Marks display lines for update + * @update_display: Updates the display + * @init_display: Initializes the display + * @blank: Blank the display (optional) + * @request_gpios_match: Do pinname to gpio matching + * @request_gpios: Request gpios from the kernel + * @free_gpios: Free previously requested gpios + * @verify_gpios: Verify that necessary gpios is present (optional) + * @register_backlight: Used to register backlight device (optional) + * @unregister_backlight: Unregister backlight device (optional) + * @set_var: Configure LCD with values from variables like @rotate and @bgr + * (optional) + * @set_gamma: Set Gamma curve (optional) + * + * Most of these operations have default functions assigned to them in + * fbtft_framebuffer_alloc() + */ +struct fbtft_ops { + int (*write)(struct fbtft_par *par, void *buf, size_t len); + int (*read)(struct fbtft_par *par, void *buf, size_t len); + int (*write_vmem)(struct fbtft_par *par, size_t offset, size_t len); + void (*write_register)(struct fbtft_par *par, int len, ...); + + void (*set_addr_win)(struct fbtft_par *par, + int xs, int ys, int xe, int ye); + void (*reset)(struct fbtft_par *par); + void (*mkdirty)(struct fb_info *info, int from, int to); + void (*update_display)(struct fbtft_par *par, + unsigned start_line, unsigned end_line); + int (*init_display)(struct fbtft_par *par); + int (*blank)(struct fbtft_par *par, bool on); + + unsigned long (*request_gpios_match)(struct fbtft_par *par, + const struct fbtft_gpio *gpio); + int (*request_gpios)(struct fbtft_par *par); + int (*verify_gpios)(struct fbtft_par *par); + + void (*register_backlight)(struct fbtft_par *par); + void (*unregister_backlight)(struct fbtft_par *par); + + int (*set_var)(struct fbtft_par *par); + int (*set_gamma)(struct fbtft_par *par, unsigned long *curves); +}; + +/** + * struct fbtft_display - Describes the display properties + * @width: Width of display in pixels + * @height: Height of display in pixels + * @regwidth: LCD Controller Register width in bits + * @buswidth: Display interface bus width in bits + * @backlight: Backlight type. + * @fbtftops: FBTFT operations provided by driver or device (platform_data) + * @bpp: Bits per pixel + * @fps: Frames per second + * @txbuflen: Size of transmit buffer + * @init_sequence: Pointer to LCD initialization array + * @gamma: String representation of Gamma curve(s) + * @gamma_num: Number of Gamma curves + * @gamma_len: Number of values per Gamma curve + * @debug: Initial debug value + * + * This structure is not stored by FBTFT except for init_sequence. + */ +struct fbtft_display { + unsigned width; + unsigned height; + unsigned regwidth; + unsigned buswidth; + unsigned backlight; + struct fbtft_ops fbtftops; + unsigned bpp; + unsigned fps; + int txbuflen; + int *init_sequence; + char *gamma; + int gamma_num; + int gamma_len; + unsigned long debug; +}; + +/** + * struct fbtft_platform_data - Passes display specific data to the driver + * @display: Display properties + * @gpios: Pointer to an array of piname to gpio mappings + * @rotate: Display rotation angle + * @bgr: LCD Controller BGR bit + * @fps: Frames per second (this will go away, use @fps in @fbtft_display) + * @txbuflen: Size of transmit buffer + * @startbyte: When set, enables use of Startbyte in transfers + * @gamma: String representation of Gamma curve(s) + * @extra: A way to pass extra info + */ +struct fbtft_platform_data { + struct fbtft_display display; + const struct fbtft_gpio *gpios; + unsigned rotate; + bool bgr; + unsigned fps; + int txbuflen; + u8 startbyte; + char *gamma; + void *extra; +}; + +/** + * struct fbtft_par - Main FBTFT data structure + * + * This structure holds all relevant data to operate the display + * + * See sourcefile for documentation since nested structs is not + * supported by kernel-doc. + * + */ +/* @spi: Set if it is a SPI device + * @pdev: Set if it is a platform device + * @info: Pointer to framebuffer fb_info structure + * @pdata: Pointer to platform data + * @ssbuf: Not used + * @pseudo_palette: Used by fb_set_colreg() + * @txbuf.buf: Transmit buffer + * @txbuf.len: Transmit buffer length + * @buf: Small buffer used when writing init data over SPI + * @startbyte: Used by some controllers when in SPI mode. + * Format: 6 bit Device id + RS bit + RW bit + * @fbtftops: FBTFT operations provided by driver or device (platform_data) + * @dirty_lock: Protects dirty_lines_start and dirty_lines_end + * @dirty_lines_start: Where to begin updating display + * @dirty_lines_end: Where to end updating display + * @gpio.reset: GPIO used to reset display + * @gpio.dc: Data/Command signal, also known as RS + * @gpio.rd: Read latching signal + * @gpio.wr: Write latching signal + * @gpio.latch: Bus latch signal, eg. 16->8 bit bus latch + * @gpio.cs: LCD Chip Select with parallel interface bus + * @gpio.db[16]: Parallel databus + * @gpio.led[16]: Led control signals + * @gpio.aux[16]: Auxillary signals, not used by core + * @init_sequence: Pointer to LCD initialization array + * @gamma.lock: Mutex for Gamma curve locking + * @gamma.curves: Pointer to Gamma curve array + * @gamma.num_values: Number of values per Gamma curve + * @gamma.num_curves: Number of Gamma curves + * @debug: Pointer to debug value + * @current_debug: + * @first_update_done: Used to only time the first display update + * @update_time: Used to calculate 'fps' in debug output + * @bgr: BGR mode/\n + * @extra: Extra info needed by driver + */ +struct fbtft_par { + struct spi_device *spi; + struct platform_device *pdev; + struct fb_info *info; + struct fbtft_platform_data *pdata; + u16 *ssbuf; + u32 pseudo_palette[16]; + struct { + void *buf; + dma_addr_t dma; + size_t len; + } txbuf; + u8 *buf; + u8 startbyte; + struct fbtft_ops fbtftops; + spinlock_t dirty_lock; + unsigned dirty_lines_start; + unsigned dirty_lines_end; + struct { + int reset; + int dc; + int rd; + int wr; + int latch; + int cs; + int db[16]; + int led[16]; + int aux[16]; + } gpio; + int *init_sequence; + struct { + struct mutex lock; + unsigned long *curves; + int num_values; + int num_curves; + } gamma; + unsigned long debug; + bool first_update_done; + struct timespec update_time; + bool bgr; + void *extra; +}; + +#define NUMARGS(...) (sizeof((int[]){__VA_ARGS__})/sizeof(int)) + +#define write_reg(par, ...) \ +do { \ + par->fbtftops.write_register(par, NUMARGS(__VA_ARGS__), __VA_ARGS__); \ +} while (0) + +/* fbtft-core.c */ +extern void fbtft_dbg_hex(const struct device *dev, + int groupsize, void *buf, size_t len, const char *fmt, ...); +extern struct fb_info *fbtft_framebuffer_alloc(struct fbtft_display *display, + struct device *dev); +extern void fbtft_framebuffer_release(struct fb_info *info); +extern int fbtft_register_framebuffer(struct fb_info *fb_info); +extern int fbtft_unregister_framebuffer(struct fb_info *fb_info); +extern void fbtft_register_backlight(struct fbtft_par *par); +extern void fbtft_unregister_backlight(struct fbtft_par *par); +extern int fbtft_init_display(struct fbtft_par *par); +extern int fbtft_probe_common(struct fbtft_display *display, + struct spi_device *sdev, struct platform_device *pdev); +extern int fbtft_remove_common(struct device *dev, struct fb_info *info); + +/* fbtft-io.c */ +extern int fbtft_write_spi(struct fbtft_par *par, void *buf, size_t len); +extern int fbtft_write_spi_emulate_9(struct fbtft_par *par, + void *buf, size_t len); +extern int fbtft_read_spi(struct fbtft_par *par, void *buf, size_t len); +extern int fbtft_write_gpio8_wr(struct fbtft_par *par, void *buf, size_t len); +extern int fbtft_write_gpio16_wr(struct fbtft_par *par, void *buf, size_t len); +extern int fbtft_write_gpio16_wr_latched(struct fbtft_par *par, + void *buf, size_t len); + +/* fbtft-bus.c */ +extern int fbtft_write_vmem8_bus8(struct fbtft_par *par, size_t offset, size_t len); +extern int fbtft_write_vmem16_bus16(struct fbtft_par *par, size_t offset, size_t len); +extern int fbtft_write_vmem16_bus8(struct fbtft_par *par, size_t offset, size_t len); +extern int fbtft_write_vmem16_bus9(struct fbtft_par *par, size_t offset, size_t len); +extern void fbtft_write_reg8_bus8(struct fbtft_par *par, int len, ...); +extern void fbtft_write_reg8_bus9(struct fbtft_par *par, int len, ...); +extern void fbtft_write_reg16_bus8(struct fbtft_par *par, int len, ...); +extern void fbtft_write_reg16_bus16(struct fbtft_par *par, int len, ...); + + +#define FBTFT_REGISTER_DRIVER(_name, _compatible, _display) \ + \ +static int fbtft_driver_probe_spi(struct spi_device *spi) \ +{ \ + return fbtft_probe_common(_display, spi, NULL); \ +} \ + \ +static int fbtft_driver_remove_spi(struct spi_device *spi) \ +{ \ + struct fb_info *info = spi_get_drvdata(spi); \ + \ + return fbtft_remove_common(&spi->dev, info); \ +} \ + \ +static int fbtft_driver_probe_pdev(struct platform_device *pdev) \ +{ \ + return fbtft_probe_common(_display, NULL, pdev); \ +} \ + \ +static int fbtft_driver_remove_pdev(struct platform_device *pdev) \ +{ \ + struct fb_info *info = platform_get_drvdata(pdev); \ + \ + return fbtft_remove_common(&pdev->dev, info); \ +} \ + \ +static const struct of_device_id dt_ids[] = { \ + { .compatible = _compatible }, \ + {}, \ +}; \ + \ +MODULE_DEVICE_TABLE(of, dt_ids); \ + \ + \ +static struct spi_driver fbtft_driver_spi_driver = { \ + .driver = { \ + .name = _name, \ + .owner = THIS_MODULE, \ + .of_match_table = of_match_ptr(dt_ids), \ + }, \ + .probe = fbtft_driver_probe_spi, \ + .remove = fbtft_driver_remove_spi, \ +}; \ + \ +static struct platform_driver fbtft_driver_platform_driver = { \ + .driver = { \ + .name = _name, \ + .owner = THIS_MODULE, \ + .of_match_table = of_match_ptr(dt_ids), \ + }, \ + .probe = fbtft_driver_probe_pdev, \ + .remove = fbtft_driver_remove_pdev, \ +}; \ + \ +static int __init fbtft_driver_module_init(void) \ +{ \ + int ret; \ + \ + ret = spi_register_driver(&fbtft_driver_spi_driver); \ + if (ret < 0) \ + return ret; \ + return platform_driver_register(&fbtft_driver_platform_driver); \ +} \ + \ +static void __exit fbtft_driver_module_exit(void) \ +{ \ + spi_unregister_driver(&fbtft_driver_spi_driver); \ + platform_driver_unregister(&fbtft_driver_platform_driver); \ +} \ + \ +module_init(fbtft_driver_module_init); \ +module_exit(fbtft_driver_module_exit); + + +/* Debug macros */ + +/* shorthand debug levels */ +#define DEBUG_LEVEL_1 DEBUG_REQUEST_GPIOS +#define DEBUG_LEVEL_2 (DEBUG_LEVEL_1 | DEBUG_DRIVER_INIT_FUNCTIONS | DEBUG_TIME_FIRST_UPDATE) +#define DEBUG_LEVEL_3 (DEBUG_LEVEL_2 | DEBUG_RESET | DEBUG_INIT_DISPLAY | DEBUG_BLANK | DEBUG_REQUEST_GPIOS | DEBUG_FREE_GPIOS | DEBUG_VERIFY_GPIOS | DEBUG_BACKLIGHT | DEBUG_SYSFS) +#define DEBUG_LEVEL_4 (DEBUG_LEVEL_2 | DEBUG_FB_READ | DEBUG_FB_WRITE | DEBUG_FB_FILLRECT | DEBUG_FB_COPYAREA | DEBUG_FB_IMAGEBLIT | DEBUG_FB_BLANK) +#define DEBUG_LEVEL_5 (DEBUG_LEVEL_3 | DEBUG_UPDATE_DISPLAY) +#define DEBUG_LEVEL_6 (DEBUG_LEVEL_4 | DEBUG_LEVEL_5) +#define DEBUG_LEVEL_7 0xFFFFFFFF + +#define DEBUG_DRIVER_INIT_FUNCTIONS (1<<3) +#define DEBUG_TIME_FIRST_UPDATE (1<<4) +#define DEBUG_TIME_EACH_UPDATE (1<<5) +#define DEBUG_DEFERRED_IO (1<<6) +#define DEBUG_FBTFT_INIT_FUNCTIONS (1<<7) + +/* fbops */ +#define DEBUG_FB_READ (1<<8) +#define DEBUG_FB_WRITE (1<<9) +#define DEBUG_FB_FILLRECT (1<<10) +#define DEBUG_FB_COPYAREA (1<<11) +#define DEBUG_FB_IMAGEBLIT (1<<12) +#define DEBUG_FB_SETCOLREG (1<<13) +#define DEBUG_FB_BLANK (1<<14) + +#define DEBUG_SYSFS (1<<16) + +/* fbtftops */ +#define DEBUG_BACKLIGHT (1<<17) +#define DEBUG_READ (1<<18) +#define DEBUG_WRITE (1<<19) +#define DEBUG_WRITE_VMEM (1<<20) +#define DEBUG_WRITE_REGISTER (1<<21) +#define DEBUG_SET_ADDR_WIN (1<<22) +#define DEBUG_RESET (1<<23) +#define DEBUG_MKDIRTY (1<<24) +#define DEBUG_UPDATE_DISPLAY (1<<25) +#define DEBUG_INIT_DISPLAY (1<<26) +#define DEBUG_BLANK (1<<27) +#define DEBUG_REQUEST_GPIOS (1<<28) +#define DEBUG_FREE_GPIOS (1<<29) +#define DEBUG_REQUEST_GPIOS_MATCH (1<<30) +#define DEBUG_VERIFY_GPIOS (1<<31) + + +#define fbtft_init_dbg(dev, format, arg...) \ +do { \ + if (unlikely((dev)->platform_data && \ + (((struct fbtft_platform_data *)(dev)->platform_data)->display.debug & DEBUG_DRIVER_INIT_FUNCTIONS))) \ + dev_info(dev, format, ##arg); \ +} while (0) + +#define fbtft_par_dbg(level, par, format, arg...) \ +do { \ + if (unlikely(par->debug & level)) \ + dev_info(par->info->device, format, ##arg); \ +} while (0) + +#define fbtft_dev_dbg(level, par, dev, format, arg...) \ +do { \ + if (unlikely(par->debug & level)) \ + dev_info(dev, format, ##arg); \ +} while (0) + +#define fbtft_par_dbg_hex(level, par, dev, type, buf, num, format, arg...) \ +do { \ + if (unlikely(par->debug & level)) \ + fbtft_dbg_hex(dev, sizeof(type), buf, num * sizeof(type), format, ##arg); \ +} while (0) + +#endif /* __LINUX_FBTFT_H */ diff --git a/drivers/video/fbtft/fbtft_device.c b/drivers/video/fbtft/fbtft_device.c new file mode 100644 index 0000000..b9f4c30 --- /dev/null +++ b/drivers/video/fbtft/fbtft_device.c @@ -0,0 +1,1444 @@ +/* + * + * Copyright (C) 2013, Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "fbtft_device" + +#define MAX_GPIOS 32 + +struct spi_device *spi_device; +struct platform_device *p_device; + +static char *name; +module_param(name, charp, 0); +MODULE_PARM_DESC(name, "Devicename (required). " \ +"name=list => list all supported devices."); + +static unsigned rotate; +module_param(rotate, uint, 0); +MODULE_PARM_DESC(rotate, +"Angle to rotate display counter clockwise: 0, 90, 180, 270"); + +static unsigned busnum; +module_param(busnum, uint, 0); +MODULE_PARM_DESC(busnum, "SPI bus number (default=0)"); + +static unsigned cs; +module_param(cs, uint, 0); +MODULE_PARM_DESC(cs, "SPI chip select (default=0)"); + +static unsigned speed; +module_param(speed, uint, 0); +MODULE_PARM_DESC(speed, "SPI speed (override device default)"); + +static int mode = -1; +module_param(mode, int, 0); +MODULE_PARM_DESC(mode, "SPI mode (override device default)"); + +static char *gpios; +module_param(gpios, charp, 0); +MODULE_PARM_DESC(gpios, +"List of gpios. Comma separated with the form: reset:23,dc:24 " \ +"(when overriding the default, all gpios must be specified)"); + +static unsigned fps; +module_param(fps, uint, 0); +MODULE_PARM_DESC(fps, "Frames per second (override driver default)"); + +static char *gamma; +module_param(gamma, charp, 0); +MODULE_PARM_DESC(gamma, +"String representation of Gamma Curve(s). Driver specific."); + +static int txbuflen; +module_param(txbuflen, int, 0); +MODULE_PARM_DESC(txbuflen, "txbuflen (override driver default)"); + +static int bgr = -1; +module_param(bgr, int, 0); +MODULE_PARM_DESC(bgr, +"BGR bit (supported by some drivers)."); + +static unsigned startbyte; +module_param(startbyte, uint, 0); +MODULE_PARM_DESC(startbyte, "Sets the Start byte used by some SPI displays."); + +static bool custom; +module_param(custom, bool, 0); +MODULE_PARM_DESC(custom, "Add a custom display device. " \ +"Use speed= argument to make it a SPI device, else platform_device"); + +static unsigned width; +module_param(width, uint, 0); +MODULE_PARM_DESC(width, "Display width, used with the custom argument"); + +static unsigned height; +module_param(height, uint, 0); +MODULE_PARM_DESC(height, "Display height, used with the custom argument"); + +static unsigned buswidth = 8; +module_param(buswidth, uint, 0); +MODULE_PARM_DESC(buswidth, "Display bus width, used with the custom argument"); + +static int init[FBTFT_MAX_INIT_SEQUENCE]; +static int init_num; +module_param_array(init, int, &init_num, 0); +MODULE_PARM_DESC(init, "Init sequence, used with the custom argument"); + +static unsigned long debug; +module_param(debug, ulong , 0); +MODULE_PARM_DESC(debug, +"level: 0-7 (the remaining 29 bits is for advanced usage)"); + +static unsigned verbose = 3; +module_param(verbose, uint, 0); +MODULE_PARM_DESC(verbose, +"0 silent, >0 show gpios, >1 show devices, >2 show devices before (default=3)"); + + +struct fbtft_device_display { + char *name; + struct spi_board_info *spi; + struct platform_device *pdev; +}; + +static void fbtft_device_pdev_release(struct device *dev); + +static int write_gpio16_wr_slow(struct fbtft_par *par, void *buf, size_t len); +static void adafruit18_green_tab_set_addr_win(struct fbtft_par *par, + int xs, int ys, int xe, int ye); + +#define ADAFRUIT18_GAMMA \ + "02 1c 07 12 37 32 29 2d 29 25 2B 39 00 01 03 10\n" \ + "03 1d 07 06 2E 2C 29 2D 2E 2E 37 3F 00 00 02 10" + +static int hy28b_init_sequence[] = { + -1,0x00e7,0x0010,-1,0x0000,0x0001,-1,0x0001,0x0100,-1,0x0002,0x0700, + -1,0x0003,0x1030,-1,0x0004,0x0000,-1,0x0008,0x0207,-1,0x0009,0x0000, + -1,0x000a,0x0000,-1,0x000c,0x0001,-1,0x000d,0x0000,-1,0x000f,0x0000, + -1,0x0010,0x0000,-1,0x0011,0x0007,-1,0x0012,0x0000,-1,0x0013,0x0000, + -2,50,-1,0x0010,0x1590,-1,0x0011,0x0227,-2,50,-1,0x0012,0x009c,-2,50, + -1,0x0013,0x1900,-1,0x0029,0x0023,-1,0x002b,0x000e,-2,50, + -1,0x0020,0x0000,-1,0x0021,0x0000,-2,50,-1,0x0050,0x0000, + -1,0x0051,0x00ef,-1,0x0052,0x0000,-1,0x0053,0x013f,-1,0x0060,0xa700, + -1,0x0061,0x0001,-1,0x006a,0x0000,-1,0x0080,0x0000,-1,0x0081,0x0000, + -1,0x0082,0x0000,-1,0x0083,0x0000,-1,0x0084,0x0000,-1,0x0085,0x0000, + -1,0x0090,0x0010,-1,0x0092,0x0000,-1,0x0093,0x0003,-1,0x0095,0x0110, + -1,0x0097,0x0000,-1,0x0098,0x0000,-1,0x0007,0x0133,-1,0x0020,0x0000, + -1,0x0021,0x0000,-2,100,-3 }; + +#define HY28B_GAMMA \ + "04 1F 4 7 7 0 7 7 6 0\n" \ + "0F 00 1 7 4 0 0 0 6 7" + +static int pitft_init_sequence[] = { + -1,0x01,-2,5,-1,0x28,-1,0xEF,0x03,0x80,0x02,-1,0xCF,0x00,0xC1,0x30, + -1,0xED,0x64,0x03,0x12,0x81,-1,0xE8,0x85,0x00,0x78, + -1,0xCB,0x39,0x2C,0x00,0x34,0x02,-1,0xF7,0x20,-1,0xEA,0x00,0x00, + -1,0xC0,0x23,-1,0xC1,0x10,-1,0xC5,0x3e,0x28,-1,0xC7,0x86,-1,0x3A,0x55, + -1,0xB1,0x00,0x18,-1,0xB6,0x08,0x82,0x27,-1,0xF2,0x00,-1,0x26,0x01, + -1,0xE0,0x0F,0x31,0x2B,0x0C,0x0E,0x08,0x4E,0xF1,0x37,0x07,0x10,0x03, + 0x0E,0x09,0x00,-1,0xE1,0x00,0x0E,0x14,0x03,0x11,0x07,0x31,0xC1,0x48, + 0x08,0x0F,0x0C,0x31,0x36,0x0F,-1,0x11,-2,100,-1,0x29,-2,20,-3 }; + +static int waveshare32b_init_sequence[] = { + -1,0xCB,0x39,0x2C,0x00,0x34,0x02,-1,0xCF,0x00,0xC1,0x30, + -1,0xE8,0x85,0x00,0x78,-1,0xEA,0x00,0x00,-1,0xED,0x64,0x03,0x12,0x81, + -1,0xF7,0x20,-1,0xC0,0x23,-1,0xC1,0x10,-1,0xC5,0x3e,0x28,-1,0xC7,0x86, + -1,0x36,0x28,-1,0x3A,0x55,-1,0xB1,0x00,0x18,-1,0xB6,0x08,0x82,0x27, + -1,0xF2,0x00,-1,0x26,0x01, + -1,0xE0,0x0F,0x31,0x2B,0x0C,0x0E,0x08,0x4E,0xF1,0x37,0x07,0x10,0x03,0x0E,0x09,0x00, + -1,0xE1,0x00,0x0E,0x14,0x03,0x11,0x07,0x31,0xC1,0x48,0x08,0x0F,0x0C,0x31,0x36,0x0F, + -1,0x11,-2,120,-1,0x29,-1,0x2c,-3 }; + +/* Supported displays in alphabetical order */ +static struct fbtft_device_display displays[] = { + { + .name = "adafruit18", + .spi = &(struct spi_board_info) { + .modalias = "fb_st7735r", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + { "led", 18 }, + {}, + }, + .gamma = ADAFRUIT18_GAMMA, + } + } + }, { + .name = "adafruit18_green", + .spi = &(struct spi_board_info) { + .modalias = "fb_st7735r", + .max_speed_hz = 4000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + .fbtftops.set_addr_win = \ + adafruit18_green_tab_set_addr_win, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + { "led", 18 }, + {}, + }, + .gamma = ADAFRUIT18_GAMMA, + } + } + }, { + .name = "adafruit22", + .spi = &(struct spi_board_info) { + .modalias = "fb_hx8340bn", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 9, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "led", 23 }, + {}, + }, + } + } + }, { + .name = "adafruit22a", + .spi = &(struct spi_board_info) { + .modalias = "fb_ili9340", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + { "led", 18 }, + {}, + }, + } + } + }, { + .name = "adafruit28", + .spi = &(struct spi_board_info) { + .modalias = "fb_ili9341", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + { "led", 18 }, + {}, + }, + } + } + }, { + .name = "adafruit13m", + .spi = &(struct spi_board_info) { + .modalias = "fb_ssd1306", + .max_speed_hz = 16000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + }, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + {}, + }, + } + } + }, { + .name = "agm1264k-fl", + .pdev = &(struct platform_device) { + .name = "fb_agm1264k-fl", + .id = 0, + .dev = { + .release = fbtft_device_pdev_release, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = FBTFT_ONBOARD_BACKLIGHT, + }, + .gpios = (const struct fbtft_gpio []) { + {}, + }, + }, + } + } + }, { + .name = "dogs102", + .spi = &(struct spi_board_info) { + .modalias = "fb_uc1701", + .max_speed_hz = 8000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 13 }, + { "dc", 6 }, + {}, + }, + } + } + }, { + .name = "er_tftm050_2", + .spi = &(struct spi_board_info) { + .modalias = "fb_ra8875", + .max_speed_hz = 5000000, + .mode = SPI_MODE_3, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + .width = 480, + .height = 272, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + {}, + }, + } + } + }, { + .name = "er_tftm070_5", + .spi = &(struct spi_board_info) { + .modalias = "fb_ra8875", + .max_speed_hz = 5000000, + .mode = SPI_MODE_3, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + .width = 800, + .height = 480, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + {}, + }, + } + } + }, { + .name = "flexfb", + .spi = &(struct spi_board_info) { + .modalias = "flexfb", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + {}, + }, + } + } + }, { + .name = "flexpfb", + .pdev = &(struct platform_device) { + .name = "flexpfb", + .id = 0, + .dev = { + .release = fbtft_device_pdev_release, + .platform_data = &(struct fbtft_platform_data) { + .gpios = (const struct fbtft_gpio []) { + { "reset", 17 }, + { "dc", 1 }, + { "wr", 0 }, + { "cs", 21 }, + { "db00", 9 }, + { "db01", 11 }, + { "db02", 18 }, + { "db03", 23 }, + { "db04", 24 }, + { "db05", 25 }, + { "db06", 8 }, + { "db07", 7 }, + { "led", 4 }, + {}, + }, + }, + } + } + }, { + .name = "freetronicsoled128", + .spi = &(struct spi_board_info) { + .modalias = "fb_ssd1351", + .max_speed_hz = 20000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = FBTFT_ONBOARD_BACKLIGHT, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 24 }, + { "dc", 25 }, + {}, + }, + } + } + }, { + .name = "hx8353d", + .spi = &(struct spi_board_info) { + .modalias = "fb_hx8353d", + .max_speed_hz = 16000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + { "led", 23 }, + {}, + }, + } + } + }, { + .name = "hy28a", + .spi = &(struct spi_board_info) { + .modalias = "fb_ili9320", + .max_speed_hz = 32000000, + .mode = SPI_MODE_3, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .startbyte = 0b01110000, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "led", 18 }, + {}, + }, + } + } + }, { + .name = "hy28b", + .spi = &(struct spi_board_info) { + .modalias = "fb_ili9325", + .max_speed_hz = 48000000, + .mode = SPI_MODE_3, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + .init_sequence = hy28b_init_sequence, + }, + .startbyte = 0b01110000, + .bgr = true, + .fps= 50, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "led", 18 }, + {}, + }, + .gamma = HY28B_GAMMA, + } + } + }, { + .name = "ili9481", + .spi = &(struct spi_board_info) { + .modalias = "fb_ili9481", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .regwidth = 16, + .buswidth = 8, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + { "led", 22 }, + {}, + }, + } + } + }, { + .name = "itdb24", + .pdev = &(struct platform_device) { + .name = "fb_s6d1121", + .id = 0, + .dev = { + .release = fbtft_device_pdev_release, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .bgr = false, + .gpios = (const struct fbtft_gpio []) { + /* Wiring for LCD adapter kit */ + { "reset", 7 }, + { "dc", 0 }, /* rev 2: 2 */ + { "wr", 1 }, /* rev 2: 3 */ + { "cs", 8 }, + { "db00", 17 }, + { "db01", 18 }, + { "db02", 21 }, /* rev 2: 27 */ + { "db03", 22 }, + { "db04", 23 }, + { "db05", 24 }, + { "db06", 25 }, + { "db07", 4 }, + {} + }, + }, + } + } + }, { + .name = "itdb28", + .pdev = &(struct platform_device) { + .name = "fb_ili9325", + .id = 0, + .dev = { + .release = fbtft_device_pdev_release, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + {}, + }, + }, + } + } + }, { + .name = "itdb28_spi", + .spi = &(struct spi_board_info) { + .modalias = "fb_ili9325", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + {}, + }, + } + } + }, { + .name = "mi0283qt-2", + .spi = &(struct spi_board_info) { + .modalias = "fb_hx8347d", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .startbyte = 0b01110000, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + { "led", 18 }, + {}, + }, + } + } + }, { + .name = "mi0283qt-9a", + .spi = &(struct spi_board_info) { + .modalias = "fb_ili9341", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 9, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "led", 18 }, + {}, + }, + } + } + }, { + .name = "mi0283qt-v2", + .spi = &(struct spi_board_info) { + .modalias = "fb_watterott", + .max_speed_hz = 4000000, + .mode = SPI_MODE_3, + .platform_data = &(struct fbtft_platform_data) { + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + {}, + }, + } + } + }, { + .name = "nokia3310", + .spi = &(struct spi_board_info) { + .modalias = "fb_pcd8544", + .max_speed_hz = 400000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + }, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + { "led", 23 }, + {}, + }, + } + } + }, { + .name = "nokia3310a", + .spi = &(struct spi_board_info) { + .modalias = "fb_tls8204", + .max_speed_hz = 1000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + }, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + { "led", 23 }, + {}, + }, + } + } + }, { + .name = "piscreen", + .spi = &(struct spi_board_info) { + .modalias = "fb_ili9486", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .regwidth = 16, + .buswidth = 8, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + { "led", 22 }, + {}, + }, + } + } + }, { + .name = "pitft", + .spi = &(struct spi_board_info) { + .modalias = "fb_ili9340", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .chip_select = 0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + .init_sequence = pitft_init_sequence, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "dc", 25 }, + {}, + }, + } + } + }, { + .name = "pioled", + .spi = &(struct spi_board_info) { + .modalias = "fb_ssd1351", + .max_speed_hz = 20000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 24 }, + { "dc", 25 }, + {}, + }, + .gamma = "0 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 2 " \ + "2 2 2 2 2 2 2 3 " \ + "3 3 3 3 3 3 3 3 " \ + "3 3 3 3 3 3 3 3 " \ + "3 3 3 4 4 4 4 4 " \ + "4 4 4 4 4 4 4" + } + } + }, { + .name = "rpi-display", + .spi = &(struct spi_board_info) { + .modalias = "fb_ili9341", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 23 }, + { "dc", 24 }, + { "led", 18 }, + {}, + }, + } + } + }, { + .name = "s6d02a1", + .spi = &(struct spi_board_info) { + .modalias = "fb_s6d02a1", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + { "led", 23 }, + {}, + }, + } + } + }, { + .name = "sainsmart18", + .spi = &(struct spi_board_info) { + .modalias = "fb_st7735r", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + }, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + {}, + }, + } + } + }, { + .name = "sainsmart32", + .pdev = &(struct platform_device) { + .name = "fb_ssd1289", + .id = 0, + .dev = { + .release = fbtft_device_pdev_release, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 16, + .txbuflen = -2, /* disable buffer */ + .backlight = 1, + .fbtftops.write = write_gpio16_wr_slow, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + {}, + }, + }, + }, + } + }, { + .name = "sainsmart32_fast", + .pdev = &(struct platform_device) { + .name = "fb_ssd1289", + .id = 0, + .dev = { + .release = fbtft_device_pdev_release, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 16, + .txbuflen = -2, /* disable buffer */ + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + {}, + }, + }, + }, + } + }, { + .name = "sainsmart32_latched", + .pdev = &(struct platform_device) { + .name = "fb_ssd1289", + .id = 0, + .dev = { + .release = fbtft_device_pdev_release, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 16, + .txbuflen = -2, /* disable buffer */ + .backlight = 1, + .fbtftops.write = \ + fbtft_write_gpio16_wr_latched, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + {}, + }, + }, + }, + } + }, { + .name = "sainsmart32_spi", + .spi = &(struct spi_board_info) { + .modalias = "fb_ssd1289", + .max_speed_hz = 16000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + {}, + }, + } + } + }, { + .name = "spidev", + .spi = &(struct spi_board_info) { + .modalias = "spidev", + .max_speed_hz = 500000, + .bus_num = 0, + .chip_select = 0, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .gpios = (const struct fbtft_gpio []) { + {}, + }, + } + } + }, { + .name = "ssd1331", + .spi = &(struct spi_board_info) { + .modalias = "fb_ssd1331", + .max_speed_hz = 20000000, + .mode = SPI_MODE_3, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + }, + .gpios = (const struct fbtft_gpio []) { + { "reset", 24 }, + { "dc", 25 }, + {}, + }, + } + } + }, { + .name = "tinylcd35", + .spi = &(struct spi_board_info) { + .modalias = "fb_tinylcd", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + { "led", 18 }, + {}, + }, + } + } + }, { + .name = "tm022hdh26", + .spi = &(struct spi_board_info) { + .modalias = "fb_ili9341", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 25 }, + { "dc", 24 }, + { "led", 18 }, + {}, + }, + } + } + }, { + .name = "tontec35_9481", /* boards before 02 July 2014 */ + .spi = &(struct spi_board_info) { + .modalias = "fb_ili9481", + .max_speed_hz = 128000000, + .mode = SPI_MODE_3, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 15 }, + { "dc", 25 }, + { "led_", 18 }, + {}, + }, + } + } + }, { + .name = "tontec35_9486", /* boards after 02 July 2014 */ + .spi = &(struct spi_board_info) { + .modalias = "fb_ili9486", + .max_speed_hz = 128000000, + .mode = SPI_MODE_3, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 15 }, + { "dc", 25 }, + { "led_", 18 }, + {}, + }, + } + } + }, { + .name = "upd161704", + .spi = &(struct spi_board_info) { + .modalias = "fb_upd161704", + .max_speed_hz = 32000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + }, + .gpios = (const struct fbtft_gpio []) { + { "reset", 24 }, + { "dc", 25 }, + {}, + }, + } + } + }, { + .name = "waveshare32b", + .spi = &(struct spi_board_info) { + .modalias = "fb_ili9340", + .max_speed_hz = 48000000, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + .backlight = 1, + .init_sequence = waveshare32b_init_sequence, + }, + .bgr = true, + .gpios = (const struct fbtft_gpio []) { + { "reset", 27 }, + { "dc", 22 }, + {}, + }, + } + } + }, { + .name = "waveshare22", + .spi = &(struct spi_board_info) { + .modalias = "fb_bd663474", + .max_speed_hz = 32000000, + .mode = SPI_MODE_3, + .platform_data = &(struct fbtft_platform_data) { + .display = { + .buswidth = 8, + }, + .gpios = (const struct fbtft_gpio []) { + { "reset", 24 }, + { "dc", 25 }, + {}, + }, + } + } + }, { + /* This should be the last item. + Used with the custom argument */ + .name = "", + .spi = &(struct spi_board_info) { + .modalias = "", + .max_speed_hz = 0, + .mode = SPI_MODE_0, + .platform_data = &(struct fbtft_platform_data) { + .gpios = (const struct fbtft_gpio []) { + {}, + }, + } + }, + .pdev = &(struct platform_device) { + .name = "", + .id = 0, + .dev = { + .release = fbtft_device_pdev_release, + .platform_data = &(struct fbtft_platform_data) { + .gpios = (const struct fbtft_gpio []) { + {}, + }, + }, + }, + }, + } +}; + +static int write_gpio16_wr_slow(struct fbtft_par *par, void *buf, size_t len) +{ + u16 data; + int i; +#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO + static u16 prev_data; +#endif + + fbtft_par_dbg_hex(DEBUG_WRITE, par, par->info->device, u8, buf, len, + "%s(len=%d): ", __func__, len); + + while (len) { + data = *(u16 *) buf; + + /* Start writing by pulling down /WR */ + gpio_set_value(par->gpio.wr, 0); + + /* Set data */ +#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO + if (data == prev_data) { + gpio_set_value(par->gpio.wr, 0); /* used as delay */ + } else { + for (i = 0; i < 16; i++) { + if ((data & 1) != (prev_data & 1)) + gpio_set_value(par->gpio.db[i], + (data & 1)); + data >>= 1; + prev_data >>= 1; + } + } +#else + for (i = 0; i < 16; i++) { + gpio_set_value(par->gpio.db[i], (data & 1)); + data >>= 1; + } +#endif + + /* Pullup /WR */ + gpio_set_value(par->gpio.wr, 1); + +#ifndef DO_NOT_OPTIMIZE_FBTFT_WRITE_GPIO + prev_data = *(u16 *) buf; +#endif + buf += 2; + len -= 2; + } + + return 0; +} + +static void adafruit18_green_tab_set_addr_win(struct fbtft_par *par, + int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, + "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + write_reg(par, 0x2A, 0, xs + 2, 0, xe + 2); + write_reg(par, 0x2B, 0, ys + 1, 0, ye + 1); + write_reg(par, 0x2C); +} + +/* used if gpios parameter is present */ +static struct fbtft_gpio fbtft_device_param_gpios[MAX_GPIOS+1] = { }; + +static void fbtft_device_pdev_release(struct device *dev) +{ +/* Needed to silence this message: +Device 'xxx' does not have a release() function, it is broken and must be fixed +*/ +} + +static int spi_device_found(struct device *dev, void *data) +{ + struct spi_device *spi = container_of(dev, struct spi_device, dev); + + pr_info(DRVNAME": %s %s %dkHz %d bits mode=0x%02X\n", + spi->modalias, dev_name(dev), spi->max_speed_hz/1000, + spi->bits_per_word, spi->mode); + + return 0; +} + +static void pr_spi_devices(void) +{ + pr_info(DRVNAME": SPI devices registered:\n"); + bus_for_each_dev(&spi_bus_type, NULL, NULL, spi_device_found); +} + +static int p_device_found(struct device *dev, void *data) +{ + struct platform_device + *pdev = container_of(dev, struct platform_device, dev); + + if (strstr(pdev->name, "fb")) + pr_info(DRVNAME": %s id=%d pdata? %s\n", + pdev->name, pdev->id, + pdev->dev.platform_data ? "yes" : "no"); + + return 0; +} + +static void pr_p_devices(void) +{ + pr_info(DRVNAME": 'fb' Platform devices registered:\n"); + bus_for_each_dev(&platform_bus_type, NULL, NULL, p_device_found); +} + +#ifdef MODULE +static void fbtft_device_spi_delete(struct spi_master *master, unsigned cs) +{ + struct device *dev; + char str[32]; + + snprintf(str, sizeof(str), "%s.%u", dev_name(&master->dev), cs); + + dev = bus_find_device_by_name(&spi_bus_type, NULL, str); + if (dev) { + if (verbose) + pr_info(DRVNAME": Deleting %s\n", str); + device_del(dev); + } +} + +static int fbtft_device_spi_device_register(struct spi_board_info *spi) +{ + struct spi_master *master; + + master = spi_busnum_to_master(spi->bus_num); + if (!master) { + pr_err(DRVNAME ": spi_busnum_to_master(%d) returned NULL\n", + spi->bus_num); + return -EINVAL; + } + /* make sure it's available */ + fbtft_device_spi_delete(master, spi->chip_select); + spi_device = spi_new_device(master, spi); + put_device(&master->dev); + if (!spi_device) { + pr_err(DRVNAME ": spi_new_device() returned NULL\n"); + return -EPERM; + } + return 0; +} +#else +static int fbtft_device_spi_device_register(struct spi_board_info *spi) +{ + return spi_register_board_info(spi, 1); +} +#endif + +static int __init fbtft_device_init(void) +{ + struct spi_board_info *spi = NULL; + struct fbtft_platform_data *pdata; + const struct fbtft_gpio *gpio = NULL; + char *p_gpio, *p_name, *p_num; + bool found = false; + int i = 0; + long val; + int ret = 0; + + pr_debug("\n\n"DRVNAME": init\n"); + + if (name == NULL) { +#ifdef MODULE + pr_err(DRVNAME": missing module parameter: 'name'\n"); + return -EINVAL; +#else + return 0; +#endif + } + + if (init_num > FBTFT_MAX_INIT_SEQUENCE) { + pr_err(DRVNAME \ + ": init parameter: exceeded max array size: %d\n", + FBTFT_MAX_INIT_SEQUENCE); + return -EINVAL; + } + + /* parse module parameter: gpios */ + while ((p_gpio = strsep(&gpios, ","))) { + if (strchr(p_gpio, ':') == NULL) { + pr_err(DRVNAME \ + ": error: missing ':' in gpios parameter: %s\n", + p_gpio); + return -EINVAL; + } + p_num = p_gpio; + p_name = strsep(&p_num, ":"); + if (p_name == NULL || p_num == NULL) { + pr_err(DRVNAME \ + ": something bad happened parsing gpios parameter: %s\n", + p_gpio); + return -EINVAL; + } + ret = kstrtol(p_num, 10, &val); + if (ret) { + pr_err(DRVNAME \ + ": could not parse number in gpios parameter: %s:%s\n", + p_name, p_num); + return -EINVAL; + } + strcpy(fbtft_device_param_gpios[i].name, p_name); + fbtft_device_param_gpios[i++].gpio = (int) val; + if (i == MAX_GPIOS) { + pr_err(DRVNAME \ + ": gpios parameter: exceeded max array size: %d\n", + MAX_GPIOS); + return -EINVAL; + } + } + if (fbtft_device_param_gpios[0].name[0]) + gpio = fbtft_device_param_gpios; + + if (verbose > 2) + pr_spi_devices(); /* print list of registered SPI devices */ + + if (verbose > 2) + pr_p_devices(); /* print list of 'fb' platform devices */ + + pr_debug(DRVNAME": name='%s', busnum=%d, cs=%d\n", name, busnum, cs); + + if (rotate > 0 && rotate < 4) { + rotate = (4 - rotate) * 90; + pr_warn("argument 'rotate' should be an angle. Values 1-3 is deprecated. Setting it to %d.\n", + rotate); + } + if (rotate != 0 && rotate != 90 && rotate != 180 && rotate != 270) { + pr_warn("argument 'rotate' illegal value: %d. Setting it to 0.\n", + rotate); + rotate = 0; + } + + /* name=list lists all supported displays */ + if (strncmp(name, "list", 32) == 0) { + pr_info(DRVNAME": Supported displays:\n"); + + for (i = 0; i < ARRAY_SIZE(displays); i++) + pr_info(DRVNAME": %s\n", displays[i].name); + return -ECANCELED; + } + + if (custom) { + i = ARRAY_SIZE(displays) - 1; + displays[i].name = name; + if (speed == 0) { + displays[i].pdev->name = name; + displays[i].spi = NULL; + } else { + strncpy(displays[i].spi->modalias, name, SPI_NAME_SIZE); + displays[i].pdev = NULL; + } + } + + for (i = 0; i < ARRAY_SIZE(displays); i++) { + if (strncmp(name, displays[i].name, 32) == 0) { + if (displays[i].spi) { + spi = displays[i].spi; + spi->chip_select = cs; + spi->bus_num = busnum; + if (speed) + spi->max_speed_hz = speed; + if (mode != -1) + spi->mode = mode; + pdata = (void *)spi->platform_data; + } else if (displays[i].pdev) { + p_device = displays[i].pdev; + pdata = p_device->dev.platform_data; + } else { + pr_err(DRVNAME": broken displays array\n"); + return -EINVAL; + } + + pdata->rotate = rotate; + if (bgr == 0) + pdata->bgr = false; + else if (bgr == 1) + pdata->bgr = true; + if (startbyte) + pdata->startbyte = startbyte; + if (gamma) + pdata->gamma = gamma; + pdata->display.debug = debug; + if (fps) + pdata->fps = fps; + if (txbuflen) + pdata->txbuflen = txbuflen; + if (init_num) + pdata->display.init_sequence = init; + if (gpio) + pdata->gpios = gpio; + if (custom) { + pdata->display.width = width; + pdata->display.height = height; + pdata->display.buswidth = buswidth; + pdata->display.backlight = 1; + } + + if (displays[i].spi) { + ret = fbtft_device_spi_device_register(spi); + if (ret) { + pr_err(DRVNAME \ + ": failed to register SPI device\n"); + return ret; + } + found = true; + break; + } else { + ret = platform_device_register(p_device); + if (ret < 0) { + pr_err(DRVNAME \ + ": platform_device_register() returned %d\n", + ret); + return ret; + } + found = true; + break; + } + } + } + + if (!found) { + pr_err(DRVNAME": display not supported: '%s'\n", name); + return -EINVAL; + } + + if (verbose && pdata && pdata->gpios) { + gpio = pdata->gpios; + pr_info(DRVNAME": GPIOS used by '%s':\n", name); + found = false; + while (verbose && gpio->name[0]) { + pr_info(DRVNAME": '%s' = GPIO%d\n", + gpio->name, gpio->gpio); + gpio++; + found = true; + } + if (!found) + pr_info(DRVNAME": (none)\n"); + } + + if (spi_device && (verbose > 1)) + pr_spi_devices(); + if (p_device && (verbose > 1)) + pr_p_devices(); + + return 0; +} + +static void __exit fbtft_device_exit(void) +{ + pr_debug(DRVNAME" - exit\n"); + + if (spi_device) { + device_del(&spi_device->dev); + kfree(spi_device); + } + + if (p_device) + platform_device_unregister(p_device); + +} + +arch_initcall(fbtft_device_init); +module_exit(fbtft_device_exit); + +MODULE_DESCRIPTION("Add a FBTFT device."); +MODULE_AUTHOR("Noralf Tronnes"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbtft/flexfb.c b/drivers/video/fbtft/flexfb.c new file mode 100644 index 0000000..45574a0 --- /dev/null +++ b/drivers/video/fbtft/flexfb.c @@ -0,0 +1,593 @@ +/* + * Generic FB driver for TFT LCD displays + * + * Copyright (C) 2013 Noralf Tronnes + * + * This program 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 program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "fbtft.h" + +#define DRVNAME "flexfb" + + +static char *chip = NULL; +module_param(chip, charp, 0); +MODULE_PARM_DESC(chip, "LCD controller"); + +static unsigned int width = 0; +module_param(width, uint, 0); +MODULE_PARM_DESC(width, "Display width"); + +static unsigned int height = 0; +module_param(height, uint, 0); +MODULE_PARM_DESC(height, "Display height"); + +static int init[512]; +static int init_num = 0; +module_param_array(init, int, &init_num, 0); +MODULE_PARM_DESC(init, "Init sequence"); + +static unsigned int setaddrwin = 0; +module_param(setaddrwin, uint, 0); +MODULE_PARM_DESC(setaddrwin, "Which set_addr_win() implementation to use"); + +static unsigned int buswidth = 8; +module_param(buswidth, uint, 0); +MODULE_PARM_DESC(buswidth, "Width of databus (default: 8)"); + +static unsigned int regwidth = 8; +module_param(regwidth, uint, 0); +MODULE_PARM_DESC(regwidth, "Width of controller register (default: 8)"); + +static bool nobacklight = false; +module_param(nobacklight, bool, 0); +MODULE_PARM_DESC(nobacklight, "Turn off backlight functionality."); + +static bool latched = false; +module_param(latched, bool, 0); +MODULE_PARM_DESC(latched, "Use with latched 16-bit databus"); + + +static int *initp = NULL; +static int initp_num = 0; + +/* default init sequences */ +static int st7735r_init[] = { \ +-1,0x01,-2,150,-1,0x11,-2,500,-1,0xB1,0x01,0x2C,0x2D,-1,0xB2,0x01,0x2C,0x2D,-1,0xB3,0x01,0x2C,0x2D,0x01,0x2C,0x2D, \ +-1,0xB4,0x07,-1,0xC0,0xA2,0x02,0x84,-1,0xC1,0xC5,-1,0xC2,0x0A,0x00,-1,0xC3,0x8A,0x2A,-1,0xC4,0x8A,0xEE,-1,0xC5,0x0E, \ +-1,0x20,-1,0x36,0xC0,-1,0x3A,0x05,-1,0xE0,0x0f,0x1a,0x0f,0x18,0x2f,0x28,0x20,0x22,0x1f,0x1b,0x23,0x37,0x00,0x07,0x02,0x10, \ +-1,0xE1,0x0f,0x1b,0x0f,0x17,0x33,0x2c,0x29,0x2e,0x30,0x30,0x39,0x3f,0x00,0x07,0x03,0x10,-1,0x29,-2,100,-1,0x13,-2,10,-3 }; + +static int ssd1289_init[] = { \ +-1,0x00,0x0001,-1,0x03,0xA8A4,-1,0x0C,0x0000,-1,0x0D,0x080C,-1,0x0E,0x2B00,-1,0x1E,0x00B7,-1,0x01,0x2B3F,-1,0x02,0x0600, \ +-1,0x10,0x0000,-1,0x11,0x6070,-1,0x05,0x0000,-1,0x06,0x0000,-1,0x16,0xEF1C,-1,0x17,0x0003,-1,0x07,0x0233,-1,0x0B,0x0000, \ +-1,0x0F,0x0000,-1,0x41,0x0000,-1,0x42,0x0000,-1,0x48,0x0000,-1,0x49,0x013F,-1,0x4A,0x0000,-1,0x4B,0x0000,-1,0x44,0xEF00, \ +-1,0x45,0x0000,-1,0x46,0x013F,-1,0x30,0x0707,-1,0x31,0x0204,-1,0x32,0x0204,-1,0x33,0x0502,-1,0x34,0x0507,-1,0x35,0x0204, \ +-1,0x36,0x0204,-1,0x37,0x0502,-1,0x3A,0x0302,-1,0x3B,0x0302,-1,0x23,0x0000,-1,0x24,0x0000,-1,0x25,0x8000,-1,0x4f,0x0000, \ +-1,0x4e,0x0000,-1,0x22,-3 }; + +static int hx8340bn_init[] = { \ +-1,0xC1,0xFF,0x83,0x40,-1,0x11,-2,150,-1,0xCA,0x70,0x00,0xD9,-1,0xB0,0x01,0x11, \ +-1,0xC9,0x90,0x49,0x10,0x28,0x28,0x10,0x00,0x06,-2,20,-1,0xC2,0x60,0x71,0x01,0x0E,0x05,0x02,0x09,0x31,0x0A, \ +-1,0xC3,0x67,0x30,0x61,0x17,0x48,0x07,0x05,0x33,-2,10,-1,0xB5,0x35,0x20,0x45,-1,0xB4,0x33,0x25,0x4C,-2,10, \ +-1,0x3A,0x05,-1,0x29,-2,10,-3 }; + +static int ili9225_init[] = { \ +-1,0x0001,0x011C,-1,0x0002,0x0100,-1,0x0003,0x1030,-1,0x0008,0x0808,-1,0x000C,0x0000,-1,0x000F,0x0A01,-1,0x0020,0x0000, \ +-1,0x0021,0x0000,-2,50,-1,0x0010,0x0A00,-1,0x0011,0x1038,-2,50,-1,0x0012,0x1121,-1,0x0013,0x004E,-1,0x0014,0x676F, \ +-1,0x0030,0x0000,-1,0x0031,0x00DB,-1,0x0032,0x0000,-1,0x0033,0x0000,-1,0x0034,0x00DB,-1,0x0035,0x0000,-1,0x0036,0x00AF, \ +-1,0x0037,0x0000,-1,0x0038,0x00DB,-1,0x0039,0x0000,-1,0x0050,0x0000,-1,0x0051,0x060A,-1,0x0052,0x0D0A,-1,0x0053,0x0303, \ +-1,0x0054,0x0A0D,-1,0x0055,0x0A06,-1,0x0056,0x0000,-1,0x0057,0x0303,-1,0x0058,0x0000,-1,0x0059,0x0000,-2,50, \ +-1,0x0007,0x1017,-2,50,-3 }; + +static int ili9320_init[] = { \ +-1,0x00E5,0x8000,-1,0x0000,0x0001,-1,0x0001,0x0100,-1,0x0002,0x0700,-1,0x0003,0x1030,-1,0x0004,0x0000,-1,0x0008,0x0202, \ +-1,0x0009,0x0000,-1,0x000A,0x0000,-1,0x000C,0x0000,-1,0x000D,0x0000,-1,0x000F,0x0000,-1,0x0010,0x0000,-1,0x0011,0x0007, \ +-1,0x0012,0x0000,-1,0x0013,0x0000,-2,200,-1,0x0010,0x17B0,-1,0x0011,0x0031,-2,50,-1,0x0012,0x0138,-2,50,-1,0x0013,0x1800, \ +-1,0x0029,0x0008,-2,50,-1,0x0020,0x0000,-1,0x0021,0x0000,-1,0x0030,0x0000,-1,0x0031,0x0505,-1,0x0032,0x0004, \ +-1,0x0035,0x0006,-1,0x0036,0x0707,-1,0x0037,0x0105,-1,0x0038,0x0002,-1,0x0039,0x0707,-1,0x003C,0x0704,-1,0x003D,0x0807, \ +-1,0x0050,0x0000,-1,0x0051,0x00EF,-1,0x0052,0x0000,-1,0x0053,0x013F,-1,0x0060,0x2700,-1,0x0061,0x0001,-1,0x006A,0x0000, \ +-1,0x0080,0x0000,-1,0x0081,0x0000,-1,0x0082,0x0000,-1,0x0083,0x0000,-1,0x0084,0x0000,-1,0x0085,0x0000,-1,0x0090,0x0010, \ +-1,0x0092,0x0000,-1,0x0093,0x0003,-1,0x0095,0x0110,-1,0x0097,0x0000,-1,0x0098,0x0000,-1,0x0007,0x0173,-3 }; + +static int ili9325_init[] = { \ +-1,0x00E3,0x3008,-1,0x00E7,0x0012,-1,0x00EF,0x1231,-1,0x0001,0x0100,-1,0x0002,0x0700,-1,0x0003,0x1030,-1,0x0004,0x0000, \ +-1,0x0008,0x0207,-1,0x0009,0x0000,-1,0x000A,0x0000,-1,0x000C,0x0000,-1,0x000D,0x0000,-1,0x000F,0x0000,-1,0x0010,0x0000, \ +-1,0x0011,0x0007,-1,0x0012,0x0000,-1,0x0013,0x0000,-2,200,-1,0x0010,0x1690,-1,0x0011,0x0223,-2,50,-1,0x0012,0x000D,-2,50, \ +-1,0x0013,0x1200,-1,0x0029,0x000A,-1,0x002B,0x000C,-2,50,-1,0x0020,0x0000,-1,0x0021,0x0000,-1,0x0030,0x0000, \ +-1,0x0031,0x0506,-1,0x0032,0x0104,-1,0x0035,0x0207,-1,0x0036,0x000F,-1,0x0037,0x0306,-1,0x0038,0x0102,-1,0x0039,0x0707, \ +-1,0x003C,0x0702,-1,0x003D,0x1604,-1,0x0050,0x0000,-1,0x0051,0x00EF,-1,0x0052,0x0000,-1,0x0053,0x013F,-1,0x0060,0xA700, \ +-1,0x0061,0x0001,-1,0x006A,0x0000,-1,0x0080,0x0000,-1,0x0081,0x0000,-1,0x0082,0x0000,-1,0x0083,0x0000,-1,0x0084,0x0000, \ +-1,0x0085,0x0000,-1,0x0090,0x0010,-1,0x0092,0x0600,-1,0x0007,0x0133,-3 }; + +static int ili9341_init[] = { \ +-1,0x28,-2,20,-1,0xCF,0x00,0x83,0x30,-1,0xED,0x64,0x03,0x12,0x81,-1,0xE8,0x85,0x01,0x79, \ +-1,0xCB,0x39,0x2c,0x00,0x34,0x02,-1,0xF7,0x20,-1,0xEA,0x00,0x00,-1,0xC0,0x26,-1,0xC1,0x11, \ +-1,0xC5,0x35,0x3E,-1,0xC7,0xBE,-1,0xB1,0x00,0x1B,-1,0xB6,0x0a,0x82,0x27,0x00,-1,0xB7,0x07, \ +-1,0x3A,0x55,-1,0x36,0x48,-1,0x11,-2,120,-1,0x29,-2,20,-3 }; + +static int ssd1351_init[] = { -1,0xfd,0x12,-1,0xfd,0xb1,-1,0xae,-1,0xb3,0xf1,-1,0xca,0x7f,-1,0xa0,0x74, \ + -1,0x15,0x00,0x7f,-1,0x75,0x00,0x7f,-1,0xa1,0x00,-1,0xa2,0x00,-1,0xb5,0x00, \ + -1,0xab,0x01,-1,0xb1,0x32,-1,0xb4,0xa0,0xb5,0x55,-1,0xbb,0x17,-1,0xbe,0x05, \ + -1,0xc1,0xc8,0x80,0xc8,-1,0xc7,0x0f,-1,0xb6,0x01,-1,0xa6,-1,0xaf,-3 }; + + +/* ili9320, ili9325 */ +static void flexfb_set_addr_win_1(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + switch (par->info->var.rotate) { + /* R20h = Horizontal GRAM Start Address */ + /* R21h = Vertical GRAM Start Address */ + case 0: + write_reg(par, 0x0020, xs); + write_reg(par, 0x0021, ys); + break; + case 180: + write_reg(par, 0x0020, width - 1 - xs); + write_reg(par, 0x0021, height - 1 - ys); + break; + case 270: + write_reg(par, 0x0020, width - 1 - ys); + write_reg(par, 0x0021, xs); + break; + case 90: + write_reg(par, 0x0020, ys); + write_reg(par, 0x0021, height - 1 - xs); + break; + } + write_reg(par, 0x0022); /* Write Data to GRAM */ +} + +/* ssd1289 */ +static void flexfb_set_addr_win_2(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + switch (par->info->var.rotate) { + /* R4Eh - Set GDDRAM X address counter */ + /* R4Fh - Set GDDRAM Y address counter */ + case 0: + write_reg(par, 0x4e, xs); + write_reg(par, 0x4f, ys); + break; + case 180: + write_reg(par, 0x4e, par->info->var.xres - 1 - xs); + write_reg(par, 0x4f, par->info->var.yres - 1 - ys); + break; + case 270: + write_reg(par, 0x4e, par->info->var.yres - 1 - ys); + write_reg(par, 0x4f, xs); + break; + case 90: + write_reg(par, 0x4e, ys); + write_reg(par, 0x4f, par->info->var.xres - 1 - xs); + break; + } + + /* R22h - RAM data write */ + write_reg(par, 0x22, 0); +} + +/* ssd1351 */ +static void set_addr_win_3(struct fbtft_par *par, int xs, int ys, int xe, int ye) +{ + fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); + + write_reg(par, 0x15, xs, xe); + write_reg(par, 0x75, ys, ye); + write_reg(par, 0x5C); +} + +static int flexfb_verify_gpios_dc(struct fbtft_par *par) +{ + fbtft_par_dbg(DEBUG_VERIFY_GPIOS, par, "%s()\n", __func__); + + if (par->gpio.dc < 0) { + dev_err(par->info->device, "Missing info about 'dc' gpio. Aborting.\n"); + return -EINVAL; + } + + return 0; +} + +static int flexfb_verify_gpios_db(struct fbtft_par *par) +{ + int i; + int num_db = buswidth; + + fbtft_par_dbg(DEBUG_VERIFY_GPIOS, par, "%s()\n", __func__); + + if (par->gpio.dc < 0) { + dev_err(par->info->device, "Missing info about 'dc' gpio. Aborting.\n"); + return -EINVAL; + } + if (par->gpio.wr < 0) { + dev_err(par->info->device, "Missing info about 'wr' gpio. Aborting.\n"); + return -EINVAL; + } + if (latched && (par->gpio.latch < 0)) { + dev_err(par->info->device, "Missing info about 'latch' gpio. Aborting.\n"); + return -EINVAL; + } + if (latched) + num_db=buswidth/2; + for (i=0;i < num_db;i++) { + if (par->gpio.db[i] < 0) { + dev_err(par->info->device, "Missing info about 'db%02d' gpio. Aborting.\n", i); + return -EINVAL; + } + } + + return 0; +} + +static struct fbtft_display flex_display = { }; + +static int flexfb_probe_common(struct spi_device *sdev, struct platform_device *pdev) +{ + struct device *dev; + struct fb_info *info; + struct fbtft_par *par; + int ret; + + initp = init; + initp_num = init_num; + + if (sdev) + dev = &sdev->dev; + else + dev = &pdev->dev; + + fbtft_init_dbg(dev, "%s(%s)\n", __func__, sdev ? "'SPI device'" : "'Platform device'"); + + if (chip) { + + if (!strcmp(chip, "st7735r")) { + if (!width) + width = 128; + if (!height) + height = 160; + if (init_num == 0) { + initp = st7735r_init; + initp_num = ARRAY_SIZE(st7735r_init); + } + + + } else if (!strcmp(chip, "hx8340bn")) { + if (!width) + width = 176; + if (!height) + height = 220; + setaddrwin = 0; + if (init_num == 0) { + initp = hx8340bn_init; + initp_num = ARRAY_SIZE(hx8340bn_init); + } + + + } else if (!strcmp(chip, "ili9225")) { + if (!width) + width = 176; + if (!height) + height = 220; + setaddrwin = 0; + regwidth = 16; + if (init_num == 0) { + initp = ili9225_init; + initp_num = ARRAY_SIZE(ili9225_init); + } + + + + } else if (!strcmp(chip, "ili9320")) { + if (!width) + width = 240; + if (!height) + height = 320; + setaddrwin = 1; + regwidth = 16; + if (init_num == 0) { + initp = ili9320_init; + initp_num = ARRAY_SIZE(ili9320_init); + } + + + } else if (!strcmp(chip, "ili9325")) { + if (!width) + width = 240; + if (!height) + height = 320; + setaddrwin = 1; + regwidth = 16; + if (init_num == 0) { + initp = ili9325_init; + initp_num = ARRAY_SIZE(ili9325_init); + } + + } else if (!strcmp(chip, "ili9341")) { + if (!width) + width = 240; + if (!height) + height = 320; + setaddrwin = 0; + regwidth = 8; + if (init_num == 0) { + initp = ili9341_init; + initp_num = ARRAY_SIZE(ili9341_init); + } + + + } else if (!strcmp(chip, "ssd1289")) { + if (!width) + width = 240; + if (!height) + height = 320; + setaddrwin = 2; + regwidth = 16; + if (init_num == 0) { + initp = ssd1289_init; + initp_num = ARRAY_SIZE(ssd1289_init); + } + + + + } else if (!strcmp(chip, "ssd1351")) { + if (!width) + width = 128; + if (!height) + height = 128; + setaddrwin = 3; + if (init_num == 0) { + initp = ssd1351_init; + initp_num = ARRAY_SIZE(ssd1351_init); + } + } else { + dev_err(dev, "chip=%s is not supported\n", chip); + return -EINVAL; + } + } + + if (width == 0 || height == 0) { + dev_err(dev, "argument(s) missing: width and height has to be set.\n"); + return -EINVAL; + } + flex_display.width = width; + flex_display.height = height; + fbtft_init_dbg(dev, "Display resolution: %dx%d\n", width, height); + fbtft_init_dbg(dev, "chip = %s\n", chip ? chip : "not set"); + fbtft_init_dbg(dev, "setaddrwin = %d\n", setaddrwin); + fbtft_init_dbg(dev, "regwidth = %d\n", regwidth); + fbtft_init_dbg(dev, "buswidth = %d\n", buswidth); + + info = fbtft_framebuffer_alloc(&flex_display, dev); + if (!info) + return -ENOMEM; + + par = info->par; + if (sdev) + par->spi = sdev; + else + par->pdev = pdev; + if (!par->init_sequence) + par->init_sequence = initp; + par->fbtftops.init_display = fbtft_init_display; + + /* registerwrite functions */ + switch (regwidth) { + case 8: + par->fbtftops.write_register = fbtft_write_reg8_bus8; + break; + case 16: + par->fbtftops.write_register = fbtft_write_reg16_bus8; + break; + default: + dev_err(dev, "argument 'regwidth': %d is not supported.\n", regwidth); + return -EINVAL; + } + + /* bus functions */ + if (sdev) { + par->fbtftops.write = fbtft_write_spi; + switch (buswidth) { + case 8: + par->fbtftops.write_vmem = fbtft_write_vmem16_bus8; + if (!par->startbyte) + par->fbtftops.verify_gpios = flexfb_verify_gpios_dc; + break; + case 9: + if (regwidth == 16) { + dev_err(dev, "argument 'regwidth': %d is not supported with buswidth=%d and SPI.\n", regwidth, buswidth); + return -EINVAL; + } + par->fbtftops.write_register = fbtft_write_reg8_bus9; + par->fbtftops.write_vmem = fbtft_write_vmem16_bus9; + sdev->bits_per_word=9; + ret = sdev->master->setup(sdev); + if (ret) { + dev_warn(dev, + "9-bit SPI not available, emulating using 8-bit.\n"); + sdev->bits_per_word = 8; + ret = sdev->master->setup(sdev); + if (ret) + goto out_release; + /* allocate buffer with room for dc bits */ + par->extra = devm_kzalloc(par->info->device, + par->txbuf.len + (par->txbuf.len / 8) + 8, + GFP_KERNEL); + if (!par->extra) { + ret = -ENOMEM; + goto out_release; + } + par->fbtftops.write = fbtft_write_spi_emulate_9; + } + break; + default: + dev_err(dev, "argument 'buswidth': %d is not supported with SPI.\n", buswidth); + return -EINVAL; + } + } else { + par->fbtftops.verify_gpios = flexfb_verify_gpios_db; + switch (buswidth) { + case 8: + par->fbtftops.write = fbtft_write_gpio8_wr; + par->fbtftops.write_vmem = fbtft_write_vmem16_bus8; + break; + case 16: + par->fbtftops.write_register = fbtft_write_reg16_bus16; + if (latched) + par->fbtftops.write = fbtft_write_gpio16_wr_latched; + else + par->fbtftops.write = fbtft_write_gpio16_wr; + par->fbtftops.write_vmem = fbtft_write_vmem16_bus16; + break; + default: + dev_err(dev, "argument 'buswidth': %d is not supported with parallel.\n", buswidth); + return -EINVAL; + } + } + + /* set_addr_win function */ + switch (setaddrwin) { + case 0: + /* use default */ + break; + case 1: + par->fbtftops.set_addr_win = flexfb_set_addr_win_1; + break; + case 2: + par->fbtftops.set_addr_win = flexfb_set_addr_win_2; + break; + case 3: + par->fbtftops.set_addr_win = set_addr_win_3; + break; + default: + dev_err(dev, "argument 'setaddrwin': unknown value %d.\n", setaddrwin); + return -EINVAL; + } + + if (!nobacklight) + par->fbtftops.register_backlight = fbtft_register_backlight; + + ret = fbtft_register_framebuffer(info); + if (ret < 0) + goto out_release; + + return 0; + +out_release: + fbtft_framebuffer_release(info); + + return ret; +} + +static int flexfb_remove_common(struct device *dev, struct fb_info *info) +{ + struct fbtft_par *par; + + if (!info) + return -EINVAL; + par = info->par; + if (par) + fbtft_par_dbg(DEBUG_DRIVER_INIT_FUNCTIONS, par, + "%s()\n", __func__); + fbtft_unregister_framebuffer(info); + fbtft_framebuffer_release(info); + + return 0; +} + +static int flexfb_probe_spi(struct spi_device *spi) +{ + return flexfb_probe_common(spi, NULL); +} + +static int flexfb_remove_spi(struct spi_device *spi) +{ + struct fb_info *info = spi_get_drvdata(spi); + + return flexfb_remove_common(&spi->dev, info); +} + +static int flexfb_probe_pdev(struct platform_device *pdev) +{ + return flexfb_probe_common(NULL, pdev); +} + +static int flexfb_remove_pdev(struct platform_device *pdev) +{ + struct fb_info *info = platform_get_drvdata(pdev); + + return flexfb_remove_common(&pdev->dev, info); +} + +static struct spi_driver flexfb_spi_driver = { + .driver = { + .name = DRVNAME, + .owner = THIS_MODULE, + }, + .probe = flexfb_probe_spi, + .remove = flexfb_remove_spi, +}; + +static const struct platform_device_id flexfb_platform_ids[] = { + { "flexpfb", 0 }, + { }, +}; + +static struct platform_driver flexfb_platform_driver = { + .driver = { + .name = DRVNAME, + .owner = THIS_MODULE, + }, + .id_table = flexfb_platform_ids, + .probe = flexfb_probe_pdev, + .remove = flexfb_remove_pdev, +}; + +static int __init flexfb_init(void) +{ + int ret, ret2; + + ret = spi_register_driver(&flexfb_spi_driver); + ret2 = platform_driver_register(&flexfb_platform_driver); + if (ret < 0) + return ret; + return ret2; +} + +static void __exit flexfb_exit(void) +{ + spi_unregister_driver(&flexfb_spi_driver); + platform_driver_unregister(&flexfb_platform_driver); +} + +/* ------------------------------------------------------------------------- */ + +module_init(flexfb_init); +module_exit(flexfb_exit); + +MODULE_DESCRIPTION("Generic FB driver for TFT LCD displays"); +MODULE_AUTHOR("Noralf Tronnes"); +MODULE_LICENSE("GPL");