From a23f3b981dce544fba8235c2ce68540c65904ab8 Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Fri, 5 Feb 2016 19:38:24 +0100 Subject: [PATCH] Added Cubox [DEV] and Guitar [NEXT] alternative legacy kernels --- configuration.sh | 18 +- .../kernel/cubox-dev/01-packaging-cubox.patch | 240 + .../kernel/cubox-dev/02-headers-packing.patch | 13 + .../cubox-dev/403-ath_regd_optional.patch | 58 + patch/kernel/cubox-dev/fbtft_drivers.patch | 12012 ++++++++++++++++ ...lock_atheros_regulatory_restrictions.patch | 70 + .../s500-next/01-packagin-guitar-next.patch | 311 + .../kernel/s500-next/02-headers-packing.patch | 13 + patch/kernel/s500-next/patch-3.10.94-95.patch | 1222 ++ patch/kernel/s500-next/patch-3.10.95-96.patch | 1497 ++ 10 files changed, 15447 insertions(+), 7 deletions(-) create mode 100644 patch/kernel/cubox-dev/01-packaging-cubox.patch create mode 100644 patch/kernel/cubox-dev/02-headers-packing.patch create mode 100644 patch/kernel/cubox-dev/403-ath_regd_optional.patch create mode 100644 patch/kernel/cubox-dev/fbtft_drivers.patch create mode 100644 patch/kernel/cubox-dev/unlock_atheros_regulatory_restrictions.patch create mode 100644 patch/kernel/s500-next/01-packagin-guitar-next.patch create mode 100644 patch/kernel/s500-next/02-headers-packing.patch create mode 100644 patch/kernel/s500-next/patch-3.10.94-95.patch create mode 100644 patch/kernel/s500-next/patch-3.10.95-96.patch diff --git a/configuration.sh b/configuration.sh index 9e6a2a38a..8d82426f5 100644 --- a/configuration.sh +++ b/configuration.sh @@ -224,7 +224,7 @@ cubox-i)#enabled #description Freescale iMx dual/quad core Wifi - #build 3 + #build 6 LINUXFAMILY="cubox" BOOTCONFIG="mx6_cubox-i_config" MODULES="bonding" @@ -255,7 +255,7 @@ guitar)#enabled #description S500 Lemaker Guitar Action quad core - #build 1 + #build 3 LINUXFAMILY="s500" OFFSET="16" BOOTSIZE="16" @@ -391,15 +391,16 @@ ;; cubox) - KERNEL_DEFAULT='https://github.com/linux4kix/linux-linaro-stable-mx6' - #KERNEL_DEFAULT='https://github.com/SolidRun/linux-fslc' - KERNEL_DEFAULT_BRANCH="linux-linaro-lsk-v3.14-mx6" - #KERNEL_DEFAULT_BRANCH="3.14-1.0.x-mx6-sr" + KERNEL_DEFAULT='https://github.com/linux4kix/linux-linaro-stable-mx6' + KERNEL_DEFAULT_BRANCH="linux-linaro-lsk-v3.14-mx6" KERNEL_DEFAULT_SOURCE="linux-cubox" KERNEL_NEXT='git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git' [ "$USE_MAINLINE_GOOGLE_MIRROR" = "yes" ] && KERNEL_NEXT='https://kernel.googlesource.com/pub/scm/linux/kernel/git/stable/linux-stable' KERNEL_NEXT_BRANCH="v"`wget -qO- https://www.kernel.org/finger_banner | grep "The latest st" | awk '{print $NF}' | head -1` - KERNEL_NEXT_SOURCE="linux-vanilla" + KERNEL_NEXT_SOURCE="linux-vanilla" + KERNEL_DEV='https://github.com/SolidRun/linux-fslc' + KERNEL_DEV_BRANCH="3.14-1.0.x-mx6-sr" + KERNEL_DEV_SOURCE="linux-cubox" UBOOT_DEFAULT="https://github.com/SolidRun/u-boot-imx6" UBOOT_DEFAULT_BRANCH="imx6" UBOOT_DEFAULT_SOURCE="u-boot-cubox" @@ -415,6 +416,9 @@ KERNEL_DEFAULT='https://github.com/LeMaker/linux-actions' KERNEL_DEFAULT_BRANCH="s500-master" KERNEL_DEFAULT_SOURCE="linux-s500" + KERNEL_NEXT='https://github.com/LeMaker/linux-actions' + KERNEL_NEXT_BRANCH="linux-linaro-3.10.94-test" + KERNEL_NEXT_SOURCE="linux-s500" UBOOT_DEFAULT="https://github.com/LeMaker/u-boot-actions" UBOOT_DEFAULT_BRANCH="s500-master" UBOOT_DEFAULT_SOURCE="u-boot-s500" diff --git a/patch/kernel/cubox-dev/01-packaging-cubox.patch b/patch/kernel/cubox-dev/01-packaging-cubox.patch new file mode 100644 index 000000000..05a95dbdd --- /dev/null +++ b/patch/kernel/cubox-dev/01-packaging-cubox.patch @@ -0,0 +1,240 @@ +--- a/scripts/package/builddeb ++++ b/scripts/package/builddeb +@@ -35,13 +35,15 @@ + sparc*) + debarch=sparc ;; + s390*) +- debarch=s390 ;; ++ debarch=s390$(grep -q CONFIG_64BIT=y $KCONFIG_CONFIG && echo x || true) ;; + ppc*) +- debarch=powerpc ;; ++ debarch=$(grep -q CPU_LITTLE_ENDIAN=y $KCONFIG_CONFIG && echo ppc64el || echo powerpc) ;; + parisc*) + debarch=hppa ;; + mips*) + debarch=mips$(grep -q CPU_LITTLE_ENDIAN=y $KCONFIG_CONFIG && echo el || true) ;; ++ arm64) ++ debarch=arm64 ;; + arm*) + debarch=arm$(grep -q CONFIG_AEABI=y $KCONFIG_CONFIG && echo el || true) ;; + *) +@@ -62,7 +64,7 @@ + fi + + # Create the package +- dpkg-gencontrol -isp $forcearch -Vkernel:debarch="${debarch:-$(dpkg --print-architecture)}" -p$pname -P"$pdir" ++ dpkg-gencontrol $forcearch -Vkernel:debarch="${debarch:-$(dpkg --print-architecture)}" -p$pname -P"$pdir" + dpkg --build "$pdir" .. + } + +@@ -78,11 +80,13 @@ + fwdir="$objtree/debian/fwtmp" + kernel_headers_dir="$objtree/debian/hdrtmp" + libc_headers_dir="$objtree/debian/headertmp" ++dtb_dir="$objtree/debian/dtbtmp" + dbg_dir="$objtree/debian/dbgtmp" +-packagename=linux-image-$version +-fwpackagename=linux-firmware-image-$version +-kernel_headers_packagename=linux-headers-$version +-libc_headers_packagename=linux-libc-dev ++packagename=linux-image-dev"$LOCALVERSION" ++fwpackagename=linux-firmware-image-dev"$LOCALVERSION" ++kernel_headers_packagename=linux-headers-dev"$LOCALVERSION" ++dtb_packagename=linux-dtb-dev"$LOCALVERSION" ++libc_headers_packagename=linux-libc-dev-dev"$LOCALVERSION" + dbg_packagename=$packagename-dbg + + if [ "$ARCH" = "um" ] ; then +@@ -106,13 +110,17 @@ + BUILD_DEBUG="$(grep -s '^CONFIG_DEBUG_INFO=y' $KCONFIG_CONFIG || true)" + + # Setup the directory structure +-rm -rf "$tmpdir" "$fwdir" "$kernel_headers_dir" "$libc_headers_dir" "$dbg_dir" ++rm -rf "$tmpdir" "$fwdir" "$kernel_headers_dir" "$libc_headers_dir" "$dbg_dir" "$dtb_dir" + mkdir -m 755 -p "$tmpdir/DEBIAN" + mkdir -p "$tmpdir/lib" "$tmpdir/boot" "$tmpdir/usr/share/doc/$packagename" + mkdir -m 755 -p "$fwdir/DEBIAN" + mkdir -p "$fwdir/lib/firmware/$version/" "$fwdir/usr/share/doc/$fwpackagename" + mkdir -m 755 -p "$libc_headers_dir/DEBIAN" + mkdir -p "$libc_headers_dir/usr/share/doc/$libc_headers_packagename" ++ ++mkdir -m 755 -p "$dtb_dir/DEBIAN" ++mkdir -p "$dtb_dir/boot/dtb" "$dtb_dir/usr/share/doc/$dtb_packagename" ++ + mkdir -m 755 -p "$kernel_headers_dir/DEBIAN" + mkdir -p "$kernel_headers_dir/usr/share/doc/$kernel_headers_packagename" + mkdir -p "$kernel_headers_dir/lib/modules/$version/" +@@ -130,7 +138,7 @@ + cp System.map "$tmpdir/usr/lib/uml/modules/$version/System.map" + cp $KCONFIG_CONFIG "$tmpdir/usr/share/doc/$packagename/config" + gzip "$tmpdir/usr/share/doc/$packagename/config" +-else ++else + cp System.map "$tmpdir/boot/System.map-$version" + cp $KCONFIG_CONFIG "$tmpdir/boot/config-$version" + fi +@@ -150,21 +158,25 @@ + rmdir "$tmpdir/lib/modules/$version" + fi + if [ -n "$BUILD_DEBUG" ] ; then +- ( +- cd $tmpdir +- for module in $(find lib/modules/ -name *.ko); do +- mkdir -p $(dirname $dbg_dir/usr/lib/debug/$module) +- # only keep debug symbols in the debug file +- $OBJCOPY --only-keep-debug $module $dbg_dir/usr/lib/debug/$module +- # strip original module from debug symbols +- $OBJCOPY --strip-debug $module +- # then add a link to those +- $OBJCOPY --add-gnu-debuglink=$dbg_dir/usr/lib/debug/$module $module +- done +- ) ++ for module in $(find $tmpdir/lib/modules/ -name *.ko -printf '%P\n'); do ++ module=lib/modules/$module ++ mkdir -p $(dirname $dbg_dir/usr/lib/debug/$module) ++ # only keep debug symbols in the debug file ++ $OBJCOPY --only-keep-debug $tmpdir/$module $dbg_dir/usr/lib/debug/$module ++ # strip original module from debug symbols ++ $OBJCOPY --strip-debug $tmpdir/$module ++ # then add a link to those ++ $OBJCOPY --add-gnu-debuglink=$dbg_dir/usr/lib/debug/$module $tmpdir/$module ++ done + fi + fi + ++if grep -q '^CONFIG_OF=y' $KCONFIG_CONFIG ; then ++ mkdir -p "$tmpdir/boot/dtb" ++ cp $objtree/arch/arm/boot/dts/*.dtb $dtb_dir/boot/dtb ++ #INSTALL_DTBS_PATH="$dtb_dir/boot/dtb" $MAKE KBUILD_SRC= dtbs_install ++fi ++ + if [ "$ARCH" != "um" ]; then + $MAKE headers_check KBUILD_SRC= + $MAKE headers_install KBUILD_SRC= INSTALL_HDR_PATH="$libc_headers_dir/usr" +@@ -189,9 +201,11 @@ + set -e + + # Pass maintainer script parameters to hook scripts ++ + export DEB_MAINT_PARAMS="\$*" + + # Tell initramfs builder whether it's wanted ++ + export INITRD=$want_initrd + + test -d $debhookdir/$script.d && run-parts --arg="$version" --arg="/$installed_image_path" $debhookdir/$script.d +@@ -200,6 +214,15 @@ + chmod 755 "$tmpdir/DEBIAN/$script" + done + ++## ++## Create sym link to kernel image ++## ++kernel_tmp_version="${installed_image_path////\\/}" ++sed -e "s#exit 0#ln -sf $(basename $kernel_tmp_version) /boot/zImage || cp /$kernel_tmp_version /boot/zImage#g" -i $tmpdir/DEBIAN/postinst ++#echo "touch /boot/.next" >> $tmpdir/DEBIAN/postinst ++echo "exit 0" >> $tmpdir/DEBIAN/postinst ++ ++ + # Try to determine maintainer and email values + if [ -n "$DEBEMAIL" ]; then + email=$DEBEMAIL +@@ -217,9 +240,20 @@ + fi + maintainer="$name <$email>" + ++# Try to determine distribution ++if [ -n "$KDEB_CHANGELOG_DIST" ]; then ++ distribution=$KDEB_CHANGELOG_DIST ++elif distribution=$(lsb_release -cs 2>/dev/null) && [ -n "$distribution" ]; then ++ : # nothing to do in this case ++else ++ distribution="unstable" ++ echo >&2 "Using default distribution of 'unstable' in the changelog" ++ echo >&2 "Install lsb-release or set \$KDEB_CHANGELOG_DIST explicitly" ++fi ++ + # Generate a simple changelog template + cat < debian/changelog +-linux-upstream ($packageversion) unstable; urgency=low ++linux-upstream ($packageversion) $distribution; urgency=low + + * Custom built Linux kernel. + +@@ -233,10 +267,10 @@ + The sources may be found at most Linux ftp sites, including: + ftp://ftp.kernel.org/pub/linux/kernel + +-Copyright: 1991 - 2009 Linus Torvalds and others. ++Copyright: 1991 - 2015 Linus Torvalds and others. + + The git repository for mainline kernel development is at: +-git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git ++git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git + + 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 +@@ -287,14 +321,22 @@ + + fi + +-# Build header package +-(cd $srctree; find . -name Makefile\* -o -name Kconfig\* -o -name \*.pl > "$objtree/debian/hdrsrcfiles") +-(cd $srctree; find arch/$SRCARCH/include include scripts -type f >> "$objtree/debian/hdrsrcfiles") +-(cd $objtree; find arch/$SRCARCH/include Module.symvers include scripts -type f >> "$objtree/debian/hdrobjfiles") ++# Build kernel header package ++(cd $srctree; find . -name Makefile\* -o -name Kconfig\* -o -name \*.pl) > "$objtree/debian/hdrsrcfiles" ++(cd $srctree; find arch/$SRCARCH/include include scripts -type f) >> "$objtree/debian/hdrsrcfiles" ++(cd $srctree; find arch/$SRCARCH -name module.lds -o -name Kbuild.platforms -o -name Platform) >> "$objtree/debian/hdrsrcfiles" ++(cd $srctree; find $(find arch/$SRCARCH -name include -o -name scripts -type d) -type f) >> "$objtree/debian/hdrsrcfiles" ++(cd $objtree; find arch/$SRCARCH/include Module.symvers include scripts -type f) >> "$objtree/debian/hdrobjfiles" + destdir=$kernel_headers_dir/usr/src/linux-headers-$version + mkdir -p "$destdir" +-(cd $srctree; tar -c -f - -T "$objtree/debian/hdrsrcfiles") | (cd $destdir; tar -xf -) +-(cd $objtree; tar -c -f - -T "$objtree/debian/hdrobjfiles") | (cd $destdir; tar -xf -) ++######################## headers patch ++ZACNI=$(pwd) ++cd $destdir ++patch -p1 < /tmp/headers-debian-byteshift.patch ++cd $ZACNI ++######################## headers patch ++(cd $srctree; tar -c -f - -T -) < "$objtree/debian/hdrsrcfiles" | (cd $destdir; tar -xf -) ++(cd $objtree; tar -c -f - -T -) < "$objtree/debian/hdrobjfiles" | (cd $destdir; tar -xf -) + (cd $objtree; cp $KCONFIG_CONFIG $destdir/.config) # copy .config manually to be where it's expected to be + ln -sf "/usr/src/linux-headers-$version" "$kernel_headers_dir/lib/modules/$version/build" + rm -f "$objtree/debian/hdrsrcfiles" "$objtree/debian/hdrobjfiles" +@@ -302,7 +344,7 @@ + cat <> debian/control + + Package: $kernel_headers_packagename +-Provides: linux-headers, linux-headers-2.6 ++Provides: linux-headers + Architecture: any + Description: Linux kernel headers for $KERNELRELEASE on \${kernel:debarch} + This package provides kernel header files for $KERNELRELEASE on \${kernel:debarch} +@@ -328,6 +370,16 @@ + + cat <> debian/control + ++Package: $dtb_packagename ++Architecture: any ++Description: Linux DTB, version $version ++ This package contains device blobs from the Linux kernel, version $version. ++EOF ++ ++create_package "$dtb_packagename" "$dtb_dir" ++ ++cat <> debian/control ++ + Package: $libc_headers_packagename + Section: devel + Provides: linux-kernel-headers +@@ -339,7 +391,7 @@ + + if [ "$ARCH" != "um" ]; then + create_package "$kernel_headers_packagename" "$kernel_headers_dir" +- create_package "$libc_headers_packagename" "$libc_headers_dir" ++# create_package "$libc_headers_packagename" "$libc_headers_dir" + fi + + create_package "$packagename" "$tmpdir" diff --git a/patch/kernel/cubox-dev/02-headers-packing.patch b/patch/kernel/cubox-dev/02-headers-packing.patch new file mode 100644 index 000000000..d27fd190d --- /dev/null +++ b/patch/kernel/cubox-dev/02-headers-packing.patch @@ -0,0 +1,13 @@ +diff --git a/scripts/package/builddeb b/scripts/package/builddeb +index 6c3b038..616aa9b 100755 +--- a/scripts/package/builddeb ++++ b/scripts/package/builddeb +@@ -334,6 +334,8 @@ mkdir -p "$destdir" + ln -sf "/usr/src/linux-headers-$version" "$kernel_headers_dir/lib/modules/$version/build" + rm -f "$objtree/debian/hdrsrcfiles" "$objtree/debian/hdrobjfiles" + ++(cd "$destdir"; make M=scripts clean) ++ + cat <> debian/control + + Package: $kernel_headers_packagename diff --git a/patch/kernel/cubox-dev/403-ath_regd_optional.patch b/patch/kernel/cubox-dev/403-ath_regd_optional.patch new file mode 100644 index 000000000..8c79e47e6 --- /dev/null +++ b/patch/kernel/cubox-dev/403-ath_regd_optional.patch @@ -0,0 +1,58 @@ +--- a/drivers/net/wireless/ath/regd.c ++++ b/drivers/net/wireless/ath/regd.c +@@ -341,6 +341,10 @@ ath_reg_apply_beaconing_flags(struct wip + struct ieee80211_channel *ch; + unsigned int i; + ++#ifdef CPTCFG_ATH_USER_REGD ++ return; ++#endif ++ + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + if (!wiphy->bands[band]) + continue; +@@ -374,6 +378,10 @@ ath_reg_apply_ir_flags(struct wiphy *wip + { + struct ieee80211_supported_band *sband; + ++#ifdef CPTCFG_ATH_USER_REGD ++ return; ++#endif ++ + sband = wiphy->bands[IEEE80211_BAND_2GHZ]; + if (!sband) + return; +@@ -402,6 +410,10 @@ static void ath_reg_apply_radar_flags(st + struct ieee80211_channel *ch; + unsigned int i; + ++#ifdef CPTCFG_ATH_USER_REGD ++ return; ++#endif ++ + if (!wiphy->bands[IEEE80211_BAND_5GHZ]) + return; + +@@ -631,6 +643,10 @@ ath_regd_init_wiphy(struct ath_regulator + { + const struct ieee80211_regdomain *regd; + ++#ifdef CPTCFG_ATH_USER_REGD ++ return 0; ++#endif ++ + wiphy->reg_notifier = reg_notifier; + wiphy->regulatory_flags |= REGULATORY_STRICT_REG | + REGULATORY_CUSTOM_REG; +--- a/drivers/net/wireless/ath/Kconfig ++++ b/drivers/net/wireless/ath/Kconfig +@@ -21,6 +21,9 @@ menuconfig ATH_CARDS + + if ATH_CARDS + ++config ATH_USER_REGD ++ bool "Do not enforce EEPROM regulatory restrictions" ++ + config ATH_DEBUG + bool "Atheros wireless debugging" + ---help--- \ No newline at end of file diff --git a/patch/kernel/cubox-dev/fbtft_drivers.patch b/patch/kernel/cubox-dev/fbtft_drivers.patch new file mode 100644 index 000000000..8ecb55230 --- /dev/null +++ b/patch/kernel/cubox-dev/fbtft_drivers.patch @@ -0,0 +1,12012 @@ +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"); diff --git a/patch/kernel/cubox-dev/unlock_atheros_regulatory_restrictions.patch b/patch/kernel/cubox-dev/unlock_atheros_regulatory_restrictions.patch new file mode 100644 index 000000000..65324ca24 --- /dev/null +++ b/patch/kernel/cubox-dev/unlock_atheros_regulatory_restrictions.patch @@ -0,0 +1,70 @@ +diff --git a/drivers/net/wireless/ath/regd.c b/drivers/net/wireless/ath/regd.c +index ccc4c71..71a4d00 100644 +--- a/drivers/net/wireless/ath/regd.c ++++ b/drivers/net/wireless/ath/regd.c +@@ -49,12 +49,9 @@ static int __ath_regd_init(struct ath_regulatory *reg); + #define ATH9K_5GHZ_5725_5850 REG_RULE(5725-10, 5850+10, 40, 0, 30,\ + NL80211_RRF_PASSIVE_SCAN | NL80211_RRF_NO_IBSS) + +-#define ATH9K_2GHZ_ALL ATH9K_2GHZ_CH01_11, \ +- ATH9K_2GHZ_CH12_13, \ +- ATH9K_2GHZ_CH14 ++#define ATH9K_2GHZ_ALL REG_RULE(2400, 2483, 40, 0, 30, 0) + +-#define ATH9K_5GHZ_ALL ATH9K_5GHZ_5150_5350, \ +- ATH9K_5GHZ_5470_5850 ++#define ATH9K_5GHZ_ALL REG_RULE(5140, 5860, 40, 0, 30, 0) + + /* This one skips what we call "mid band" */ + #define ATH9K_5GHZ_NO_MIDBAND ATH9K_5GHZ_5150_5350, \ +@@ -76,9 +73,8 @@ static const struct ieee80211_regdomain ath_world_regdom_63_65 = { + .n_reg_rules = 4, + .alpha2 = "99", + .reg_rules = { +- ATH9K_2GHZ_CH01_11, +- ATH9K_2GHZ_CH12_13, +- ATH9K_5GHZ_NO_MIDBAND, ++ ATH9K_2GHZ_ALL, ++ ATH9K_5GHZ_ALL, + } + }; + +@@ -87,8 +83,8 @@ static const struct ieee80211_regdomain ath_world_regdom_64 = { + .n_reg_rules = 3, + .alpha2 = "99", + .reg_rules = { +- ATH9K_2GHZ_CH01_11, +- ATH9K_5GHZ_NO_MIDBAND, ++ ATH9K_2GHZ_ALL, ++ ATH9K_5GHZ_ALL, + } + }; + +@@ -97,7 +93,7 @@ static const struct ieee80211_regdomain ath_world_regdom_66_69 = { + .n_reg_rules = 3, + .alpha2 = "99", + .reg_rules = { +- ATH9K_2GHZ_CH01_11, ++ ATH9K_2GHZ_ALL, + ATH9K_5GHZ_ALL, + } + }; +@@ -107,8 +103,7 @@ static const struct ieee80211_regdomain ath_world_regdom_67_68_6A_6C = { + .n_reg_rules = 4, + .alpha2 = "99", + .reg_rules = { +- ATH9K_2GHZ_CH01_11, +- ATH9K_2GHZ_CH12_13, ++ ATH9K_2GHZ_ALL, + ATH9K_5GHZ_ALL, + } + }; +@@ -174,7 +169,7 @@ EXPORT_SYMBOL(ath_is_49ghz_allowed); + /* Frequency is one where radar detection is required */ + static bool ath_is_radar_freq(u16 center_freq) + { +- return (center_freq >= 5260 && center_freq <= 5700); ++ return false; + } + + /* diff --git a/patch/kernel/s500-next/01-packagin-guitar-next.patch b/patch/kernel/s500-next/01-packagin-guitar-next.patch new file mode 100644 index 000000000..16dc9e900 --- /dev/null +++ b/patch/kernel/s500-next/01-packagin-guitar-next.patch @@ -0,0 +1,311 @@ +diff --git a/scripts/package/builddeb b/scripts/package/builddeb +old mode 100644 +new mode 100755 +index 3001ec5..d6b844d +--- a/scripts/package/builddeb ++++ b/scripts/package/builddeb +@@ -35,15 +35,17 @@ create_package() { + sparc*) + debarch=sparc ;; + s390*) +- debarch=s390 ;; ++ debarch=s390$(grep -q CONFIG_64BIT=y $KCONFIG_CONFIG && echo x || true) ;; + ppc*) +- debarch=powerpc ;; ++ debarch=$(grep -q CPU_LITTLE_ENDIAN=y $KCONFIG_CONFIG && echo ppc64el || echo powerpc) ;; + parisc*) + debarch=hppa ;; + mips*) +- debarch=mips$(grep -q CPU_LITTLE_ENDIAN=y .config && echo el) ;; ++ debarch=mips$(grep -q CPU_LITTLE_ENDIAN=y $KCONFIG_CONFIG && echo el || true) ;; ++ arm64) ++ debarch=arm64 ;; + arm*) +- debarch=arm$(grep -q CONFIG_AEABI=y .config && echo el) ;; ++ debarch=arm$(grep -q CONFIG_AEABI=y $KCONFIG_CONFIG && echo el || true) ;; + *) + echo "" >&2 + echo "** ** ** WARNING ** ** **" >&2 +@@ -62,7 +64,7 @@ create_package() { + fi + + # Create the package +- dpkg-gencontrol -isp $forcearch -Vkernel:debarch="${debarch:-$(dpkg --print-architecture)}" -p$pname -P"$pdir" ++ dpkg-gencontrol $forcearch -Vkernel:debarch="${debarch:-$(dpkg --print-architecture)}" -p$pname -P"$pdir" + dpkg --build "$pdir" .. + } + +@@ -78,49 +80,76 @@ tmpdir="$objtree/debian/tmp" + fwdir="$objtree/debian/fwtmp" + kernel_headers_dir="$objtree/debian/hdrtmp" + libc_headers_dir="$objtree/debian/headertmp" +-packagename=linux-image-$version +-fwpackagename=linux-firmware-image +-kernel_headers_packagename=linux-headers-$version +-libc_headers_packagename=linux-libc-dev ++dtb_dir="$objtree/debian/dtbtmp" ++dbg_dir="$objtree/debian/dbgtmp" ++packagename=linux-image-next"$LOCALVERSION" ++fwpackagename=linux-firmware-image-next"$LOCALVERSION" ++kernel_headers_packagename=linux-headers-next"$LOCALVERSION" ++dtb_packagename=linux-dtb-next"$LOCALVERSION" ++libc_headers_packagename=linux-libc-dev-next"$LOCALVERSION" ++dbg_packagename=$packagename-dbg + + if [ "$ARCH" = "um" ] ; then + packagename=user-mode-linux-$version + fi + ++# Not all arches have the same installed path in debian ++# XXX: have each arch Makefile export a variable of the canonical image install ++# path instead ++case $ARCH in ++um) ++ installed_image_path="usr/bin/linux-$version" ++ ;; ++parisc|mips|powerpc) ++ installed_image_path="boot/vmlinux-$version" ++ ;; ++*) ++ installed_image_path="boot/vmlinuz-$version" ++esac ++ ++BUILD_DEBUG="$(grep -s '^CONFIG_DEBUG_INFO=y' $KCONFIG_CONFIG || true)" ++ + # Setup the directory structure +-rm -rf "$tmpdir" "$fwdir" "$kernel_headers_dir" "$libc_headers_dir" ++rm -rf "$tmpdir" "$fwdir" "$kernel_headers_dir" "$libc_headers_dir" "$dbg_dir" "$dtb_dir" + mkdir -m 755 -p "$tmpdir/DEBIAN" + mkdir -p "$tmpdir/lib" "$tmpdir/boot" "$tmpdir/usr/share/doc/$packagename" + mkdir -m 755 -p "$fwdir/DEBIAN" + mkdir -p "$fwdir/lib/firmware/$version/" "$fwdir/usr/share/doc/$fwpackagename" + mkdir -m 755 -p "$libc_headers_dir/DEBIAN" + mkdir -p "$libc_headers_dir/usr/share/doc/$libc_headers_packagename" ++ ++mkdir -m 755 -p "$dtb_dir/DEBIAN" ++mkdir -p "$dtb_dir/boot/dtb" "$dtb_dir/usr/share/doc/$dtb_packagename" ++ + mkdir -m 755 -p "$kernel_headers_dir/DEBIAN" + mkdir -p "$kernel_headers_dir/usr/share/doc/$kernel_headers_packagename" + mkdir -p "$kernel_headers_dir/lib/modules/$version/" + if [ "$ARCH" = "um" ] ; then + mkdir -p "$tmpdir/usr/lib/uml/modules/$version" "$tmpdir/usr/bin" + fi ++if [ -n "$BUILD_DEBUG" ] ; then ++ mkdir -p "$dbg_dir/usr/share/doc/$dbg_packagename" ++ mkdir -m 755 -p "$dbg_dir/DEBIAN" ++fi + + # Build and install the kernel + if [ "$ARCH" = "um" ] ; then + $MAKE linux + cp System.map "$tmpdir/usr/lib/uml/modules/$version/System.map" +- cp .config "$tmpdir/usr/share/doc/$packagename/config" ++ cp $KCONFIG_CONFIG "$tmpdir/usr/share/doc/$packagename/config" + gzip "$tmpdir/usr/share/doc/$packagename/config" +- cp $KBUILD_IMAGE "$tmpdir/usr/bin/linux-$version" +-else ++else + cp System.map "$tmpdir/boot/System.map-$version" +- cp .config "$tmpdir/boot/config-$version" +- # Not all arches include the boot path in KBUILD_IMAGE +- if [ -e $KBUILD_IMAGE ]; then +- cp $KBUILD_IMAGE "$tmpdir/boot/vmlinuz-$version" +- else +- cp arch/$ARCH/boot/$KBUILD_IMAGE "$tmpdir/boot/vmlinuz-$version" +- fi ++ cp $KCONFIG_CONFIG "$tmpdir/boot/config-$version" ++fi ++# Not all arches include the boot path in KBUILD_IMAGE ++if [ -e $KBUILD_IMAGE ]; then ++ cp $KBUILD_IMAGE "$tmpdir/$installed_image_path" ++else ++ cp arch/$ARCH/boot/$KBUILD_IMAGE "$tmpdir/$installed_image_path" + fi + +-if grep -q '^CONFIG_MODULES=y' .config ; then ++if grep -q '^CONFIG_MODULES=y' $KCONFIG_CONFIG ; then + INSTALL_MOD_PATH="$tmpdir" $MAKE KBUILD_SRC= modules_install + rm -f "$tmpdir/lib/modules/$version/build" + rm -f "$tmpdir/lib/modules/$version/source" +@@ -128,6 +157,24 @@ if grep -q '^CONFIG_MODULES=y' .config ; then + mv "$tmpdir/lib/modules/$version"/* "$tmpdir/usr/lib/uml/modules/$version/" + rmdir "$tmpdir/lib/modules/$version" + fi ++ if [ -n "$BUILD_DEBUG" ] ; then ++ for module in $(find $tmpdir/lib/modules/ -name *.ko -printf '%P\n'); do ++ module=lib/modules/$module ++ mkdir -p $(dirname $dbg_dir/usr/lib/debug/$module) ++ # only keep debug symbols in the debug file ++ $OBJCOPY --only-keep-debug $tmpdir/$module $dbg_dir/usr/lib/debug/$module ++ # strip original module from debug symbols ++ $OBJCOPY --strip-debug $tmpdir/$module ++ # then add a link to those ++ $OBJCOPY --add-gnu-debuglink=$dbg_dir/usr/lib/debug/$module $tmpdir/$module ++ done ++ fi ++fi ++ ++if grep -q '^CONFIG_OF=y' $KCONFIG_CONFIG ; then ++ mkdir -p "$tmpdir/boot/dtb" ++ cp $objtree/arch/arm/boot/dts/*.dtb $dtb_dir/boot/dtb ++ #INSTALL_DTBS_PATH="$dtb_dir/boot/dtb" $MAKE KBUILD_SRC= dtbs_install + fi + + if [ "$ARCH" != "um" ]; then +@@ -137,8 +184,15 @@ fi + + # Install the maintainer scripts + # Note: hook scripts under /etc/kernel are also executed by official Debian +-# kernel packages, as well as kernel packages built using make-kpkg ++# kernel packages, as well as kernel packages built using make-kpkg. ++# make-kpkg sets $INITRD to indicate whether an initramfs is wanted, and ++# so do we; recent versions of dracut and initramfs-tools will obey this. + debhookdir=${KDEB_HOOKDIR:-/etc/kernel} ++if grep -q '^CONFIG_BLK_DEV_INITRD=y' $KCONFIG_CONFIG; then ++ want_initrd=Yes ++else ++ want_initrd=No ++fi + for script in postinst postrm preinst prerm ; do + mkdir -p "$tmpdir$debhookdir/$script.d" + cat < "$tmpdir/DEBIAN/$script" +@@ -147,14 +201,43 @@ for script in postinst postrm preinst prerm ; do + set -e + + # Pass maintainer script parameters to hook scripts ++ + export DEB_MAINT_PARAMS="\$*" + +-test -d $debhookdir/$script.d && run-parts --arg="$version" $debhookdir/$script.d ++# Tell initramfs builder whether it's wanted ++ ++export INITRD=$want_initrd ++ ++test -d $debhookdir/$script.d && run-parts --arg="$version" --arg="/$installed_image_path" $debhookdir/$script.d + exit 0 + EOF + chmod 755 "$tmpdir/DEBIAN/$script" + done + ++## ++## Create sym link to kernel image ++## ++sed -e "s/exit 0//g" -i $tmpdir/DEBIAN/postinst ++cat >> $tmpdir/DEBIAN/postinst < /dev/null 2>&1 ++rm -f /$installed_image_path /boot/zImage ++else ++ln -sf $(basename $installed_image_path) /boot/zImage > /dev/null 2>&1 || mv /$installed_image_path /boot/zImage ++fi ++rm -f /boot/.next ++exit 0 ++EOT ++## ++## FAT install workaround ++## ++sed -e "s/exit 0//g" -i $tmpdir/DEBIAN/preinst ++cat >> $tmpdir/DEBIAN/preinst <> $tmpdir/DEBIAN/preinst ++ ++ + # Try to determine maintainer and email values + if [ -n "$DEBEMAIL" ]; then + email=$DEBEMAIL +@@ -242,21 +325,30 @@ EOF + + fi + +-# Build header package +-(cd $srctree; find . -name Makefile\* -o -name Kconfig\* -o -name \*.pl > "$objtree/debian/hdrsrcfiles") +-(cd $srctree; find arch/$SRCARCH/include include scripts -type f >> "$objtree/debian/hdrsrcfiles") +-(cd $objtree; find arch/$SRCARCH/include .config Module.symvers include scripts -type f >> "$objtree/debian/hdrobjfiles") ++# Build kernel header package ++(cd $srctree; find . -name Makefile\* -o -name Kconfig\* -o -name \*.pl) > "$objtree/debian/hdrsrcfiles" ++(cd $srctree; find arch/$SRCARCH/include include scripts -type f) >> "$objtree/debian/hdrsrcfiles" ++(cd $srctree; find arch/$SRCARCH -name module.lds -o -name Kbuild.platforms -o -name Platform) >> "$objtree/debian/hdrsrcfiles" ++(cd $srctree; find $(find arch/$SRCARCH -name include -o -name scripts -type d) -type f) >> "$objtree/debian/hdrsrcfiles" ++(cd $objtree; find arch/$SRCARCH/include Module.symvers include scripts -type f) >> "$objtree/debian/hdrobjfiles" + destdir=$kernel_headers_dir/usr/src/linux-headers-$version + mkdir -p "$destdir" +-(cd $srctree; tar -c -f - -T "$objtree/debian/hdrsrcfiles") | (cd $destdir; tar -xf -) +-(cd $objtree; tar -c -f - -T "$objtree/debian/hdrobjfiles") | (cd $destdir; tar -xf -) ++######################## headers patch ++ZACNI=$(pwd) ++cd $destdir ++patch -p1 < /tmp/headers-debian-byteshift.patch ++cd $ZACNI ++######################## headers patch ++(cd $srctree; tar -c -f - -T -) < "$objtree/debian/hdrsrcfiles" | (cd $destdir; tar -xf -) ++(cd $objtree; tar -c -f - -T -) < "$objtree/debian/hdrobjfiles" | (cd $destdir; tar -xf -) ++(cd $objtree; cp $KCONFIG_CONFIG $destdir/.config) # copy .config manually to be where it's expected to be + ln -sf "/usr/src/linux-headers-$version" "$kernel_headers_dir/lib/modules/$version/build" + rm -f "$objtree/debian/hdrsrcfiles" "$objtree/debian/hdrobjfiles" + + cat <> debian/control + + Package: $kernel_headers_packagename +-Provides: linux-headers, linux-headers-2.6 ++Provides: linux-headers + Architecture: any + Description: Linux kernel headers for $KERNELRELEASE on \${kernel:debarch} + This package provides kernel header files for $KERNELRELEASE on \${kernel:debarch} +@@ -282,6 +374,16 @@ fi + + cat <> debian/control + ++Package: $dtb_packagename ++Architecture: any ++Description: Linux DTB, version $version ++ This package contains device blobs from the Linux kernel, version $version. ++EOF ++ ++create_package "$dtb_packagename" "$dtb_dir" ++ ++cat <> debian/control ++ + Package: $libc_headers_packagename + Section: devel + Provides: linux-kernel-headers +@@ -293,9 +395,35 @@ EOF + + if [ "$ARCH" != "um" ]; then + create_package "$kernel_headers_packagename" "$kernel_headers_dir" +- create_package "$libc_headers_packagename" "$libc_headers_dir" ++# create_package "$libc_headers_packagename" "$libc_headers_dir" + fi + + create_package "$packagename" "$tmpdir" + +-exit 0 ++if [ -n "$BUILD_DEBUG" ] ; then ++ # Build debug package ++ # Different tools want the image in different locations ++ # perf ++ mkdir -p $dbg_dir/usr/lib/debug/lib/modules/$version/ ++ cp vmlinux $dbg_dir/usr/lib/debug/lib/modules/$version/ ++ # systemtap ++ mkdir -p $dbg_dir/usr/lib/debug/boot/ ++ ln -s ../lib/modules/$version/vmlinux $dbg_dir/usr/lib/debug/boot/vmlinux-$version ++ # kdump-tools ++ ln -s lib/modules/$version/vmlinux $dbg_dir/usr/lib/debug/vmlinux-$version ++ ++ cat <> debian/control ++ ++Package: $dbg_packagename ++Section: debug ++Provides: linux-debug, linux-debug-$version ++Architecture: any ++Description: Linux kernel debugging symbols for $version ++ This package will come in handy if you need to debug the kernel. It provides ++ all the necessary debug symbols for the kernel and its modules. ++EOF ++ ++ create_package "$dbg_packagename" "$dbg_dir" ++fi ++ ++exit 0 +\ No newline at end of file diff --git a/patch/kernel/s500-next/02-headers-packing.patch b/patch/kernel/s500-next/02-headers-packing.patch new file mode 100644 index 000000000..1cf5b59ec --- /dev/null +++ b/patch/kernel/s500-next/02-headers-packing.patch @@ -0,0 +1,13 @@ +diff --git a/scripts/package/builddeb b/scripts/package/builddeb +index 6c3b038..616aa9b 100755 +--- a/scripts/package/builddeb ++++ b/scripts/package/builddeb +@@ -334,6 +334,8 @@ mkdir -p "$destdir" + ln -sf "/usr/src/linux-headers-$version" "$kernel_headers_dir/lib/modules/$version/build" + rm -f "$objtree/debian/hdrsrcfiles" "$objtree/debian/hdrobjfiles" + ++olddir="$(pwd)"; cd "$destdir"; make M=scripts clean; cd $olddir ++ + cat <> debian/control + + Package: $kernel_headers_packagename diff --git a/patch/kernel/s500-next/patch-3.10.94-95.patch b/patch/kernel/s500-next/patch-3.10.94-95.patch new file mode 100644 index 000000000..199647cdc --- /dev/null +++ b/patch/kernel/s500-next/patch-3.10.94-95.patch @@ -0,0 +1,1222 @@ +diff --git a/Makefile b/Makefile +index f73ae0748cbc..eb120001bc12 100644 +--- a/Makefile ++++ b/Makefile +@@ -1,6 +1,6 @@ + VERSION = 3 + PATCHLEVEL = 10 +-SUBLEVEL = 94 ++SUBLEVEL = 95 + EXTRAVERSION = + NAME = TOSSUG Baby Fish + +diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c +index 0f3e3047e29c..14ca13a0698b 100644 +--- a/drivers/firewire/ohci.c ++++ b/drivers/firewire/ohci.c +@@ -3670,6 +3670,11 @@ static int pci_probe(struct pci_dev *dev, + + reg_write(ohci, OHCI1394_IsoXmitIntMaskSet, ~0); + ohci->it_context_support = reg_read(ohci, OHCI1394_IsoXmitIntMaskSet); ++ /* JMicron JMB38x often shows 0 at first read, just ignore it */ ++ if (!ohci->it_context_support) { ++ ohci_notice(ohci, "overriding IsoXmitIntMask\n"); ++ ohci->it_context_support = 0xf; ++ } + reg_write(ohci, OHCI1394_IsoXmitIntMaskClear, ~0); + ohci->it_context_mask = ohci->it_context_support; + ohci->n_it = hweight32(ohci->it_context_mask); +diff --git a/drivers/net/ethernet/atheros/atl1c/atl1c_main.c b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c +index 11cdf1d43041..297c3e5ec3f3 100644 +--- a/drivers/net/ethernet/atheros/atl1c/atl1c_main.c ++++ b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c +@@ -1016,13 +1016,12 @@ static int atl1c_setup_ring_resources(struct atl1c_adapter *adapter) + sizeof(struct atl1c_recv_ret_status) * rx_desc_count + + 8 * 4; + +- ring_header->desc = pci_alloc_consistent(pdev, ring_header->size, +- &ring_header->dma); ++ ring_header->desc = dma_zalloc_coherent(&pdev->dev, ring_header->size, ++ &ring_header->dma, GFP_KERNEL); + if (unlikely(!ring_header->desc)) { +- dev_err(&pdev->dev, "pci_alloc_consistend failed\n"); ++ dev_err(&pdev->dev, "could not get memory for DMA buffer\n"); + goto err_nomem; + } +- memset(ring_header->desc, 0, ring_header->size); + /* init TPD ring */ + + tpd_ring[0].dma = roundup(ring_header->dma, 8); +diff --git a/drivers/net/ethernet/renesas/sh_eth.c b/drivers/net/ethernet/renesas/sh_eth.c +index e29fe8dbd226..b93a0fb17236 100644 +--- a/drivers/net/ethernet/renesas/sh_eth.c ++++ b/drivers/net/ethernet/renesas/sh_eth.c +@@ -1421,6 +1421,7 @@ static int sh_eth_rx(struct net_device *ndev, u32 intr_status) + desc_status >>= 16; + #endif + ++ skb = mdp->rx_skbuff[entry]; + if (desc_status & (RD_RFS1 | RD_RFS2 | RD_RFS3 | RD_RFS4 | + RD_RFS5 | RD_RFS6 | RD_RFS10)) { + ndev->stats.rx_errors++; +@@ -1436,12 +1437,11 @@ static int sh_eth_rx(struct net_device *ndev, u32 intr_status) + ndev->stats.rx_missed_errors++; + if (desc_status & RD_RFS10) + ndev->stats.rx_over_errors++; +- } else { ++ } else if (skb) { + if (!mdp->cd->hw_swap) + sh_eth_soft_swap( + phys_to_virt(ALIGN(rxdesc->addr, 4)), + pkt_len + 2); +- skb = mdp->rx_skbuff[entry]; + mdp->rx_skbuff[entry] = NULL; + if (mdp->cd->rpadir) + skb_reserve(skb, NET_IP_ALIGN); +diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c +index f8c90ea75108..7a1ff5797f12 100644 +--- a/drivers/net/phy/broadcom.c ++++ b/drivers/net/phy/broadcom.c +@@ -848,7 +848,7 @@ static struct mdio_device_id __maybe_unused broadcom_tbl[] = { + { PHY_ID_BCM5421, 0xfffffff0 }, + { PHY_ID_BCM5461, 0xfffffff0 }, + { PHY_ID_BCM5464, 0xfffffff0 }, +- { PHY_ID_BCM5482, 0xfffffff0 }, ++ { PHY_ID_BCM5481, 0xfffffff0 }, + { PHY_ID_BCM5482, 0xfffffff0 }, + { PHY_ID_BCM50610, 0xfffffff0 }, + { PHY_ID_BCM50610M, 0xfffffff0 }, +diff --git a/drivers/net/ppp/pptp.c b/drivers/net/ppp/pptp.c +index 0d5a5faaf83b..9a423435039a 100644 +--- a/drivers/net/ppp/pptp.c ++++ b/drivers/net/ppp/pptp.c +@@ -420,6 +420,9 @@ static int pptp_bind(struct socket *sock, struct sockaddr *uservaddr, + struct pptp_opt *opt = &po->proto.pptp; + int error = 0; + ++ if (sockaddr_len < sizeof(struct sockaddr_pppox)) ++ return -EINVAL; ++ + lock_sock(sk); + + opt->src_addr = sp->sa_addr.pptp; +@@ -441,6 +444,9 @@ static int pptp_connect(struct socket *sock, struct sockaddr *uservaddr, + struct flowi4 fl4; + int error = 0; + ++ if (sockaddr_len < sizeof(struct sockaddr_pppox)) ++ return -EINVAL; ++ + if (sp->sa_protocol != PX_PROTO_PPTP) + return -EINVAL; + +diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c +index 43204f4be2da..0244a1fb38f9 100644 +--- a/drivers/net/usb/qmi_wwan.c ++++ b/drivers/net/usb/qmi_wwan.c +@@ -742,6 +742,7 @@ static const struct usb_device_id products[] = { + {QMI_FIXED_INTF(0x2357, 0x9000, 4)}, /* TP-LINK MA260 */ + {QMI_FIXED_INTF(0x1bc7, 0x1200, 5)}, /* Telit LE920 */ + {QMI_FIXED_INTF(0x1bc7, 0x1201, 2)}, /* Telit LE920 */ ++ {QMI_FIXED_INTF(0x1c9e, 0x9b01, 3)}, /* XS Stick W100-2 from 4G Systems */ + {QMI_FIXED_INTF(0x0b3c, 0xc000, 4)}, /* Olivetti Olicard 100 */ + {QMI_FIXED_INTF(0x0b3c, 0xc001, 4)}, /* Olivetti Olicard 120 */ + {QMI_FIXED_INTF(0x0b3c, 0xc002, 4)}, /* Olivetti Olicard 140 */ +diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c +index 2800776b2e91..d2ea64de92df 100644 +--- a/drivers/usb/class/cdc-acm.c ++++ b/drivers/usb/class/cdc-acm.c +@@ -1726,6 +1726,11 @@ static const struct usb_device_id acm_ids[] = { + }, + #endif + ++ /* Exclude Infineon Flash Loader utility */ ++ { USB_DEVICE(0x058b, 0x0041), ++ .driver_info = IGNORE_DEVICE, ++ }, ++ + /* control interfaces without any protocol set */ + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_PROTO_NONE) }, +diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c +index 85756bd36746..9b05e88d6220 100644 +--- a/drivers/usb/core/config.c ++++ b/drivers/usb/core/config.c +@@ -117,7 +117,8 @@ static void usb_parse_ss_endpoint_companion(struct device *ddev, int cfgno, + USB_SS_MULT(desc->bmAttributes) > 3) { + dev_warn(ddev, "Isoc endpoint has Mult of %d in " + "config %d interface %d altsetting %d ep %d: " +- "setting to 3\n", desc->bmAttributes + 1, ++ "setting to 3\n", ++ USB_SS_MULT(desc->bmAttributes), + cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ss_ep_comp.bmAttributes = 2; + } +diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c +index 11a073cda1d6..92873f2773fc 100644 +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -137,6 +137,10 @@ struct usb_hub *usb_hub_to_struct_hub(struct usb_device *hdev) + + static int usb_device_supports_lpm(struct usb_device *udev) + { ++ /* Some devices have trouble with LPM */ ++ if (udev->quirks & USB_QUIRK_NO_LPM) ++ return 0; ++ + /* USB 2.1 (and greater) devices indicate LPM support through + * their USB 2.0 Extended Capabilities BOS descriptor. + */ +@@ -4289,6 +4293,8 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, + goto fail; + } + ++ usb_detect_quirks(udev); ++ + if (udev->wusb == 0 && le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0201) { + retval = usb_get_bos_descriptor(udev); + if (!retval) { +@@ -4530,7 +4536,6 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, + if (status < 0) + goto loop; + +- usb_detect_quirks(udev); + if (udev->quirks & USB_QUIRK_DELAY_INIT) + msleep(1000); + +diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c +index d4db4ea4a92d..94e9cddc05c1 100644 +--- a/drivers/usb/core/quirks.c ++++ b/drivers/usb/core/quirks.c +@@ -182,6 +182,12 @@ static const struct usb_device_id usb_interface_quirk_list[] = { + { USB_DEVICE(0x0b05, 0x17e0), .driver_info = + USB_QUIRK_IGNORE_REMOTE_WAKEUP }, + ++ /* Blackmagic Design Intensity Shuttle */ ++ { USB_DEVICE(0x1edb, 0xbd3b), .driver_info = USB_QUIRK_NO_LPM }, ++ ++ /* Blackmagic Design UltraStudio SDI */ ++ { USB_DEVICE(0x1edb, 0xbd4f), .driver_info = USB_QUIRK_NO_LPM }, ++ + { } /* terminating entry must be last */ + }; + +diff --git a/drivers/usb/host/whci/qset.c b/drivers/usb/host/whci/qset.c +index dc31c425ce01..9f1c0538b211 100644 +--- a/drivers/usb/host/whci/qset.c ++++ b/drivers/usb/host/whci/qset.c +@@ -377,6 +377,10 @@ static int qset_fill_page_list(struct whc *whc, struct whc_std *std, gfp_t mem_f + if (std->pl_virt == NULL) + return -ENOMEM; + std->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt, pl_len, DMA_TO_DEVICE); ++ if (dma_mapping_error(whc->wusbhc.dev, std->dma_addr)) { ++ kfree(std->pl_virt); ++ return -EFAULT; ++ } + + for (p = 0; p < std->num_pointers; p++) { + std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr); +diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c +index dd84416a23cd..25522e98602e 100644 +--- a/drivers/usb/serial/cp210x.c ++++ b/drivers/usb/serial/cp210x.c +@@ -132,7 +132,6 @@ static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x10C4, 0xEA60) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0xEA61) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0xEA70) }, /* Silicon Labs factory default */ +- { USB_DEVICE(0x10C4, 0xEA80) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0xEA71) }, /* Infinity GPS-MIC-1 Radio Monophone */ + { USB_DEVICE(0x10C4, 0xF001) }, /* Elan Digital Systems USBscope50 */ + { USB_DEVICE(0x10C4, 0xF002) }, /* Elan Digital Systems USBwave12 */ +diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c +index f22beda91ffc..ae29b403a7e2 100644 +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -1286,8 +1286,14 @@ next_slot: + num_bytes = 0; + btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]); + +- if (found_key.objectid > ino || +- found_key.type > BTRFS_EXTENT_DATA_KEY || ++ if (found_key.objectid > ino) ++ break; ++ if (WARN_ON_ONCE(found_key.objectid < ino) || ++ found_key.type < BTRFS_EXTENT_DATA_KEY) { ++ path->slots[0]++; ++ goto next_slot; ++ } ++ if (found_key.type > BTRFS_EXTENT_DATA_KEY || + found_key.offset > end) + break; + +diff --git a/fs/ext4/super.c b/fs/ext4/super.c +index af1eaed96a91..a7e079749425 100644 +--- a/fs/ext4/super.c ++++ b/fs/ext4/super.c +@@ -400,9 +400,13 @@ static void ext4_handle_error(struct super_block *sb) + ext4_msg(sb, KERN_CRIT, "Remounting filesystem read-only"); + sb->s_flags |= MS_RDONLY; + } +- if (test_opt(sb, ERRORS_PANIC)) ++ if (test_opt(sb, ERRORS_PANIC)) { ++ if (EXT4_SB(sb)->s_journal && ++ !(EXT4_SB(sb)->s_journal->j_flags & JBD2_REC_ERR)) ++ return; + panic("EXT4-fs (device %s): panic forced after error\n", + sb->s_id); ++ } + } + + void __ext4_error(struct super_block *sb, const char *function, +@@ -576,8 +580,12 @@ void __ext4_abort(struct super_block *sb, const char *function, + jbd2_journal_abort(EXT4_SB(sb)->s_journal, -EIO); + save_error_info(sb, function, line); + } +- if (test_opt(sb, ERRORS_PANIC)) ++ if (test_opt(sb, ERRORS_PANIC)) { ++ if (EXT4_SB(sb)->s_journal && ++ !(EXT4_SB(sb)->s_journal->j_flags & JBD2_REC_ERR)) ++ return; + panic("EXT4-fs panic from previous error\n"); ++ } + } + + void ext4_msg(struct super_block *sb, const char *prefix, const char *fmt, ...) +diff --git a/fs/jbd2/journal.c b/fs/jbd2/journal.c +index 3e7ef8874ffb..644f95e7208f 100644 +--- a/fs/jbd2/journal.c ++++ b/fs/jbd2/journal.c +@@ -2049,8 +2049,12 @@ static void __journal_abort_soft (journal_t *journal, int errno) + + __jbd2_journal_abort_hard(journal); + +- if (errno) ++ if (errno) { + jbd2_journal_update_sb_errno(journal); ++ write_lock(&journal->j_state_lock); ++ journal->j_flags |= JBD2_REC_ERR; ++ write_unlock(&journal->j_state_lock); ++ } + } + + /** +diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c +index e9be01b2cc5a..02c6eade0bdc 100644 +--- a/fs/nfs/inode.c ++++ b/fs/nfs/inode.c +@@ -1503,7 +1503,11 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr) + nfsi->attrtimeo_timestamp = now; + } + } +- invalid &= ~NFS_INO_INVALID_ATTR; ++ ++ /* Don't declare attrcache up to date if there were no attrs! */ ++ if (fattr->valid != 0) ++ invalid &= ~NFS_INO_INVALID_ATTR; ++ + /* Don't invalidate the data if we were to blame */ + if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) + || S_ISLNK(inode->i_mode))) +diff --git a/fs/nfs/nfs4client.c b/fs/nfs/nfs4client.c +index 5f8d5ffdad8f..498811c09da7 100644 +--- a/fs/nfs/nfs4client.c ++++ b/fs/nfs/nfs4client.c +@@ -32,7 +32,7 @@ static int nfs_get_cb_ident_idr(struct nfs_client *clp, int minorversion) + return ret; + idr_preload(GFP_KERNEL); + spin_lock(&nn->nfs_client_lock); +- ret = idr_alloc(&nn->cb_ident_idr, clp, 0, 0, GFP_NOWAIT); ++ ret = idr_alloc(&nn->cb_ident_idr, clp, 1, 0, GFP_NOWAIT); + if (ret >= 0) + clp->cl_cb_ident = ret; + spin_unlock(&nn->nfs_client_lock); +diff --git a/include/linux/jbd2.h b/include/linux/jbd2.h +index 0c67c1f2a890..7d4a932305be 100644 +--- a/include/linux/jbd2.h ++++ b/include/linux/jbd2.h +@@ -977,6 +977,7 @@ struct journal_s + #define JBD2_ABORT_ON_SYNCDATA_ERR 0x040 /* Abort the journal on file + * data write error in ordered + * mode */ ++#define JBD2_REC_ERR 0x080 /* The errno in the sb has been recorded */ + + /* + * Function declarations for the journaling transaction and buffer +diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h +index 49587dc22f5d..41ea53c39389 100644 +--- a/include/linux/usb/quirks.h ++++ b/include/linux/usb/quirks.h +@@ -33,4 +33,7 @@ + /* device generates spurious wakeup, ignore remote wakeup capability */ + #define USB_QUIRK_IGNORE_REMOTE_WAKEUP 0x00000200 + ++/* device can't handle Link Power Management */ ++#define USB_QUIRK_NO_LPM BIT(10) ++ + #endif /* __LINUX_USB_QUIRKS_H */ +diff --git a/include/net/af_unix.h b/include/net/af_unix.h +index e927d3e80b61..686760024572 100644 +--- a/include/net/af_unix.h ++++ b/include/net/af_unix.h +@@ -62,6 +62,7 @@ struct unix_sock { + #define UNIX_GC_CANDIDATE 0 + #define UNIX_GC_MAYBE_CYCLE 1 + struct socket_wq peer_wq; ++ wait_queue_t peer_wake; + }; + + static inline struct unix_sock *unix_sk(struct sock *sk) +diff --git a/include/net/ipv6.h b/include/net/ipv6.h +index 087370ff05f1..413e23be60d1 100644 +--- a/include/net/ipv6.h ++++ b/include/net/ipv6.h +@@ -478,6 +478,7 @@ struct ip6_create_arg { + u32 user; + const struct in6_addr *src; + const struct in6_addr *dst; ++ int iif; + u8 ecn; + }; + +diff --git a/include/net/sock.h b/include/net/sock.h +index 95dc0c8a9dac..2317d122874e 100644 +--- a/include/net/sock.h ++++ b/include/net/sock.h +@@ -352,6 +352,7 @@ struct sock { + sk_no_check : 2, + sk_userlocks : 4, + sk_protocol : 8, ++#define SK_PROTOCOL_MAX U8_MAX + sk_type : 16; + kmemcheck_bitfield_end(flags); + int sk_wmem_queued; +@@ -672,6 +673,8 @@ enum sock_flags { + SOCK_SELECT_ERR_QUEUE, /* Wake select on error queue */ + }; + ++#define SK_FLAGS_TIMESTAMP ((1UL << SOCK_TIMESTAMP) | (1UL << SOCK_TIMESTAMPING_RX_SOFTWARE)) ++ + static inline void sock_copy_flags(struct sock *nsk, struct sock *osk) + { + nsk->sk_flags = osk->sk_flags; +diff --git a/net/ax25/af_ax25.c b/net/ax25/af_ax25.c +index ba6db78a02b1..69940a723abd 100644 +--- a/net/ax25/af_ax25.c ++++ b/net/ax25/af_ax25.c +@@ -806,6 +806,9 @@ static int ax25_create(struct net *net, struct socket *sock, int protocol, + struct sock *sk; + ax25_cb *ax25; + ++ if (protocol < 0 || protocol > SK_PROTOCOL_MAX) ++ return -EINVAL; ++ + if (!net_eq(net, &init_net)) + return -EAFNOSUPPORT; + +diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c +index c9ae6b703c13..b586a3230247 100644 +--- a/net/bluetooth/sco.c ++++ b/net/bluetooth/sco.c +@@ -456,6 +456,9 @@ static int sco_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_le + if (!addr || addr->sa_family != AF_BLUETOOTH) + return -EINVAL; + ++ if (addr_len < sizeof(struct sockaddr_sco)) ++ return -EINVAL; ++ + lock_sock(sk); + + if (sk->sk_state != BT_OPEN) { +diff --git a/net/core/scm.c b/net/core/scm.c +index b4da80b1cc07..dbc6bfcdf446 100644 +--- a/net/core/scm.c ++++ b/net/core/scm.c +@@ -306,6 +306,8 @@ void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm) + err = put_user(cmlen, &cm->cmsg_len); + if (!err) { + cmlen = CMSG_SPACE(i*sizeof(int)); ++ if (msg->msg_controllen < cmlen) ++ cmlen = msg->msg_controllen; + msg->msg_control += cmlen; + msg->msg_controllen -= cmlen; + } +diff --git a/net/core/sock.c b/net/core/sock.c +index af65d17517b8..5a954fccc7d3 100644 +--- a/net/core/sock.c ++++ b/net/core/sock.c +@@ -419,8 +419,6 @@ static void sock_warn_obsolete_bsdism(const char *name) + } + } + +-#define SK_FLAGS_TIMESTAMP ((1UL << SOCK_TIMESTAMP) | (1UL << SOCK_TIMESTAMPING_RX_SOFTWARE)) +- + static void sock_disable_timestamp(struct sock *sk, unsigned long flags) + { + if (sk->sk_flags & flags) { +diff --git a/net/decnet/af_decnet.c b/net/decnet/af_decnet.c +index c21f200eed93..ca6106562769 100644 +--- a/net/decnet/af_decnet.c ++++ b/net/decnet/af_decnet.c +@@ -677,6 +677,9 @@ static int dn_create(struct net *net, struct socket *sock, int protocol, + { + struct sock *sk; + ++ if (protocol < 0 || protocol > SK_PROTOCOL_MAX) ++ return -EINVAL; ++ + if (!net_eq(net, &init_net)) + return -EAFNOSUPPORT; + +diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c +index c4adc319cc2e..975c369d4e6d 100644 +--- a/net/ipv4/af_inet.c ++++ b/net/ipv4/af_inet.c +@@ -288,6 +288,9 @@ static int inet_create(struct net *net, struct socket *sock, int protocol, + if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM) + build_ehash_secret(); + ++ if (protocol < 0 || protocol >= IPPROTO_MAX) ++ return -EINVAL; ++ + sock->state = SS_UNCONNECTED; + + /* Look for the requested type/protocol pair. */ +diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c +index 6f5f943ff395..b31553d385bb 100644 +--- a/net/ipv4/ipmr.c ++++ b/net/ipv4/ipmr.c +@@ -136,7 +136,7 @@ static int __ipmr_fill_mroute(struct mr_table *mrt, struct sk_buff *skb, + struct mfc_cache *c, struct rtmsg *rtm); + static void mroute_netlink_event(struct mr_table *mrt, struct mfc_cache *mfc, + int cmd); +-static void mroute_clean_tables(struct mr_table *mrt); ++static void mroute_clean_tables(struct mr_table *mrt, bool all); + static void ipmr_expire_process(unsigned long arg); + + #ifdef CONFIG_IP_MROUTE_MULTIPLE_TABLES +@@ -348,7 +348,7 @@ static struct mr_table *ipmr_new_table(struct net *net, u32 id) + static void ipmr_free_table(struct mr_table *mrt) + { + del_timer_sync(&mrt->ipmr_expire_timer); +- mroute_clean_tables(mrt); ++ mroute_clean_tables(mrt, true); + kfree(mrt); + } + +@@ -1199,7 +1199,7 @@ static int ipmr_mfc_add(struct net *net, struct mr_table *mrt, + * Close the multicast socket, and clear the vif tables etc + */ + +-static void mroute_clean_tables(struct mr_table *mrt) ++static void mroute_clean_tables(struct mr_table *mrt, bool all) + { + int i; + LIST_HEAD(list); +@@ -1208,8 +1208,9 @@ static void mroute_clean_tables(struct mr_table *mrt) + /* Shut down all active vif entries */ + + for (i = 0; i < mrt->maxvif; i++) { +- if (!(mrt->vif_table[i].flags & VIFF_STATIC)) +- vif_delete(mrt, i, 0, &list); ++ if (!all && (mrt->vif_table[i].flags & VIFF_STATIC)) ++ continue; ++ vif_delete(mrt, i, 0, &list); + } + unregister_netdevice_many(&list); + +@@ -1217,7 +1218,7 @@ static void mroute_clean_tables(struct mr_table *mrt) + + for (i = 0; i < MFC_LINES; i++) { + list_for_each_entry_safe(c, next, &mrt->mfc_cache_array[i], list) { +- if (c->mfc_flags & MFC_STATIC) ++ if (!all && (c->mfc_flags & MFC_STATIC)) + continue; + list_del_rcu(&c->list); + mroute_netlink_event(mrt, c, RTM_DELROUTE); +@@ -1252,7 +1253,7 @@ static void mrtsock_destruct(struct sock *sk) + NETCONFA_IFINDEX_ALL, + net->ipv4.devconf_all); + RCU_INIT_POINTER(mrt->mroute_sk, NULL); +- mroute_clean_tables(mrt); ++ mroute_clean_tables(mrt, false); + } + } + rtnl_unlock(); +diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c +index a8be45e4d34f..f89087c3cfc8 100644 +--- a/net/ipv4/tcp_input.c ++++ b/net/ipv4/tcp_input.c +@@ -5575,6 +5575,7 @@ discard: + } + + tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1; ++ tp->copied_seq = tp->rcv_nxt; + tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1; + + /* RFC1323: The window in SYN & SYN/ACK segments is +diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c +index 7c3eec386a4b..11f27a45b8ef 100644 +--- a/net/ipv4/tcp_ipv4.c ++++ b/net/ipv4/tcp_ipv4.c +@@ -1015,7 +1015,8 @@ int tcp_md5_do_add(struct sock *sk, const union tcp_md5_addr *addr, + } + + md5sig = rcu_dereference_protected(tp->md5sig_info, +- sock_owned_by_user(sk)); ++ sock_owned_by_user(sk) || ++ lockdep_is_held(&sk->sk_lock.slock)); + if (!md5sig) { + md5sig = kmalloc(sizeof(*md5sig), gfp); + if (!md5sig) +diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c +index ab5c7ad482cd..a944f1313c5f 100644 +--- a/net/ipv6/af_inet6.c ++++ b/net/ipv6/af_inet6.c +@@ -113,6 +113,9 @@ static int inet6_create(struct net *net, struct socket *sock, int protocol, + !inet_ehash_secret) + build_ehash_secret(); + ++ if (protocol < 0 || protocol >= IPPROTO_MAX) ++ return -EINVAL; ++ + /* Look for the requested type/protocol pair. */ + lookup_protocol: + err = -ESOCKTNOSUPPORT; +diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c +index bf6233cdb753..7eb7267861ac 100644 +--- a/net/ipv6/ip6_gre.c ++++ b/net/ipv6/ip6_gre.c +@@ -1541,13 +1541,11 @@ static int ip6gre_changelink(struct net_device *dev, struct nlattr *tb[], + return -EEXIST; + } else { + t = nt; +- +- ip6gre_tunnel_unlink(ign, t); +- ip6gre_tnl_change(t, &p, !tb[IFLA_MTU]); +- ip6gre_tunnel_link(ign, t); +- netdev_state_change(dev); + } + ++ ip6gre_tunnel_unlink(ign, t); ++ ip6gre_tnl_change(t, &p, !tb[IFLA_MTU]); ++ ip6gre_tunnel_link(ign, t); + return 0; + } + +diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c +index 57dd3e7d86cc..9ec416552cc5 100644 +--- a/net/ipv6/ip6mr.c ++++ b/net/ipv6/ip6mr.c +@@ -120,7 +120,7 @@ static void mr6_netlink_event(struct mr6_table *mrt, struct mfc6_cache *mfc, + int cmd); + static int ip6mr_rtm_dumproute(struct sk_buff *skb, + struct netlink_callback *cb); +-static void mroute_clean_tables(struct mr6_table *mrt); ++static void mroute_clean_tables(struct mr6_table *mrt, bool all); + static void ipmr_expire_process(unsigned long arg); + + #ifdef CONFIG_IPV6_MROUTE_MULTIPLE_TABLES +@@ -337,7 +337,7 @@ static struct mr6_table *ip6mr_new_table(struct net *net, u32 id) + static void ip6mr_free_table(struct mr6_table *mrt) + { + del_timer(&mrt->ipmr_expire_timer); +- mroute_clean_tables(mrt); ++ mroute_clean_tables(mrt, true); + kfree(mrt); + } + +@@ -1537,7 +1537,7 @@ static int ip6mr_mfc_add(struct net *net, struct mr6_table *mrt, + * Close the multicast socket, and clear the vif tables etc + */ + +-static void mroute_clean_tables(struct mr6_table *mrt) ++static void mroute_clean_tables(struct mr6_table *mrt, bool all) + { + int i; + LIST_HEAD(list); +@@ -1547,8 +1547,9 @@ static void mroute_clean_tables(struct mr6_table *mrt) + * Shut down all active vif entries + */ + for (i = 0; i < mrt->maxvif; i++) { +- if (!(mrt->vif6_table[i].flags & VIFF_STATIC)) +- mif6_delete(mrt, i, &list); ++ if (!all && (mrt->vif6_table[i].flags & VIFF_STATIC)) ++ continue; ++ mif6_delete(mrt, i, &list); + } + unregister_netdevice_many(&list); + +@@ -1557,7 +1558,7 @@ static void mroute_clean_tables(struct mr6_table *mrt) + */ + for (i = 0; i < MFC6_LINES; i++) { + list_for_each_entry_safe(c, next, &mrt->mfc6_cache_array[i], list) { +- if (c->mfc_flags & MFC_STATIC) ++ if (!all && (c->mfc_flags & MFC_STATIC)) + continue; + write_lock_bh(&mrt_lock); + list_del(&c->list); +@@ -1620,7 +1621,7 @@ int ip6mr_sk_done(struct sock *sk) + net->ipv6.devconf_all); + write_unlock_bh(&mrt_lock); + +- mroute_clean_tables(mrt); ++ mroute_clean_tables(mrt, false); + err = 0; + break; + } +diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c +index 734aec059ffd..7ba6180ff8bd 100644 +--- a/net/ipv6/mcast.c ++++ b/net/ipv6/mcast.c +@@ -1441,7 +1441,6 @@ out: + if (!err) { + ICMP6MSGOUT_INC_STATS(net, idev, ICMPV6_MLD2_REPORT); + ICMP6_INC_STATS(net, idev, ICMP6_MIB_OUTMSGS); +- IP6_UPD_PO_STATS(net, idev, IPSTATS_MIB_OUTMCAST, payload_len); + } else { + IP6_INC_STATS(net, idev, IPSTATS_MIB_OUTDISCARDS); + } +@@ -1805,7 +1804,6 @@ out: + if (!err) { + ICMP6MSGOUT_INC_STATS(net, idev, type); + ICMP6_INC_STATS(net, idev, ICMP6_MIB_OUTMSGS); +- IP6_UPD_PO_STATS(net, idev, IPSTATS_MIB_OUTMCAST, full_len); + } else + IP6_INC_STATS(net, idev, IPSTATS_MIB_OUTDISCARDS); + +diff --git a/net/ipv6/netfilter/nf_conntrack_reasm.c b/net/ipv6/netfilter/nf_conntrack_reasm.c +index 253566a8d55b..7cd623588532 100644 +--- a/net/ipv6/netfilter/nf_conntrack_reasm.c ++++ b/net/ipv6/netfilter/nf_conntrack_reasm.c +@@ -172,7 +172,7 @@ static void nf_ct_frag6_expire(unsigned long data) + /* Creation primitives. */ + static inline struct frag_queue *fq_find(struct net *net, __be32 id, + u32 user, struct in6_addr *src, +- struct in6_addr *dst, u8 ecn) ++ struct in6_addr *dst, int iif, u8 ecn) + { + struct inet_frag_queue *q; + struct ip6_create_arg arg; +@@ -182,6 +182,7 @@ static inline struct frag_queue *fq_find(struct net *net, __be32 id, + arg.user = user; + arg.src = src; + arg.dst = dst; ++ arg.iif = iif; + arg.ecn = ecn; + + read_lock_bh(&nf_frags.lock); +@@ -590,7 +591,7 @@ struct sk_buff *nf_ct_frag6_gather(struct sk_buff *skb, u32 user) + local_bh_enable(); + + fq = fq_find(net, fhdr->identification, user, &hdr->saddr, &hdr->daddr, +- ip6_frag_ecn(hdr)); ++ skb->dev ? skb->dev->ifindex : 0, ip6_frag_ecn(hdr)); + if (fq == NULL) { + pr_debug("Can't find and can't create new queue\n"); + goto ret_orig; +diff --git a/net/ipv6/reassembly.c b/net/ipv6/reassembly.c +index 1aeb473b2cc6..a1fb511da3b5 100644 +--- a/net/ipv6/reassembly.c ++++ b/net/ipv6/reassembly.c +@@ -111,7 +111,10 @@ bool ip6_frag_match(struct inet_frag_queue *q, void *a) + return fq->id == arg->id && + fq->user == arg->user && + ipv6_addr_equal(&fq->saddr, arg->src) && +- ipv6_addr_equal(&fq->daddr, arg->dst); ++ ipv6_addr_equal(&fq->daddr, arg->dst) && ++ (arg->iif == fq->iif || ++ !(ipv6_addr_type(arg->dst) & (IPV6_ADDR_MULTICAST | ++ IPV6_ADDR_LINKLOCAL))); + } + EXPORT_SYMBOL(ip6_frag_match); + +@@ -180,7 +183,7 @@ static void ip6_frag_expire(unsigned long data) + + static __inline__ struct frag_queue * + fq_find(struct net *net, __be32 id, const struct in6_addr *src, +- const struct in6_addr *dst, u8 ecn) ++ const struct in6_addr *dst, int iif, u8 ecn) + { + struct inet_frag_queue *q; + struct ip6_create_arg arg; +@@ -190,6 +193,7 @@ fq_find(struct net *net, __be32 id, const struct in6_addr *src, + arg.user = IP6_DEFRAG_LOCAL_DELIVER; + arg.src = src; + arg.dst = dst; ++ arg.iif = iif; + arg.ecn = ecn; + + read_lock(&ip6_frags.lock); +@@ -558,7 +562,7 @@ static int ipv6_frag_rcv(struct sk_buff *skb) + IPSTATS_MIB_REASMFAILS, evicted); + + fq = fq_find(net, fhdr->identification, &hdr->saddr, &hdr->daddr, +- ip6_frag_ecn(hdr)); ++ skb->dev ? skb->dev->ifindex : 0, ip6_frag_ecn(hdr)); + if (fq != NULL) { + int ret; + +diff --git a/net/irda/af_irda.c b/net/irda/af_irda.c +index a5e62ef57155..f8133ff5b081 100644 +--- a/net/irda/af_irda.c ++++ b/net/irda/af_irda.c +@@ -1105,6 +1105,9 @@ static int irda_create(struct net *net, struct socket *sock, int protocol, + + IRDA_DEBUG(2, "%s()\n", __func__); + ++ if (protocol < 0 || protocol > SK_PROTOCOL_MAX) ++ return -EINVAL; ++ + if (net != &init_net) + return -EAFNOSUPPORT; + +diff --git a/net/sctp/auth.c b/net/sctp/auth.c +index bc2fae7e67be..62433f797f3f 100644 +--- a/net/sctp/auth.c ++++ b/net/sctp/auth.c +@@ -812,8 +812,8 @@ int sctp_auth_ep_set_hmacs(struct sctp_endpoint *ep, + if (!has_sha1) + return -EINVAL; + +- memcpy(ep->auth_hmacs_list->hmac_ids, &hmacs->shmac_idents[0], +- hmacs->shmac_num_idents * sizeof(__u16)); ++ for (i = 0; i < hmacs->shmac_num_idents; i++) ++ ep->auth_hmacs_list->hmac_ids[i] = htons(hmacs->shmac_idents[i]); + ep->auth_hmacs_list->param_hdr.length = htons(sizeof(sctp_paramhdr_t) + + hmacs->shmac_num_idents * sizeof(__u16)); + return 0; +diff --git a/net/sctp/ipv6.c b/net/sctp/ipv6.c +index 422d8bdacc0d..bee032a7003f 100644 +--- a/net/sctp/ipv6.c ++++ b/net/sctp/ipv6.c +@@ -639,6 +639,7 @@ static struct sock *sctp_v6_create_accept_sk(struct sock *sk, + struct sock *newsk; + struct ipv6_pinfo *newnp, *np = inet6_sk(sk); + struct sctp6_sock *newsctp6sk; ++ struct ipv6_txoptions *opt; + + newsk = sk_alloc(sock_net(sk), PF_INET6, GFP_KERNEL, sk->sk_prot); + if (!newsk) +@@ -658,6 +659,13 @@ static struct sock *sctp_v6_create_accept_sk(struct sock *sk, + + memcpy(newnp, np, sizeof(struct ipv6_pinfo)); + ++ rcu_read_lock(); ++ opt = rcu_dereference(np->opt); ++ if (opt) ++ opt = ipv6_dup_options(newsk, opt); ++ RCU_INIT_POINTER(newnp->opt, opt); ++ rcu_read_unlock(); ++ + /* Initialize sk's sport, dport, rcv_saddr and daddr for getsockname() + * and getpeername(). + */ +diff --git a/net/sctp/socket.c b/net/sctp/socket.c +index ec5766dc3946..80bd61ae5945 100644 +--- a/net/sctp/socket.c ++++ b/net/sctp/socket.c +@@ -6969,6 +6969,9 @@ void sctp_copy_sock(struct sock *newsk, struct sock *sk, + newinet->mc_ttl = 1; + newinet->mc_index = 0; + newinet->mc_list = NULL; ++ ++ if (newsk->sk_flags & SK_FLAGS_TIMESTAMP) ++ net_enable_timestamp(); + } + + static inline void sctp_copy_descendant(struct sock *sk_to, +@@ -7149,6 +7152,13 @@ struct proto sctp_prot = { + + #if IS_ENABLED(CONFIG_IPV6) + ++#include ++static void sctp_v6_destroy_sock(struct sock *sk) ++{ ++ sctp_destroy_sock(sk); ++ inet6_destroy_sock(sk); ++} ++ + struct proto sctpv6_prot = { + .name = "SCTPv6", + .owner = THIS_MODULE, +@@ -7158,7 +7168,7 @@ struct proto sctpv6_prot = { + .accept = sctp_accept, + .ioctl = sctp_ioctl, + .init = sctp_init_sock, +- .destroy = sctp_destroy_sock, ++ .destroy = sctp_v6_destroy_sock, + .shutdown = sctp_shutdown, + .setsockopt = sctp_setsockopt, + .getsockopt = sctp_getsockopt, +diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c +index 825c029bf092..f934e7ba5eb8 100644 +--- a/net/unix/af_unix.c ++++ b/net/unix/af_unix.c +@@ -313,6 +313,118 @@ found: + return s; + } + ++/* Support code for asymmetrically connected dgram sockets ++ * ++ * If a datagram socket is connected to a socket not itself connected ++ * to the first socket (eg, /dev/log), clients may only enqueue more ++ * messages if the present receive queue of the server socket is not ++ * "too large". This means there's a second writeability condition ++ * poll and sendmsg need to test. The dgram recv code will do a wake ++ * up on the peer_wait wait queue of a socket upon reception of a ++ * datagram which needs to be propagated to sleeping would-be writers ++ * since these might not have sent anything so far. This can't be ++ * accomplished via poll_wait because the lifetime of the server ++ * socket might be less than that of its clients if these break their ++ * association with it or if the server socket is closed while clients ++ * are still connected to it and there's no way to inform "a polling ++ * implementation" that it should let go of a certain wait queue ++ * ++ * In order to propagate a wake up, a wait_queue_t of the client ++ * socket is enqueued on the peer_wait queue of the server socket ++ * whose wake function does a wake_up on the ordinary client socket ++ * wait queue. This connection is established whenever a write (or ++ * poll for write) hit the flow control condition and broken when the ++ * association to the server socket is dissolved or after a wake up ++ * was relayed. ++ */ ++ ++static int unix_dgram_peer_wake_relay(wait_queue_t *q, unsigned mode, int flags, ++ void *key) ++{ ++ struct unix_sock *u; ++ wait_queue_head_t *u_sleep; ++ ++ u = container_of(q, struct unix_sock, peer_wake); ++ ++ __remove_wait_queue(&unix_sk(u->peer_wake.private)->peer_wait, ++ q); ++ u->peer_wake.private = NULL; ++ ++ /* relaying can only happen while the wq still exists */ ++ u_sleep = sk_sleep(&u->sk); ++ if (u_sleep) ++ wake_up_interruptible_poll(u_sleep, key); ++ ++ return 0; ++} ++ ++static int unix_dgram_peer_wake_connect(struct sock *sk, struct sock *other) ++{ ++ struct unix_sock *u, *u_other; ++ int rc; ++ ++ u = unix_sk(sk); ++ u_other = unix_sk(other); ++ rc = 0; ++ spin_lock(&u_other->peer_wait.lock); ++ ++ if (!u->peer_wake.private) { ++ u->peer_wake.private = other; ++ __add_wait_queue(&u_other->peer_wait, &u->peer_wake); ++ ++ rc = 1; ++ } ++ ++ spin_unlock(&u_other->peer_wait.lock); ++ return rc; ++} ++ ++static void unix_dgram_peer_wake_disconnect(struct sock *sk, ++ struct sock *other) ++{ ++ struct unix_sock *u, *u_other; ++ ++ u = unix_sk(sk); ++ u_other = unix_sk(other); ++ spin_lock(&u_other->peer_wait.lock); ++ ++ if (u->peer_wake.private == other) { ++ __remove_wait_queue(&u_other->peer_wait, &u->peer_wake); ++ u->peer_wake.private = NULL; ++ } ++ ++ spin_unlock(&u_other->peer_wait.lock); ++} ++ ++static void unix_dgram_peer_wake_disconnect_wakeup(struct sock *sk, ++ struct sock *other) ++{ ++ unix_dgram_peer_wake_disconnect(sk, other); ++ wake_up_interruptible_poll(sk_sleep(sk), ++ POLLOUT | ++ POLLWRNORM | ++ POLLWRBAND); ++} ++ ++/* preconditions: ++ * - unix_peer(sk) == other ++ * - association is stable ++ */ ++static int unix_dgram_peer_wake_me(struct sock *sk, struct sock *other) ++{ ++ int connected; ++ ++ connected = unix_dgram_peer_wake_connect(sk, other); ++ ++ if (unix_recvq_full(other)) ++ return 1; ++ ++ if (connected) ++ unix_dgram_peer_wake_disconnect(sk, other); ++ ++ return 0; ++} ++ + static inline int unix_writable(struct sock *sk) + { + return (atomic_read(&sk->sk_wmem_alloc) << 2) <= sk->sk_sndbuf; +@@ -417,6 +529,8 @@ static void unix_release_sock(struct sock *sk, int embrion) + skpair->sk_state_change(skpair); + sk_wake_async(skpair, SOCK_WAKE_WAITD, POLL_HUP); + } ++ ++ unix_dgram_peer_wake_disconnect(sk, skpair); + sock_put(skpair); /* It may now die */ + unix_peer(sk) = NULL; + } +@@ -650,6 +764,7 @@ static struct sock *unix_create1(struct net *net, struct socket *sock) + INIT_LIST_HEAD(&u->link); + mutex_init(&u->readlock); /* single task reading lock */ + init_waitqueue_head(&u->peer_wait); ++ init_waitqueue_func_entry(&u->peer_wake, unix_dgram_peer_wake_relay); + unix_insert_socket(unix_sockets_unbound(sk), sk); + out: + if (sk == NULL) +@@ -1017,6 +1132,8 @@ restart: + if (unix_peer(sk)) { + struct sock *old_peer = unix_peer(sk); + unix_peer(sk) = other; ++ unix_dgram_peer_wake_disconnect_wakeup(sk, old_peer); ++ + unix_state_double_unlock(sk, other); + + if (other != old_peer) +@@ -1456,6 +1573,7 @@ static int unix_dgram_sendmsg(struct kiocb *kiocb, struct socket *sock, + struct scm_cookie tmp_scm; + int max_level; + int data_len = 0; ++ int sk_locked; + + if (NULL == siocb->scm) + siocb->scm = &tmp_scm; +@@ -1532,12 +1650,14 @@ restart: + goto out_free; + } + ++ sk_locked = 0; + unix_state_lock(other); ++restart_locked: + err = -EPERM; + if (!unix_may_send(sk, other)) + goto out_unlock; + +- if (sock_flag(other, SOCK_DEAD)) { ++ if (unlikely(sock_flag(other, SOCK_DEAD))) { + /* + * Check with 1003.1g - what should + * datagram error +@@ -1545,10 +1665,14 @@ restart: + unix_state_unlock(other); + sock_put(other); + ++ if (!sk_locked) ++ unix_state_lock(sk); ++ + err = 0; +- unix_state_lock(sk); + if (unix_peer(sk) == other) { + unix_peer(sk) = NULL; ++ unix_dgram_peer_wake_disconnect_wakeup(sk, other); ++ + unix_state_unlock(sk); + + unix_dgram_disconnected(sk, other); +@@ -1574,21 +1698,38 @@ restart: + goto out_unlock; + } + +- if (unix_peer(other) != sk && unix_recvq_full(other)) { +- if (!timeo) { +- err = -EAGAIN; +- goto out_unlock; ++ if (unlikely(unix_peer(other) != sk && unix_recvq_full(other))) { ++ if (timeo) { ++ timeo = unix_wait_for_peer(other, timeo); ++ ++ err = sock_intr_errno(timeo); ++ if (signal_pending(current)) ++ goto out_free; ++ ++ goto restart; + } + +- timeo = unix_wait_for_peer(other, timeo); ++ if (!sk_locked) { ++ unix_state_unlock(other); ++ unix_state_double_lock(sk, other); ++ } + +- err = sock_intr_errno(timeo); +- if (signal_pending(current)) +- goto out_free; ++ if (unix_peer(sk) != other || ++ unix_dgram_peer_wake_me(sk, other)) { ++ err = -EAGAIN; ++ sk_locked = 1; ++ goto out_unlock; ++ } + +- goto restart; ++ if (!sk_locked) { ++ sk_locked = 1; ++ goto restart_locked; ++ } + } + ++ if (unlikely(sk_locked)) ++ unix_state_unlock(sk); ++ + if (sock_flag(other, SOCK_RCVTSTAMP)) + __net_timestamp(skb); + maybe_add_creds(skb, sock, other); +@@ -1602,6 +1743,8 @@ restart: + return len; + + out_unlock: ++ if (sk_locked) ++ unix_state_unlock(sk); + unix_state_unlock(other); + out_free: + kfree_skb(skb); +@@ -1791,14 +1934,7 @@ static int unix_dgram_recvmsg(struct kiocb *iocb, struct socket *sock, + if (flags&MSG_OOB) + goto out; + +- err = mutex_lock_interruptible(&u->readlock); +- if (unlikely(err)) { +- /* recvmsg() in non blocking mode is supposed to return -EAGAIN +- * sk_rcvtimeo is not honored by mutex_lock_interruptible() +- */ +- err = noblock ? -EAGAIN : -ERESTARTSYS; +- goto out; +- } ++ mutex_lock(&u->readlock); + + skip = sk_peek_offset(sk, flags); + +@@ -1990,12 +2126,12 @@ again: + + timeo = unix_stream_data_wait(sk, timeo, last); + +- if (signal_pending(current) +- || mutex_lock_interruptible(&u->readlock)) { ++ if (signal_pending(current)) { + err = sock_intr_errno(timeo); + goto out; + } + ++ mutex_lock(&u->readlock); + continue; + unlock: + unix_state_unlock(sk); +@@ -2260,14 +2396,16 @@ static unsigned int unix_dgram_poll(struct file *file, struct socket *sock, + return mask; + + writable = unix_writable(sk); +- other = unix_peer_get(sk); +- if (other) { +- if (unix_peer(other) != sk) { +- sock_poll_wait(file, &unix_sk(other)->peer_wait, wait); +- if (unix_recvq_full(other)) +- writable = 0; +- } +- sock_put(other); ++ if (writable) { ++ unix_state_lock(sk); ++ ++ other = unix_peer(sk); ++ if (other && unix_peer(other) != sk && ++ unix_recvq_full(other) && ++ unix_dgram_peer_wake_me(sk, other)) ++ writable = 0; ++ ++ unix_state_unlock(sk); + } + + if (writable) +diff --git a/security/keys/gc.c b/security/keys/gc.c +index 797818695c87..de34c290bd6f 100644 +--- a/security/keys/gc.c ++++ b/security/keys/gc.c +@@ -187,6 +187,12 @@ static noinline void key_gc_unused_keys(struct list_head *keys) + kdebug("- %u", key->serial); + key_check(key); + ++ /* Throw away the key data if the key is instantiated */ ++ if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags) && ++ !test_bit(KEY_FLAG_NEGATIVE, &key->flags) && ++ key->type->destroy) ++ key->type->destroy(key); ++ + security_key_free(key); + + /* deal with the user's key tracking and quota */ +@@ -201,10 +207,6 @@ static noinline void key_gc_unused_keys(struct list_head *keys) + if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) + atomic_dec(&key->user->nikeys); + +- /* now throw away the key memory */ +- if (key->type->destroy) +- key->type->destroy(key); +- + key_user_put(key->user); + + kfree(key->description); +diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c +index 33cfd27b4de2..3242195bfa95 100644 +--- a/security/keys/keyctl.c ++++ b/security/keys/keyctl.c +@@ -744,16 +744,16 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen) + + /* the key is probably readable - now try to read it */ + can_read_key: +- ret = key_validate(key); +- if (ret == 0) { +- ret = -EOPNOTSUPP; +- if (key->type->read) { +- /* read the data with the semaphore held (since we +- * might sleep) */ +- down_read(&key->sem); ++ ret = -EOPNOTSUPP; ++ if (key->type->read) { ++ /* Read the data with the semaphore held (since we might sleep) ++ * to protect against the key being updated or revoked. ++ */ ++ down_read(&key->sem); ++ ret = key_validate(key); ++ if (ret == 0) + ret = key->type->read(key, buffer, buflen); +- up_read(&key->sem); +- } ++ up_read(&key->sem); + } + + error2: +diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c +index 42defae1e161..cd871dc8b7c0 100644 +--- a/security/keys/process_keys.c ++++ b/security/keys/process_keys.c +@@ -792,6 +792,7 @@ long join_session_keyring(const char *name) + ret = PTR_ERR(keyring); + goto error2; + } else if (keyring == new->session_keyring) { ++ key_put(keyring); + ret = 0; + goto error2; + } diff --git a/patch/kernel/s500-next/patch-3.10.95-96.patch b/patch/kernel/s500-next/patch-3.10.95-96.patch new file mode 100644 index 000000000..6c35ece4a --- /dev/null +++ b/patch/kernel/s500-next/patch-3.10.95-96.patch @@ -0,0 +1,1497 @@ +diff --git a/Makefile b/Makefile +index eb120001bc12..c88ea5d8d19c 100644 +--- a/Makefile ++++ b/Makefile +@@ -1,6 +1,6 @@ + VERSION = 3 + PATCHLEVEL = 10 +-SUBLEVEL = 95 ++SUBLEVEL = 96 + EXTRAVERSION = + NAME = TOSSUG Baby Fish + +diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c +index 33a74fc45959..dfad98fda4f8 100644 +--- a/arch/arm64/kernel/ptrace.c ++++ b/arch/arm64/kernel/ptrace.c +@@ -51,6 +51,12 @@ + */ + void ptrace_disable(struct task_struct *child) + { ++ /* ++ * This would be better off in core code, but PTRACE_DETACH has ++ * grown its fair share of arch-specific worts and changing it ++ * is likely to cause regressions on obscure architectures. ++ */ ++ user_disable_single_step(child); + } + + /* +diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c +index ba7477efad5c..5b77586ef0af 100644 +--- a/arch/arm64/mm/mmu.c ++++ b/arch/arm64/mm/mmu.c +@@ -348,6 +348,9 @@ void __init paging_init(void) + + empty_zero_page = virt_to_page(zero_page); + ++ /* Ensure the zero page is visible to the page table walker */ ++ dsb(); ++ + /* + * TTBR0 is only used for the identity mapping at this stage. Make it + * point to zero page to avoid speculatively fetching new entries. +diff --git a/arch/mn10300/Kconfig b/arch/mn10300/Kconfig +index 428da175d073..31b91602f055 100644 +--- a/arch/mn10300/Kconfig ++++ b/arch/mn10300/Kconfig +@@ -2,6 +2,7 @@ config MN10300 + def_bool y + select HAVE_OPROFILE + select HAVE_GENERIC_HARDIRQS ++ select HAVE_UID16 + select GENERIC_IRQ_SHOW + select ARCH_WANT_IPC_PARSE_VERSION + select HAVE_ARCH_TRACEHOOK +@@ -37,9 +38,6 @@ config HIGHMEM + config NUMA + def_bool n + +-config UID16 +- def_bool y +- + config RWSEM_GENERIC_SPINLOCK + def_bool y + +diff --git a/arch/openrisc/Kconfig b/arch/openrisc/Kconfig +index 1072bfd18c50..f4dd44a7118f 100644 +--- a/arch/openrisc/Kconfig ++++ b/arch/openrisc/Kconfig +@@ -17,6 +17,7 @@ config OPENRISC + select GENERIC_IRQ_SHOW + select GENERIC_IOMAP + select GENERIC_CPU_DEVICES ++ select HAVE_UID16 + select GENERIC_ATOMIC64 + select GENERIC_CLOCKEVENTS + select GENERIC_STRNCPY_FROM_USER +@@ -29,9 +30,6 @@ config MMU + config HAVE_DMA_ATTRS + def_bool y + +-config UID16 +- def_bool y +- + config RWSEM_GENERIC_SPINLOCK + def_bool y + +diff --git a/arch/powerpc/include/asm/cmpxchg.h b/arch/powerpc/include/asm/cmpxchg.h +index e245aab7f191..95b515113186 100644 +--- a/arch/powerpc/include/asm/cmpxchg.h ++++ b/arch/powerpc/include/asm/cmpxchg.h +@@ -18,12 +18,12 @@ __xchg_u32(volatile void *p, unsigned long val) + unsigned long prev; + + __asm__ __volatile__( +- PPC_RELEASE_BARRIER ++ PPC_ATOMIC_ENTRY_BARRIER + "1: lwarx %0,0,%2 \n" + PPC405_ERR77(0,%2) + " stwcx. %3,0,%2 \n\ + bne- 1b" +- PPC_ACQUIRE_BARRIER ++ PPC_ATOMIC_EXIT_BARRIER + : "=&r" (prev), "+m" (*(volatile unsigned int *)p) + : "r" (p), "r" (val) + : "cc", "memory"); +@@ -61,12 +61,12 @@ __xchg_u64(volatile void *p, unsigned long val) + unsigned long prev; + + __asm__ __volatile__( +- PPC_RELEASE_BARRIER ++ PPC_ATOMIC_ENTRY_BARRIER + "1: ldarx %0,0,%2 \n" + PPC405_ERR77(0,%2) + " stdcx. %3,0,%2 \n\ + bne- 1b" +- PPC_ACQUIRE_BARRIER ++ PPC_ATOMIC_EXIT_BARRIER + : "=&r" (prev), "+m" (*(volatile unsigned long *)p) + : "r" (p), "r" (val) + : "cc", "memory"); +@@ -152,14 +152,14 @@ __cmpxchg_u32(volatile unsigned int *p, unsigned long old, unsigned long new) + unsigned int prev; + + __asm__ __volatile__ ( +- PPC_RELEASE_BARRIER ++ PPC_ATOMIC_ENTRY_BARRIER + "1: lwarx %0,0,%2 # __cmpxchg_u32\n\ + cmpw 0,%0,%3\n\ + bne- 2f\n" + PPC405_ERR77(0,%2) + " stwcx. %4,0,%2\n\ + bne- 1b" +- PPC_ACQUIRE_BARRIER ++ PPC_ATOMIC_EXIT_BARRIER + "\n\ + 2:" + : "=&r" (prev), "+m" (*p) +@@ -198,13 +198,13 @@ __cmpxchg_u64(volatile unsigned long *p, unsigned long old, unsigned long new) + unsigned long prev; + + __asm__ __volatile__ ( +- PPC_RELEASE_BARRIER ++ PPC_ATOMIC_ENTRY_BARRIER + "1: ldarx %0,0,%2 # __cmpxchg_u64\n\ + cmpd 0,%0,%3\n\ + bne- 2f\n\ + stdcx. %4,0,%2\n\ + bne- 1b" +- PPC_ACQUIRE_BARRIER ++ PPC_ATOMIC_EXIT_BARRIER + "\n\ + 2:" + : "=&r" (prev), "+m" (*p) +diff --git a/arch/powerpc/include/asm/reg.h b/arch/powerpc/include/asm/reg.h +index 795f67792ea9..60c31698f7d5 100644 +--- a/arch/powerpc/include/asm/reg.h ++++ b/arch/powerpc/include/asm/reg.h +@@ -108,6 +108,7 @@ + #define MSR_TS_T __MASK(MSR_TS_T_LG) /* Transaction Transactional */ + #define MSR_TS_MASK (MSR_TS_T | MSR_TS_S) /* Transaction State bits */ + #define MSR_TM_ACTIVE(x) (((x) & MSR_TS_MASK) != 0) /* Transaction active? */ ++#define MSR_TM_RESV(x) (((x) & MSR_TS_MASK) == MSR_TS_MASK) /* Reserved */ + #define MSR_TM_TRANSACTIONAL(x) (((x) & MSR_TS_MASK) == MSR_TS_T) + #define MSR_TM_SUSPENDED(x) (((x) & MSR_TS_MASK) == MSR_TS_S) + +diff --git a/arch/powerpc/include/asm/synch.h b/arch/powerpc/include/asm/synch.h +index e682a7143edb..c50868681f9e 100644 +--- a/arch/powerpc/include/asm/synch.h ++++ b/arch/powerpc/include/asm/synch.h +@@ -44,7 +44,7 @@ static inline void isync(void) + MAKE_LWSYNC_SECTION_ENTRY(97, __lwsync_fixup); + #define PPC_ACQUIRE_BARRIER "\n" stringify_in_c(__PPC_ACQUIRE_BARRIER) + #define PPC_RELEASE_BARRIER stringify_in_c(LWSYNC) "\n" +-#define PPC_ATOMIC_ENTRY_BARRIER "\n" stringify_in_c(LWSYNC) "\n" ++#define PPC_ATOMIC_ENTRY_BARRIER "\n" stringify_in_c(sync) "\n" + #define PPC_ATOMIC_EXIT_BARRIER "\n" stringify_in_c(sync) "\n" + #else + #define PPC_ACQUIRE_BARRIER +diff --git a/arch/powerpc/kernel/signal_32.c b/arch/powerpc/kernel/signal_32.c +index d9b673b06757..8220ae86252c 100644 +--- a/arch/powerpc/kernel/signal_32.c ++++ b/arch/powerpc/kernel/signal_32.c +@@ -858,6 +858,15 @@ static long restore_tm_user_regs(struct pt_regs *regs, + return 1; + #endif /* CONFIG_SPE */ + ++ /* Get the top half of the MSR from the user context */ ++ if (__get_user(msr_hi, &tm_sr->mc_gregs[PT_MSR])) ++ return 1; ++ msr_hi <<= 32; ++ /* If TM bits are set to the reserved value, it's an invalid context */ ++ if (MSR_TM_RESV(msr_hi)) ++ return 1; ++ /* Pull in the MSR TM bits from the user context */ ++ regs->msr = (regs->msr & ~MSR_TS_MASK) | (msr_hi & MSR_TS_MASK); + /* Now, recheckpoint. This loads up all of the checkpointed (older) + * registers, including FP and V[S]Rs. After recheckpointing, the + * transactional versions should be loaded. +@@ -867,11 +876,6 @@ static long restore_tm_user_regs(struct pt_regs *regs, + current->thread.tm_texasr |= TEXASR_FS; + /* This loads the checkpointed FP/VEC state, if used */ + tm_recheckpoint(¤t->thread, msr); +- /* Get the top half of the MSR */ +- if (__get_user(msr_hi, &tm_sr->mc_gregs[PT_MSR])) +- return 1; +- /* Pull in MSR TM from user context */ +- regs->msr = (regs->msr & ~MSR_TS_MASK) | ((msr_hi<<32) & MSR_TS_MASK); + + /* This loads the speculative FP/VEC state, if used */ + if (msr & MSR_FP) { +diff --git a/arch/powerpc/kernel/signal_64.c b/arch/powerpc/kernel/signal_64.c +index 74d9615a6bb6..2419c17538e2 100644 +--- a/arch/powerpc/kernel/signal_64.c ++++ b/arch/powerpc/kernel/signal_64.c +@@ -416,6 +416,10 @@ static long restore_tm_sigcontexts(struct pt_regs *regs, + + /* get MSR separately, transfer the LE bit if doing signal return */ + err |= __get_user(msr, &sc->gp_regs[PT_MSR]); ++ /* Don't allow reserved mode. */ ++ if (MSR_TM_RESV(msr)) ++ return -EINVAL; ++ + /* pull in MSR TM from user context */ + regs->msr = (regs->msr & ~MSR_TS_MASK) | (msr & MSR_TS_MASK); + +diff --git a/arch/powerpc/kvm/book3s_hv.c b/arch/powerpc/kvm/book3s_hv.c +index 102ad8a255f3..466fbd54e7f8 100644 +--- a/arch/powerpc/kvm/book3s_hv.c ++++ b/arch/powerpc/kvm/book3s_hv.c +@@ -160,6 +160,12 @@ void kvmppc_core_vcpu_put(struct kvm_vcpu *vcpu) + + void kvmppc_set_msr(struct kvm_vcpu *vcpu, u64 msr) + { ++ /* ++ * Check for illegal transactional state bit combination ++ * and if we find it, force the TS field to a safe state. ++ */ ++ if ((msr & MSR_TS_MASK) == MSR_TS_MASK) ++ msr &= ~MSR_TS_MASK; + vcpu->arch.shregs.msr = msr; + kvmppc_end_cede(vcpu); + } +diff --git a/arch/x86/include/asm/boot.h b/arch/x86/include/asm/boot.h +index 4fa687a47a62..6b8d6e8cd449 100644 +--- a/arch/x86/include/asm/boot.h ++++ b/arch/x86/include/asm/boot.h +@@ -27,7 +27,7 @@ + #define BOOT_HEAP_SIZE 0x400000 + #else /* !CONFIG_KERNEL_BZIP2 */ + +-#define BOOT_HEAP_SIZE 0x8000 ++#define BOOT_HEAP_SIZE 0x10000 + + #endif /* !CONFIG_KERNEL_BZIP2 */ + +diff --git a/arch/x86/kernel/reboot.c b/arch/x86/kernel/reboot.c +index 90fd1195f276..abfc89b3b55a 100644 +--- a/arch/x86/kernel/reboot.c ++++ b/arch/x86/kernel/reboot.c +@@ -366,6 +366,14 @@ static struct dmi_system_id __initdata reboot_dmi_table[] = { + DMI_MATCH(DMI_BOARD_NAME, "P4S800"), + }, + }, ++ { /* Handle problems with rebooting on the iMac10,1. */ ++ .callback = set_pci_reboot, ++ .ident = "Apple iMac10,1", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Apple Inc."), ++ DMI_MATCH(DMI_PRODUCT_NAME, "iMac10,1"), ++ }, ++ }, + + { /* Handle reboot issue on Acer Aspire one */ + .callback = set_kbd_reboot, +diff --git a/arch/x86/kernel/signal.c b/arch/x86/kernel/signal.c +index 66deef41512f..f11fd597c5b6 100644 +--- a/arch/x86/kernel/signal.c ++++ b/arch/x86/kernel/signal.c +@@ -686,12 +686,15 @@ handle_signal(struct ksignal *ksig, struct pt_regs *regs) + signal_setup_done(failed, ksig, test_thread_flag(TIF_SINGLESTEP)); + } + +-#ifdef CONFIG_X86_32 +-#define NR_restart_syscall __NR_restart_syscall +-#else /* !CONFIG_X86_32 */ +-#define NR_restart_syscall \ +- test_thread_flag(TIF_IA32) ? __NR_ia32_restart_syscall : __NR_restart_syscall +-#endif /* CONFIG_X86_32 */ ++static inline unsigned long get_nr_restart_syscall(const struct pt_regs *regs) ++{ ++#if defined(CONFIG_X86_32) || !defined(CONFIG_X86_64) ++ return __NR_restart_syscall; ++#else /* !CONFIG_X86_32 && CONFIG_X86_64 */ ++ return test_thread_flag(TIF_IA32) ? __NR_ia32_restart_syscall : ++ __NR_restart_syscall | (regs->orig_ax & __X32_SYSCALL_BIT); ++#endif /* CONFIG_X86_32 || !CONFIG_X86_64 */ ++} + + /* + * Note that 'init' is a special process: it doesn't get signals it doesn't +@@ -720,7 +723,7 @@ static void do_signal(struct pt_regs *regs) + break; + + case -ERESTART_RESTARTBLOCK: +- regs->ax = NR_restart_syscall; ++ regs->ax = get_nr_restart_syscall(regs); + regs->ip -= 2; + break; + } +diff --git a/arch/x86/xen/suspend.c b/arch/x86/xen/suspend.c +index 45329c8c226e..39e12c10b931 100644 +--- a/arch/x86/xen/suspend.c ++++ b/arch/x86/xen/suspend.c +@@ -30,7 +30,8 @@ void xen_arch_hvm_post_suspend(int suspend_cancelled) + { + #ifdef CONFIG_XEN_PVHVM + int cpu; +- xen_hvm_init_shared_info(); ++ if (!suspend_cancelled) ++ xen_hvm_init_shared_info(); + xen_callback_vector(); + xen_unplug_emulated_devices(); + if (xen_feature(XENFEAT_hvm_safe_pvclock)) { +diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c +index 02cc352d8bcc..a67ac2a71155 100644 +--- a/drivers/char/ipmi/ipmi_si_intf.c ++++ b/drivers/char/ipmi/ipmi_si_intf.c +@@ -1144,14 +1144,14 @@ static int smi_start_processing(void *send_info, + + new_smi->intf = intf; + +- /* Try to claim any interrupts. */ +- if (new_smi->irq_setup) +- new_smi->irq_setup(new_smi); +- + /* Set up the timer that drives the interface. */ + setup_timer(&new_smi->si_timer, smi_timeout, (long)new_smi); + smi_mod_timer(new_smi, jiffies + SI_TIMEOUT_JIFFIES); + ++ /* Try to claim any interrupts. */ ++ if (new_smi->irq_setup) ++ new_smi->irq_setup(new_smi); ++ + /* + * Check if the user forcefully enabled the daemon. + */ +diff --git a/drivers/connector/connector.c b/drivers/connector/connector.c +index 0daa11e418b1..bdbd80415908 100644 +--- a/drivers/connector/connector.c ++++ b/drivers/connector/connector.c +@@ -154,26 +154,21 @@ static int cn_call_callback(struct sk_buff *skb) + * + * It checks skb, netlink header and msg sizes, and calls callback helper. + */ +-static void cn_rx_skb(struct sk_buff *__skb) ++static void cn_rx_skb(struct sk_buff *skb) + { + struct nlmsghdr *nlh; +- struct sk_buff *skb; + int len, err; + +- skb = skb_get(__skb); +- + if (skb->len >= NLMSG_HDRLEN) { + nlh = nlmsg_hdr(skb); + len = nlmsg_len(nlh); + + if (len < (int)sizeof(struct cn_msg) || + skb->len < nlh->nlmsg_len || +- len > CONNECTOR_MAX_MSG_SIZE) { +- kfree_skb(skb); ++ len > CONNECTOR_MAX_MSG_SIZE) + return; +- } + +- err = cn_call_callback(skb); ++ err = cn_call_callback(skb_get(skb)); + if (err < 0) + kfree_skb(skb); + } +diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c +index 2bd798a7d9aa..c1a8cf2d490b 100644 +--- a/drivers/hid/hid-core.c ++++ b/drivers/hid/hid-core.c +@@ -1462,7 +1462,7 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) + "Multi-Axis Controller" + }; + const char *type, *bus; +- char buf[64]; ++ char buf[64] = ""; + unsigned int i; + int len; + int ret; +diff --git a/drivers/isdn/i4l/isdn_ppp.c b/drivers/isdn/i4l/isdn_ppp.c +index 38ceac5053a0..0ed6731396ef 100644 +--- a/drivers/isdn/i4l/isdn_ppp.c ++++ b/drivers/isdn/i4l/isdn_ppp.c +@@ -301,6 +301,8 @@ isdn_ppp_open(int min, struct file *file) + is->compflags = 0; + + is->reset = isdn_ppp_ccp_reset_alloc(is); ++ if (!is->reset) ++ return -ENOMEM; + + is->lp = NULL; + is->mp_seqno = 0; /* MP sequence number */ +@@ -320,6 +322,10 @@ isdn_ppp_open(int min, struct file *file) + * VJ header compression init + */ + is->slcomp = slhc_init(16, 16); /* not necessary for 2. link in bundle */ ++ if (IS_ERR(is->slcomp)) { ++ isdn_ppp_ccp_reset_free(is); ++ return PTR_ERR(is->slcomp); ++ } + #endif + #ifdef CONFIG_IPPP_FILTER + is->pass_filter = NULL; +@@ -568,10 +574,8 @@ isdn_ppp_ioctl(int min, struct file *file, unsigned int cmd, unsigned long arg) + is->maxcid = val; + #ifdef CONFIG_ISDN_PPP_VJ + sltmp = slhc_init(16, val); +- if (!sltmp) { +- printk(KERN_ERR "ippp, can't realloc slhc struct\n"); +- return -ENOMEM; +- } ++ if (IS_ERR(sltmp)) ++ return PTR_ERR(sltmp); + if (is->slcomp) + slhc_free(is->slcomp); + is->slcomp = sltmp; +diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c +index 5a1897d86e94..a2d7d5f066f1 100644 +--- a/drivers/net/ppp/ppp_generic.c ++++ b/drivers/net/ppp/ppp_generic.c +@@ -716,10 +716,8 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) + val &= 0xffff; + } + vj = slhc_init(val2+1, val+1); +- if (!vj) { +- netdev_err(ppp->dev, +- "PPP: no memory (VJ compressor)\n"); +- err = -ENOMEM; ++ if (IS_ERR(vj)) { ++ err = PTR_ERR(vj); + break; + } + ppp_lock(ppp); +diff --git a/drivers/net/slip/slhc.c b/drivers/net/slip/slhc.c +index 1252d9c726a7..b52eabc168a0 100644 +--- a/drivers/net/slip/slhc.c ++++ b/drivers/net/slip/slhc.c +@@ -84,8 +84,9 @@ static long decode(unsigned char **cpp); + static unsigned char * put16(unsigned char *cp, unsigned short x); + static unsigned short pull16(unsigned char **cpp); + +-/* Initialize compression data structure ++/* Allocate compression data structure + * slots must be in range 0 to 255 (zero meaning no compression) ++ * Returns pointer to structure or ERR_PTR() on error. + */ + struct slcompress * + slhc_init(int rslots, int tslots) +@@ -94,11 +95,14 @@ slhc_init(int rslots, int tslots) + register struct cstate *ts; + struct slcompress *comp; + ++ if (rslots < 0 || rslots > 255 || tslots < 0 || tslots > 255) ++ return ERR_PTR(-EINVAL); ++ + comp = kzalloc(sizeof(struct slcompress), GFP_KERNEL); + if (! comp) + goto out_fail; + +- if ( rslots > 0 && rslots < 256 ) { ++ if (rslots > 0) { + size_t rsize = rslots * sizeof(struct cstate); + comp->rstate = kzalloc(rsize, GFP_KERNEL); + if (! comp->rstate) +@@ -106,7 +110,7 @@ slhc_init(int rslots, int tslots) + comp->rslot_limit = rslots - 1; + } + +- if ( tslots > 0 && tslots < 256 ) { ++ if (tslots > 0) { + size_t tsize = tslots * sizeof(struct cstate); + comp->tstate = kzalloc(tsize, GFP_KERNEL); + if (! comp->tstate) +@@ -141,7 +145,7 @@ out_free2: + out_free: + kfree(comp); + out_fail: +- return NULL; ++ return ERR_PTR(-ENOMEM); + } + + +diff --git a/drivers/net/slip/slip.c b/drivers/net/slip/slip.c +index a34d6bf5e43b..ca3e73753fb8 100644 +--- a/drivers/net/slip/slip.c ++++ b/drivers/net/slip/slip.c +@@ -163,7 +163,7 @@ static int sl_alloc_bufs(struct slip *sl, int mtu) + if (cbuff == NULL) + goto err_exit; + slcomp = slhc_init(16, 16); +- if (slcomp == NULL) ++ if (IS_ERR(slcomp)) + goto err_exit; + #endif + spin_lock_bh(&sl->lock); +diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c +index 14179a6593ef..5225d4321e7c 100644 +--- a/drivers/net/team/team.c ++++ b/drivers/net/team/team.c +@@ -1636,10 +1636,10 @@ static int team_vlan_rx_kill_vid(struct net_device *dev, __be16 proto, u16 vid) + struct team *team = netdev_priv(dev); + struct team_port *port; + +- rcu_read_lock(); +- list_for_each_entry_rcu(port, &team->port_list, list) ++ mutex_lock(&team->lock); ++ list_for_each_entry(port, &team->port_list, list) + vlan_vid_del(port->dev, proto, vid); +- rcu_read_unlock(); ++ mutex_unlock(&team->lock); + + return 0; + } +diff --git a/drivers/net/veth.c b/drivers/net/veth.c +index 177f911f5946..cc6d3f987436 100644 +--- a/drivers/net/veth.c ++++ b/drivers/net/veth.c +@@ -116,12 +116,6 @@ static netdev_tx_t veth_xmit(struct sk_buff *skb, struct net_device *dev) + kfree_skb(skb); + goto drop; + } +- /* don't change ip_summed == CHECKSUM_PARTIAL, as that +- * will cause bad checksum on forwarded packets +- */ +- if (skb->ip_summed == CHECKSUM_NONE && +- rcv->features & NETIF_F_RXCSUM) +- skb->ip_summed = CHECKSUM_UNNECESSARY; + + if (likely(dev_forward_skb(rcv, skb) == NET_RX_SUCCESS)) { + struct pcpu_vstats *stats = this_cpu_ptr(dev->vstats); +diff --git a/drivers/parisc/iommu-helpers.h b/drivers/parisc/iommu-helpers.h +index 8c33491b21fe..c6aa38883466 100644 +--- a/drivers/parisc/iommu-helpers.h ++++ b/drivers/parisc/iommu-helpers.h +@@ -104,7 +104,11 @@ iommu_coalesce_chunks(struct ioc *ioc, struct device *dev, + struct scatterlist *contig_sg; /* contig chunk head */ + unsigned long dma_offset, dma_len; /* start/len of DMA stream */ + unsigned int n_mappings = 0; +- unsigned int max_seg_size = dma_get_max_seg_size(dev); ++ unsigned int max_seg_size = min(dma_get_max_seg_size(dev), ++ (unsigned)DMA_CHUNK_SIZE); ++ unsigned int max_seg_boundary = dma_get_seg_boundary(dev) + 1; ++ if (max_seg_boundary) /* check if the addition above didn't overflow */ ++ max_seg_size = min(max_seg_size, max_seg_boundary); + + while (nents > 0) { + +@@ -139,14 +143,11 @@ iommu_coalesce_chunks(struct ioc *ioc, struct device *dev, + + /* + ** First make sure current dma stream won't +- ** exceed DMA_CHUNK_SIZE if we coalesce the ++ ** exceed max_seg_size if we coalesce the + ** next entry. + */ +- if(unlikely(ALIGN(dma_len + dma_offset + startsg->length, +- IOVP_SIZE) > DMA_CHUNK_SIZE)) +- break; +- +- if (startsg->length + dma_len > max_seg_size) ++ if (unlikely(ALIGN(dma_len + dma_offset + startsg->length, IOVP_SIZE) > ++ max_seg_size)) + break; + + /* +diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c +index a3431e90345f..4dc18615cd0f 100644 +--- a/drivers/usb/host/xhci.c ++++ b/drivers/usb/host/xhci.c +@@ -4615,8 +4615,16 @@ int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, + ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); + slot_ctx = xhci_get_slot_ctx(xhci, config_cmd->in_ctx); + slot_ctx->dev_info |= cpu_to_le32(DEV_HUB); ++ /* ++ * refer to section 6.2.2: MTT should be 0 for full speed hub, ++ * but it may be already set to 1 when setup an xHCI virtual ++ * device, so clear it anyway. ++ */ + if (tt->multi) + slot_ctx->dev_info |= cpu_to_le32(DEV_MTT); ++ else if (hdev->speed == USB_SPEED_FULL) ++ slot_ctx->dev_info &= cpu_to_le32(~DEV_MTT); ++ + if (xhci->hci_version > 0x95) { + xhci_dbg(xhci, "xHCI version %x needs hub " + "TT think time and number of ports\n", +@@ -4807,6 +4815,10 @@ static int __init xhci_hcd_init(void) + BUILD_BUG_ON(sizeof(struct xhci_intr_reg) != 8*32/8); + /* xhci_run_regs has eight fields and embeds 128 xhci_intr_regs */ + BUILD_BUG_ON(sizeof(struct xhci_run_regs) != (8+8*128)*32/8); ++ ++ if (usb_disabled()) ++ return -ENODEV; ++ + return 0; + unreg_pci: + xhci_unregister_pci(); +diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c +index 25522e98602e..72c14d7d604f 100644 +--- a/drivers/usb/serial/cp210x.c ++++ b/drivers/usb/serial/cp210x.c +@@ -160,6 +160,7 @@ static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x17F4, 0xAAAA) }, /* Wavesense Jazz blood glucose meter */ + { USB_DEVICE(0x1843, 0x0200) }, /* Vaisala USB Instrument Cable */ + { USB_DEVICE(0x18EF, 0xE00F) }, /* ELV USB-I2C-Interface */ ++ { USB_DEVICE(0x18EF, 0xE025) }, /* ELV Marble Sound Board 1 */ + { USB_DEVICE(0x1ADB, 0x0001) }, /* Schweitzer Engineering C662 Cable */ + { USB_DEVICE(0x1B1C, 0x1C00) }, /* Corsair USB Dongle */ + { USB_DEVICE(0x1BA4, 0x0002) }, /* Silicon Labs 358x factory default */ +diff --git a/drivers/usb/serial/ipaq.c b/drivers/usb/serial/ipaq.c +index 76c9a847da5d..e03900e8c667 100644 +--- a/drivers/usb/serial/ipaq.c ++++ b/drivers/usb/serial/ipaq.c +@@ -532,7 +532,8 @@ static int ipaq_open(struct tty_struct *tty, + * through. Since this has a reasonably high failure rate, we retry + * several times. + */ +- while (retries--) { ++ while (retries) { ++ retries--; + result = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), 0x22, 0x21, + 0x1, 0, NULL, 0, 100); +diff --git a/drivers/xen/gntdev.c b/drivers/xen/gntdev.c +index e68205cbc46e..3d8e6098cd40 100644 +--- a/drivers/xen/gntdev.c ++++ b/drivers/xen/gntdev.c +@@ -770,7 +770,7 @@ static int gntdev_mmap(struct file *flip, struct vm_area_struct *vma) + + vma->vm_ops = &gntdev_vmops; + +- vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; ++ vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP | VM_IO; + + if (use_ptemod) + vma->vm_flags |= VM_DONTCOPY; +diff --git a/include/linux/sched.h b/include/linux/sched.h +index 7cf305d036db..4781332f2e11 100644 +--- a/include/linux/sched.h ++++ b/include/linux/sched.h +@@ -670,6 +670,7 @@ struct user_struct { + unsigned long mq_bytes; /* How many bytes can be allocated to mqueue? */ + #endif + unsigned long locked_shm; /* How many pages of mlocked shm ? */ ++ unsigned long unix_inflight; /* How many files in flight in unix sockets */ + + #ifdef CONFIG_KEYS + struct key *uid_keyring; /* UID specific keyring */ +diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h +index 84662ecc7b51..e1bc430133f4 100644 +--- a/include/linux/syscalls.h ++++ b/include/linux/syscalls.h +@@ -498,7 +498,7 @@ asmlinkage long sys_chown(const char __user *filename, + asmlinkage long sys_lchown(const char __user *filename, + uid_t user, gid_t group); + asmlinkage long sys_fchown(unsigned int fd, uid_t user, gid_t group); +-#ifdef CONFIG_UID16 ++#ifdef CONFIG_HAVE_UID16 + asmlinkage long sys_chown16(const char __user *filename, + old_uid_t user, old_gid_t group); + asmlinkage long sys_lchown16(const char __user *filename, +diff --git a/include/linux/types.h b/include/linux/types.h +index 4d118ba11349..83db8e5974dc 100644 +--- a/include/linux/types.h ++++ b/include/linux/types.h +@@ -35,7 +35,7 @@ typedef __kernel_gid16_t gid16_t; + + typedef unsigned long uintptr_t; + +-#ifdef CONFIG_UID16 ++#ifdef CONFIG_HAVE_UID16 + /* This is defined by include/asm-{arch}/posix_types.h */ + typedef __kernel_old_uid_t old_uid_t; + typedef __kernel_old_gid_t old_gid_t; +diff --git a/net/bridge/br_stp_if.c b/net/bridge/br_stp_if.c +index 886f6d6dc48a..3995a66c3e4e 100644 +--- a/net/bridge/br_stp_if.c ++++ b/net/bridge/br_stp_if.c +@@ -128,7 +128,10 @@ static void br_stp_start(struct net_bridge *br) + char *argv[] = { BR_STP_PROG, br->dev->name, "start", NULL }; + char *envp[] = { NULL }; + +- r = call_usermodehelper(BR_STP_PROG, argv, envp, UMH_WAIT_PROC); ++ if (net_eq(dev_net(br->dev), &init_net)) ++ r = call_usermodehelper(BR_STP_PROG, argv, envp, UMH_WAIT_PROC); ++ else ++ r = -ENOENT; + + spin_lock_bh(&br->lock); + +diff --git a/net/core/dst.c b/net/core/dst.c +index 01f9980af86e..1bf6842b89b8 100644 +--- a/net/core/dst.c ++++ b/net/core/dst.c +@@ -280,10 +280,11 @@ void dst_release(struct dst_entry *dst) + { + if (dst) { + int newrefcnt; ++ unsigned short nocache = dst->flags & DST_NOCACHE; + + newrefcnt = atomic_dec_return(&dst->__refcnt); + WARN_ON(newrefcnt < 0); +- if (!newrefcnt && unlikely(dst->flags & DST_NOCACHE)) ++ if (!newrefcnt && unlikely(nocache)) + call_rcu(&dst->rcu_head, dst_destroy_rcu); + } + } +diff --git a/net/ipv4/tcp_yeah.c b/net/ipv4/tcp_yeah.c +index 05c3b6f0e8e1..bf8321d6f2ef 100644 +--- a/net/ipv4/tcp_yeah.c ++++ b/net/ipv4/tcp_yeah.c +@@ -222,7 +222,7 @@ static u32 tcp_yeah_ssthresh(struct sock *sk) { + yeah->fast_count = 0; + yeah->reno_count = max(yeah->reno_count>>1, 2U); + +- return tp->snd_cwnd - reduction; ++ return max_t(int, tp->snd_cwnd - reduction, 2); + } + + static struct tcp_congestion_ops tcp_yeah __read_mostly = { +diff --git a/net/ipv6/addrlabel.c b/net/ipv6/addrlabel.c +index b30ad3741b46..d5c918975c8c 100644 +--- a/net/ipv6/addrlabel.c ++++ b/net/ipv6/addrlabel.c +@@ -558,7 +558,7 @@ static int ip6addrlbl_get(struct sk_buff *in_skb, struct nlmsghdr* nlh) + + rcu_read_lock(); + p = __ipv6_addr_label(net, addr, ipv6_addr_type(addr), ifal->ifal_index); +- if (p && ip6addrlbl_hold(p)) ++ if (p && !ip6addrlbl_hold(p)) + p = NULL; + lseq = ip6addrlbl_table.seq; + rcu_read_unlock(); +diff --git a/net/phonet/af_phonet.c b/net/phonet/af_phonet.c +index 5a940dbd74a3..f0229223bf91 100644 +--- a/net/phonet/af_phonet.c ++++ b/net/phonet/af_phonet.c +@@ -377,6 +377,10 @@ static int phonet_rcv(struct sk_buff *skb, struct net_device *dev, + struct sockaddr_pn sa; + u16 len; + ++ skb = skb_share_check(skb, GFP_ATOMIC); ++ if (!skb) ++ return NET_RX_DROP; ++ + /* check we have at least a full Phonet header */ + if (!pskb_pull(skb, sizeof(struct phonethdr))) + goto out; +diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c +index c52763a26297..d9cbecb62aca 100644 +--- a/net/sctp/sm_statefuns.c ++++ b/net/sctp/sm_statefuns.c +@@ -4835,7 +4835,8 @@ sctp_disposition_t sctp_sf_do_9_1_prm_abort( + + retval = SCTP_DISPOSITION_CONSUME; + +- sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(abort)); ++ if (abort) ++ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(abort)); + + /* Even if we can't send the ABORT due to low memory delete the + * TCB. This is a departure from our typical NOMEM handling. +@@ -4972,7 +4973,8 @@ sctp_disposition_t sctp_sf_cookie_wait_prm_abort( + SCTP_TO(SCTP_EVENT_TIMEOUT_T1_INIT)); + retval = SCTP_DISPOSITION_CONSUME; + +- sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(abort)); ++ if (abort) ++ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(abort)); + + sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE, + SCTP_STATE(SCTP_STATE_CLOSED)); +diff --git a/net/sctp/socket.c b/net/sctp/socket.c +index 80bd61ae5945..bdc3fb66717d 100644 +--- a/net/sctp/socket.c ++++ b/net/sctp/socket.c +@@ -1533,8 +1533,7 @@ SCTP_STATIC void sctp_close(struct sock *sk, long timeout) + struct sctp_chunk *chunk; + + chunk = sctp_make_abort_user(asoc, NULL, 0); +- if (chunk) +- sctp_primitive_ABORT(net, asoc, chunk); ++ sctp_primitive_ABORT(net, asoc, chunk); + } else + sctp_primitive_SHUTDOWN(net, asoc, NULL); + } +diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c +index f934e7ba5eb8..a673c1f4f638 100644 +--- a/net/unix/af_unix.c ++++ b/net/unix/af_unix.c +@@ -1483,6 +1483,21 @@ static void unix_destruct_scm(struct sk_buff *skb) + sock_wfree(skb); + } + ++/* ++ * The "user->unix_inflight" variable is protected by the garbage ++ * collection lock, and we just read it locklessly here. If you go ++ * over the limit, there might be a tiny race in actually noticing ++ * it across threads. Tough. ++ */ ++static inline bool too_many_unix_fds(struct task_struct *p) ++{ ++ struct user_struct *user = current_user(); ++ ++ if (unlikely(user->unix_inflight > task_rlimit(p, RLIMIT_NOFILE))) ++ return !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN); ++ return false; ++} ++ + #define MAX_RECURSION_LEVEL 4 + + static int unix_attach_fds(struct scm_cookie *scm, struct sk_buff *skb) +@@ -1491,6 +1506,9 @@ static int unix_attach_fds(struct scm_cookie *scm, struct sk_buff *skb) + unsigned char max_level = 0; + int unix_sock_count = 0; + ++ if (too_many_unix_fds(current)) ++ return -ETOOMANYREFS; ++ + for (i = scm->fp->count - 1; i >= 0; i--) { + struct sock *sk = unix_get_socket(scm->fp->fp[i]); + +@@ -1512,10 +1530,8 @@ static int unix_attach_fds(struct scm_cookie *scm, struct sk_buff *skb) + if (!UNIXCB(skb).fp) + return -ENOMEM; + +- if (unix_sock_count) { +- for (i = scm->fp->count - 1; i >= 0; i--) +- unix_inflight(scm->fp->fp[i]); +- } ++ for (i = scm->fp->count - 1; i >= 0; i--) ++ unix_inflight(scm->fp->fp[i]); + return max_level; + } + +@@ -1934,7 +1950,14 @@ static int unix_dgram_recvmsg(struct kiocb *iocb, struct socket *sock, + if (flags&MSG_OOB) + goto out; + +- mutex_lock(&u->readlock); ++ err = mutex_lock_interruptible(&u->readlock); ++ if (unlikely(err)) { ++ /* recvmsg() in non blocking mode is supposed to return -EAGAIN ++ * sk_rcvtimeo is not honored by mutex_lock_interruptible() ++ */ ++ err = noblock ? -EAGAIN : -ERESTARTSYS; ++ goto out; ++ } + + skip = sk_peek_offset(sk, flags); + +@@ -2083,14 +2106,7 @@ static int unix_stream_recvmsg(struct kiocb *iocb, struct socket *sock, + memset(&tmp_scm, 0, sizeof(tmp_scm)); + } + +- err = mutex_lock_interruptible(&u->readlock); +- if (unlikely(err)) { +- /* recvmsg() in non blocking mode is supposed to return -EAGAIN +- * sk_rcvtimeo is not honored by mutex_lock_interruptible() +- */ +- err = noblock ? -EAGAIN : -ERESTARTSYS; +- goto out; +- } ++ mutex_lock(&u->readlock); + + do { + int chunk; +diff --git a/net/unix/garbage.c b/net/unix/garbage.c +index 9bc73f87f64a..06730fe6ad9d 100644 +--- a/net/unix/garbage.c ++++ b/net/unix/garbage.c +@@ -125,9 +125,12 @@ struct sock *unix_get_socket(struct file *filp) + void unix_inflight(struct file *fp) + { + struct sock *s = unix_get_socket(fp); ++ ++ spin_lock(&unix_gc_lock); ++ + if (s) { + struct unix_sock *u = unix_sk(s); +- spin_lock(&unix_gc_lock); ++ + if (atomic_long_inc_return(&u->inflight) == 1) { + BUG_ON(!list_empty(&u->link)); + list_add_tail(&u->link, &gc_inflight_list); +@@ -135,22 +138,27 @@ void unix_inflight(struct file *fp) + BUG_ON(list_empty(&u->link)); + } + unix_tot_inflight++; +- spin_unlock(&unix_gc_lock); + } ++ fp->f_cred->user->unix_inflight++; ++ spin_unlock(&unix_gc_lock); + } + + void unix_notinflight(struct file *fp) + { + struct sock *s = unix_get_socket(fp); ++ ++ spin_lock(&unix_gc_lock); ++ + if (s) { + struct unix_sock *u = unix_sk(s); +- spin_lock(&unix_gc_lock); ++ + BUG_ON(list_empty(&u->link)); + if (atomic_long_dec_and_test(&u->inflight)) + list_del_init(&u->link); + unix_tot_inflight--; +- spin_unlock(&unix_gc_lock); + } ++ fp->f_cred->user->unix_inflight--; ++ spin_unlock(&unix_gc_lock); + } + + static void scan_inflight(struct sock *x, void (*func)(struct unix_sock *), +diff --git a/scripts/recordmcount.pl b/scripts/recordmcount.pl +index 679218b56ede..f94f6a2d6988 100755 +--- a/scripts/recordmcount.pl ++++ b/scripts/recordmcount.pl +@@ -265,7 +265,8 @@ if ($arch eq "x86_64") { + + } elsif ($arch eq "powerpc") { + $local_regex = "^[0-9a-fA-F]+\\s+t\\s+(\\.?\\S+)"; +- $function_regex = "^([0-9a-fA-F]+)\\s+<(\\.?.*?)>:"; ++ # See comment in the sparc64 section for why we use '\w'. ++ $function_regex = "^([0-9a-fA-F]+)\\s+<(\\.?\\w*?)>:"; + $mcount_regex = "^\\s*([0-9a-fA-F]+):.*\\s\\.?_mcount\$"; + + if ($bits == 64) { +diff --git a/sound/core/control.c b/sound/core/control.c +index f2082a35b890..3fcead61f0ef 100644 +--- a/sound/core/control.c ++++ b/sound/core/control.c +@@ -1325,6 +1325,8 @@ static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file, + return -EFAULT; + if (tlv.length < sizeof(unsigned int) * 2) + return -EINVAL; ++ if (!tlv.numid) ++ return -EINVAL; + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_numid(card, tlv.numid); + if (kctl == NULL) { +diff --git a/sound/core/hrtimer.c b/sound/core/hrtimer.c +index b8b31c433d64..14d483d6b3b0 100644 +--- a/sound/core/hrtimer.c ++++ b/sound/core/hrtimer.c +@@ -90,7 +90,7 @@ static int snd_hrtimer_start(struct snd_timer *t) + struct snd_hrtimer *stime = t->private_data; + + atomic_set(&stime->running, 0); +- hrtimer_cancel(&stime->hrt); ++ hrtimer_try_to_cancel(&stime->hrt); + hrtimer_start(&stime->hrt, ns_to_ktime(t->sticks * resolution), + HRTIMER_MODE_REL); + atomic_set(&stime->running, 1); +@@ -101,6 +101,7 @@ static int snd_hrtimer_stop(struct snd_timer *t) + { + struct snd_hrtimer *stime = t->private_data; + atomic_set(&stime->running, 0); ++ hrtimer_try_to_cancel(&stime->hrt); + return 0; + } + +diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c +index c4ac3c1e19af..1bb1a43c7d03 100644 +--- a/sound/core/pcm_compat.c ++++ b/sound/core/pcm_compat.c +@@ -236,10 +236,15 @@ static int snd_pcm_ioctl_hw_params_compat(struct snd_pcm_substream *substream, + if (! (runtime = substream->runtime)) + return -ENOTTY; + +- /* only fifo_size is different, so just copy all */ +- data = memdup_user(data32, sizeof(*data32)); +- if (IS_ERR(data)) +- return PTR_ERR(data); ++ data = kmalloc(sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ /* only fifo_size (RO from userspace) is different, so just copy all */ ++ if (copy_from_user(data, data32, sizeof(*data32))) { ++ err = -EFAULT; ++ goto error; ++ } + + if (refine) + err = snd_pcm_hw_refine(substream, data); +diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c +index 4dc6bae80e15..ecfbf5f39d38 100644 +--- a/sound/core/seq/seq_clientmgr.c ++++ b/sound/core/seq/seq_clientmgr.c +@@ -1950,7 +1950,7 @@ static int snd_seq_ioctl_remove_events(struct snd_seq_client *client, + * No restrictions so for a user client we can clear + * the whole fifo + */ +- if (client->type == USER_CLIENT) ++ if (client->type == USER_CLIENT && client->data.user.fifo) + snd_seq_fifo_clear(client->data.user.fifo); + } + +diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c +index 81f7c109dc46..65175902a68a 100644 +--- a/sound/core/seq/seq_compat.c ++++ b/sound/core/seq/seq_compat.c +@@ -49,11 +49,12 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned + struct snd_seq_port_info *data; + mm_segment_t fs; + +- data = memdup_user(data32, sizeof(*data32)); +- if (IS_ERR(data)) +- return PTR_ERR(data); ++ data = kmalloc(sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; + +- if (get_user(data->flags, &data32->flags) || ++ if (copy_from_user(data, data32, sizeof(*data32)) || ++ get_user(data->flags, &data32->flags) || + get_user(data->time_queue, &data32->time_queue)) + goto error; + data->kernel = NULL; +diff --git a/sound/core/seq/seq_queue.c b/sound/core/seq/seq_queue.c +index f9077361c119..4c9aa462de9b 100644 +--- a/sound/core/seq/seq_queue.c ++++ b/sound/core/seq/seq_queue.c +@@ -144,8 +144,10 @@ static struct snd_seq_queue *queue_new(int owner, int locked) + static void queue_delete(struct snd_seq_queue *q) + { + /* stop and release the timer */ ++ mutex_lock(&q->timer_mutex); + snd_seq_timer_stop(q->timer); + snd_seq_timer_close(q); ++ mutex_unlock(&q->timer_mutex); + /* wait until access free */ + snd_use_lock_sync(&q->use_lock); + /* release resources... */ +diff --git a/sound/core/timer.c b/sound/core/timer.c +index 6ddcf06f52f9..4e436fe53afa 100644 +--- a/sound/core/timer.c ++++ b/sound/core/timer.c +@@ -73,7 +73,7 @@ struct snd_timer_user { + struct timespec tstamp; /* trigger tstamp */ + wait_queue_head_t qchange_sleep; + struct fasync_struct *fasync; +- struct mutex tread_sem; ++ struct mutex ioctl_lock; + }; + + /* list of timers */ +@@ -215,11 +215,13 @@ static void snd_timer_check_master(struct snd_timer_instance *master) + slave->slave_id == master->slave_id) { + list_move_tail(&slave->open_list, &master->slave_list_head); + spin_lock_irq(&slave_active_lock); ++ spin_lock(&master->timer->lock); + slave->master = master; + slave->timer = master->timer; + if (slave->flags & SNDRV_TIMER_IFLG_RUNNING) + list_add_tail(&slave->active_list, + &master->slave_active_head); ++ spin_unlock(&master->timer->lock); + spin_unlock_irq(&slave_active_lock); + } + } +@@ -345,15 +347,18 @@ int snd_timer_close(struct snd_timer_instance *timeri) + timer->hw.close) + timer->hw.close(timer); + /* remove slave links */ ++ spin_lock_irq(&slave_active_lock); ++ spin_lock(&timer->lock); + list_for_each_entry_safe(slave, tmp, &timeri->slave_list_head, + open_list) { +- spin_lock_irq(&slave_active_lock); +- _snd_timer_stop(slave, 1, SNDRV_TIMER_EVENT_RESOLUTION); + list_move_tail(&slave->open_list, &snd_timer_slave_list); + slave->master = NULL; + slave->timer = NULL; +- spin_unlock_irq(&slave_active_lock); ++ list_del_init(&slave->ack_list); ++ list_del_init(&slave->active_list); + } ++ spin_unlock(&timer->lock); ++ spin_unlock_irq(&slave_active_lock); + mutex_unlock(®ister_mutex); + } + out: +@@ -440,9 +445,12 @@ static int snd_timer_start_slave(struct snd_timer_instance *timeri) + + spin_lock_irqsave(&slave_active_lock, flags); + timeri->flags |= SNDRV_TIMER_IFLG_RUNNING; +- if (timeri->master) ++ if (timeri->master && timeri->timer) { ++ spin_lock(&timeri->timer->lock); + list_add_tail(&timeri->active_list, + &timeri->master->slave_active_head); ++ spin_unlock(&timeri->timer->lock); ++ } + spin_unlock_irqrestore(&slave_active_lock, flags); + return 1; /* delayed start */ + } +@@ -488,6 +496,8 @@ static int _snd_timer_stop(struct snd_timer_instance * timeri, + if (!keep_flag) { + spin_lock_irqsave(&slave_active_lock, flags); + timeri->flags &= ~SNDRV_TIMER_IFLG_RUNNING; ++ list_del_init(&timeri->ack_list); ++ list_del_init(&timeri->active_list); + spin_unlock_irqrestore(&slave_active_lock, flags); + } + goto __end; +@@ -693,7 +703,7 @@ void snd_timer_interrupt(struct snd_timer * timer, unsigned long ticks_left) + } else { + ti->flags &= ~SNDRV_TIMER_IFLG_RUNNING; + if (--timer->running) +- list_del(&ti->active_list); ++ list_del_init(&ti->active_list); + } + if ((timer->hw.flags & SNDRV_TIMER_HW_TASKLET) || + (ti->flags & SNDRV_TIMER_IFLG_FAST)) +@@ -1256,7 +1266,7 @@ static int snd_timer_user_open(struct inode *inode, struct file *file) + return -ENOMEM; + spin_lock_init(&tu->qlock); + init_waitqueue_head(&tu->qchange_sleep); +- mutex_init(&tu->tread_sem); ++ mutex_init(&tu->ioctl_lock); + tu->ticks = 1; + tu->queue_size = 128; + tu->queue = kmalloc(tu->queue_size * sizeof(struct snd_timer_read), +@@ -1276,8 +1286,10 @@ static int snd_timer_user_release(struct inode *inode, struct file *file) + if (file->private_data) { + tu = file->private_data; + file->private_data = NULL; ++ mutex_lock(&tu->ioctl_lock); + if (tu->timeri) + snd_timer_close(tu->timeri); ++ mutex_unlock(&tu->ioctl_lock); + kfree(tu->queue); + kfree(tu->tqueue); + kfree(tu); +@@ -1515,7 +1527,6 @@ static int snd_timer_user_tselect(struct file *file, + int err = 0; + + tu = file->private_data; +- mutex_lock(&tu->tread_sem); + if (tu->timeri) { + snd_timer_close(tu->timeri); + tu->timeri = NULL; +@@ -1559,7 +1570,6 @@ static int snd_timer_user_tselect(struct file *file, + } + + __err: +- mutex_unlock(&tu->tread_sem); + return err; + } + +@@ -1772,7 +1782,7 @@ enum { + SNDRV_TIMER_IOCTL_PAUSE_OLD = _IO('T', 0x23), + }; + +-static long snd_timer_user_ioctl(struct file *file, unsigned int cmd, ++static long __snd_timer_user_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) + { + struct snd_timer_user *tu; +@@ -1789,17 +1799,11 @@ static long snd_timer_user_ioctl(struct file *file, unsigned int cmd, + { + int xarg; + +- mutex_lock(&tu->tread_sem); +- if (tu->timeri) { /* too late */ +- mutex_unlock(&tu->tread_sem); ++ if (tu->timeri) /* too late */ + return -EBUSY; +- } +- if (get_user(xarg, p)) { +- mutex_unlock(&tu->tread_sem); ++ if (get_user(xarg, p)) + return -EFAULT; +- } + tu->tread = xarg ? 1 : 0; +- mutex_unlock(&tu->tread_sem); + return 0; + } + case SNDRV_TIMER_IOCTL_GINFO: +@@ -1832,6 +1836,18 @@ static long snd_timer_user_ioctl(struct file *file, unsigned int cmd, + return -ENOTTY; + } + ++static long snd_timer_user_ioctl(struct file *file, unsigned int cmd, ++ unsigned long arg) ++{ ++ struct snd_timer_user *tu = file->private_data; ++ long ret; ++ ++ mutex_lock(&tu->ioctl_lock); ++ ret = __snd_timer_user_ioctl(file, cmd, arg); ++ mutex_unlock(&tu->ioctl_lock); ++ return ret; ++} ++ + static int snd_timer_user_fasync(int fd, struct file * file, int on) + { + struct snd_timer_user *tu; +diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c +index 1800db643a16..57d01f101b04 100644 +--- a/sound/pci/hda/hda_intel.c ++++ b/sound/pci/hda/hda_intel.c +@@ -1104,6 +1104,36 @@ static unsigned int azx_get_response(struct hda_bus *bus, + return azx_rirb_get_response(bus, addr); + } + ++#ifdef CONFIG_PM_SLEEP ++/* put codec down to D3 at hibernation for Intel SKL+; ++ * otherwise BIOS may still access the codec and screw up the driver ++ */ ++#define IS_SKL(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0xa170) ++#define IS_SKL_LP(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x9d70) ++#define IS_BXT(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x5a98) ++#define IS_SKL_PLUS(pci) (IS_SKL(pci) || IS_SKL_LP(pci) || IS_BXT(pci)) ++ ++static int azx_freeze_noirq(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ ++ if (IS_SKL_PLUS(pci)) ++ pci_set_power_state(pci, PCI_D3hot); ++ ++ return 0; ++} ++ ++static int azx_thaw_noirq(struct device *dev) ++{ ++ struct pci_dev *pci = to_pci_dev(dev); ++ ++ if (IS_SKL_PLUS(pci)) ++ pci_set_power_state(pci, PCI_D0); ++ ++ return 0; ++} ++#endif /* CONFIG_PM_SLEEP */ ++ + #ifdef CONFIG_PM + static void azx_power_notify(struct hda_bus *bus, bool power_up); + #endif +@@ -2974,6 +3004,10 @@ static int azx_runtime_idle(struct device *dev) + #ifdef CONFIG_PM + static const struct dev_pm_ops azx_pm = { + SET_SYSTEM_SLEEP_PM_OPS(azx_suspend, azx_resume) ++#ifdef CONFIG_PM_SLEEP ++ .freeze_noirq = azx_freeze_noirq, ++ .thaw_noirq = azx_thaw_noirq, ++#endif + SET_RUNTIME_PM_OPS(azx_runtime_suspend, azx_runtime_resume, azx_runtime_idle) + }; + +@@ -3864,6 +3898,11 @@ static DEFINE_PCI_DEVICE_TABLE(azx_ids) = { + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, + { PCI_DEVICE(0x8086, 0x8d21), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, ++ /* Lewisburg */ ++ { PCI_DEVICE(0x8086, 0xa1f0), ++ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, ++ { PCI_DEVICE(0x8086, 0xa270), ++ .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, + /* Lynx Point-LP */ + { PCI_DEVICE(0x8086, 0x9c20), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, +diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c +index 183a96ab2533..69a2aafb0b0f 100644 +--- a/sound/pci/hda/patch_realtek.c ++++ b/sound/pci/hda/patch_realtek.c +@@ -1768,6 +1768,7 @@ enum { + ALC889_FIXUP_MBA11_VREF, + ALC889_FIXUP_MBA21_VREF, + ALC889_FIXUP_MP11_VREF, ++ ALC889_FIXUP_MP41_VREF, + ALC882_FIXUP_INV_DMIC, + ALC882_FIXUP_NO_PRIMARY_HP, + ALC887_FIXUP_ASUS_BASS, +@@ -1854,7 +1855,7 @@ static void alc889_fixup_mbp_vref(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { + struct alc_spec *spec = codec->spec; +- static hda_nid_t nids[2] = { 0x14, 0x15 }; ++ static hda_nid_t nids[3] = { 0x14, 0x15, 0x19 }; + int i; + + if (action != HDA_FIXUP_ACT_INIT) +@@ -2128,6 +2129,12 @@ static const struct hda_fixup alc882_fixups[] = { + .chained = true, + .chain_id = ALC885_FIXUP_MACPRO_GPIO, + }, ++ [ALC889_FIXUP_MP41_VREF] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc889_fixup_mbp_vref, ++ .chained = true, ++ .chain_id = ALC885_FIXUP_MACPRO_GPIO, ++ }, + [ALC882_FIXUP_INV_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic_0x12, +@@ -2200,7 +2207,7 @@ static const struct snd_pci_quirk alc882_fixup_tbl[] = { + SND_PCI_QUIRK(0x106b, 0x3f00, "Macbook 5,1", ALC889_FIXUP_IMAC91_VREF), + SND_PCI_QUIRK(0x106b, 0x4000, "MacbookPro 5,1", ALC889_FIXUP_IMAC91_VREF), + SND_PCI_QUIRK(0x106b, 0x4100, "Macmini 3,1", ALC889_FIXUP_IMAC91_VREF), +- SND_PCI_QUIRK(0x106b, 0x4200, "Mac Pro 5,1", ALC885_FIXUP_MACPRO_GPIO), ++ SND_PCI_QUIRK(0x106b, 0x4200, "Mac Pro 4,1/5,1", ALC889_FIXUP_MP41_VREF), + SND_PCI_QUIRK(0x106b, 0x4300, "iMac 9,1", ALC889_FIXUP_IMAC91_VREF), + SND_PCI_QUIRK(0x106b, 0x4600, "MacbookPro 5,2", ALC889_FIXUP_IMAC91_VREF), + SND_PCI_QUIRK(0x106b, 0x4900, "iMac 9,1 Aluminum", ALC889_FIXUP_IMAC91_VREF), +@@ -4446,6 +4453,7 @@ static const struct hda_fixup alc662_fixups[] = { + static const struct snd_pci_quirk alc662_fixup_tbl[] = { + SND_PCI_QUIRK(0x1019, 0x9087, "ECS", ALC662_FIXUP_ASUS_MODE2), + SND_PCI_QUIRK(0x1025, 0x022f, "Acer Aspire One", ALC662_FIXUP_INV_DMIC), ++ SND_PCI_QUIRK(0x1025, 0x0241, "Packard Bell DOTS", ALC662_FIXUP_INV_DMIC), + SND_PCI_QUIRK(0x1025, 0x0308, "Acer Aspire 8942G", ALC662_FIXUP_ASPIRE), + SND_PCI_QUIRK(0x1025, 0x031c, "Gateway NV79", ALC662_FIXUP_SKU_IGNORE), + SND_PCI_QUIRK(0x1025, 0x0349, "eMachines eM250", ALC662_FIXUP_INV_DMIC), +diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c +index 44d3fb95ebba..5ffe7992aaed 100644 +--- a/sound/pci/hda/patch_sigmatel.c ++++ b/sound/pci/hda/patch_sigmatel.c +@@ -703,6 +703,7 @@ static bool hp_bnb2011_with_dock(struct hda_codec *codec) + static bool hp_blike_system(u32 subsystem_id) + { + switch (subsystem_id) { ++ case 0x103c1473: /* HP ProBook 6550b */ + case 0x103c1520: + case 0x103c1521: + case 0x103c1523: +diff --git a/sound/pci/rme96.c b/sound/pci/rme96.c +index 5fb88ac82aa9..4fdb234d74b4 100644 +--- a/sound/pci/rme96.c ++++ b/sound/pci/rme96.c +@@ -703,10 +703,11 @@ snd_rme96_playback_setrate(struct rme96 *rme96, + { + /* change to/from double-speed: reset the DAC (if available) */ + snd_rme96_reset_dac(rme96); ++ return 1; /* need to restore volume */ + } else { + writel(rme96->wcreg, rme96->iobase + RME96_IO_CONTROL_REGISTER); ++ return 0; + } +- return 0; + } + + static int +@@ -944,6 +945,7 @@ snd_rme96_playback_hw_params(struct snd_pcm_substream *substream, + struct rme96 *rme96 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err, rate, dummy; ++ bool apply_dac_volume = false; + + runtime->dma_area = (void __force *)(rme96->iobase + + RME96_IO_PLAY_BUFFER); +@@ -957,24 +959,26 @@ snd_rme96_playback_hw_params(struct snd_pcm_substream *substream, + { + /* slave clock */ + if ((int)params_rate(params) != rate) { +- spin_unlock_irq(&rme96->lock); +- return -EIO; +- } +- } else if ((err = snd_rme96_playback_setrate(rme96, params_rate(params))) < 0) { +- spin_unlock_irq(&rme96->lock); +- return err; +- } +- if ((err = snd_rme96_playback_setformat(rme96, params_format(params))) < 0) { +- spin_unlock_irq(&rme96->lock); +- return err; ++ err = -EIO; ++ goto error; ++ } ++ } else { ++ err = snd_rme96_playback_setrate(rme96, params_rate(params)); ++ if (err < 0) ++ goto error; ++ apply_dac_volume = err > 0; /* need to restore volume later? */ + } ++ ++ err = snd_rme96_playback_setformat(rme96, params_format(params)); ++ if (err < 0) ++ goto error; + snd_rme96_setframelog(rme96, params_channels(params), 1); + if (rme96->capture_periodsize != 0) { + if (params_period_size(params) << rme96->playback_frlog != + rme96->capture_periodsize) + { +- spin_unlock_irq(&rme96->lock); +- return -EBUSY; ++ err = -EBUSY; ++ goto error; + } + } + rme96->playback_periodsize = +@@ -985,9 +989,16 @@ snd_rme96_playback_hw_params(struct snd_pcm_substream *substream, + rme96->wcreg &= ~(RME96_WCR_PRO | RME96_WCR_DOLBY | RME96_WCR_EMP); + writel(rme96->wcreg |= rme96->wcreg_spdif_stream, rme96->iobase + RME96_IO_CONTROL_REGISTER); + } ++ ++ err = 0; ++ error: + spin_unlock_irq(&rme96->lock); +- +- return 0; ++ if (apply_dac_volume) { ++ usleep_range(3000, 10000); ++ snd_rme96_apply_dac_volume(rme96); ++ } ++ ++ return err; + } + + static int +diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c +index 663a2a748626..56815af4e00b 100644 +--- a/sound/soc/codecs/arizona.c ++++ b/sound/soc/codecs/arizona.c +@@ -1057,7 +1057,7 @@ static int arizona_hw_params(struct snd_pcm_substream *substream, + int chan_limit = arizona->pdata.max_channels_clocked[dai->id - 1]; + int bclk, lrclk, wl, frame, bclk_target; + +- if (params_rate(params) % 8000) ++ if (params_rate(params) % 4000) + rates = &arizona_44k1_bclk_rates[0]; + else + rates = &arizona_48k_bclk_rates[0]; +diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c +index 1ae1f8bd9c36..305d28dec668 100644 +--- a/sound/soc/codecs/wm8962.c ++++ b/sound/soc/codecs/wm8962.c +@@ -363,8 +363,8 @@ static struct reg_default wm8962_reg[] = { + { 16924, 0x0059 }, /* R16924 - HDBASS_PG_1 */ + { 16925, 0x999A }, /* R16925 - HDBASS_PG_0 */ + +- { 17048, 0x0083 }, /* R17408 - HPF_C_1 */ +- { 17049, 0x98AD }, /* R17409 - HPF_C_0 */ ++ { 17408, 0x0083 }, /* R17408 - HPF_C_1 */ ++ { 17409, 0x98AD }, /* R17409 - HPF_C_0 */ + + { 17920, 0x007F }, /* R17920 - ADCL_RETUNE_C1_1 */ + { 17921, 0xFFFF }, /* R17921 - ADCL_RETUNE_C1_0 */ +diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c +index 06a8000aa07b..2340554981d6 100644 +--- a/sound/soc/soc-compress.c ++++ b/sound/soc/soc-compress.c +@@ -384,17 +384,34 @@ int soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) + struct snd_compr *compr; + char new_name[64]; + int ret = 0, direction = 0; ++ int playback = 0, capture = 0; + + /* check client and interface hw capabilities */ + snprintf(new_name, sizeof(new_name), "%s %s-%d", + rtd->dai_link->stream_name, codec_dai->name, num); + + if (codec_dai->driver->playback.channels_min) ++ playback = 1; ++ if (codec_dai->driver->capture.channels_min) ++ capture = 1; ++ ++ capture = capture && cpu_dai->driver->capture.channels_min; ++ playback = playback && cpu_dai->driver->playback.channels_min; ++ ++ /* ++ * Compress devices are unidirectional so only one of the directions ++ * should be set, check for that (xor) ++ */ ++ if (playback + capture != 1) { ++ dev_err(rtd->card->dev, "Invalid direction for compress P %d, C %d\n", ++ playback, capture); ++ return -EINVAL; ++ } ++ ++ if(playback) + direction = SND_COMPRESS_PLAYBACK; +- else if (codec_dai->driver->capture.channels_min) +- direction = SND_COMPRESS_CAPTURE; + else +- return -EINVAL; ++ direction = SND_COMPRESS_CAPTURE; + + compr = kzalloc(sizeof(*compr), GFP_KERNEL); + if (compr == NULL) {