pwg5300: add pwg5300 and LCD support

This commit is contained in:
Suguru Saito 2021-04-23 22:53:02 +09:00 committed by Takumi Sueda
parent c6fac22bce
commit 0001409424
6 changed files with 1032 additions and 0 deletions

View File

@ -742,6 +742,7 @@ dtb-$(CONFIG_ARCH_MXS) += \
imx28-sps1.dtb \
imx28-ts4600.dtb \
imx28-tx28.dtb \
imx28-pwg5300.dtb \
imx28-pwsh1.dtb \
imx28-pwsh2.dtb \
imx28-pwsh3.dtb \

View File

@ -0,0 +1,113 @@
// SPDX-License-Identifier: GPL-2.0+
//
// Copyright 2012 Freescale Semiconductor, Inc.
/dts-v1/;
#include <dt-bindings/input/input.h>
#include "imx28-brain-2g.dtsi"
/ {
model = "SHARP Brain PW-G5300";
compatible = "sharp,pw-g5300", "sharp,brain", "fsl,imx28";
};
&brainlcd {
status = "okay";
sharp,lcd-width = <480>;
sharp,lcd-height = <320>;
sharp,lcd-width-mm = <152>;
sharp,lcd-height-mm = <73>;
sharp,en-gpios =
<&gpio0 26 GPIO_ACTIVE_HIGH>,
<&gpio0 27 GPIO_ACTIVE_HIGH>,
<&gpio4 16 GPIO_ACTIVE_HIGH>;
};
&keyboard_i2c {
status = "okay";
keymap =
<0x01 KEY_POWER>, /* Power */
<0x07 KEY_ESC>, /* Search */
<0x0d KEY_TAB>, /* Kokugo */
<0x11 KEY_PAGEUP>, /* Eiwa Waei */
<0x15 KEY_PAGEDOWN>, /* My Dictionary */
<0x25 KEY_INSERT>, /* History / Bookmark */
<0x1d KEY_DELETE>, /* Marker test */
/* <0x2b Memorization tool> */
/* <0x24 Home> */
<0x02 KEY_Q>, /* Q */
<0x08 KEY_W>, /* W */
<0x0e KEY_E>, /* E */
<0x12 KEY_R>, /* R */
<0x16 KEY_T>, /* T */
<0x1e KEY_Y>, /* Y */
<0x26 KEY_U>, /* U */
<0x2c KEY_I>, /* I */
<0x27 KEY_O>, /* O */
<0x2d KEY_P>, /* P */
<0x03 KEY_A>, /* A */
<0x09 KEY_S>, /* S */
<0x0f KEY_D>, /* D */
<0x13 KEY_F>, /* F */
<0x17 KEY_G>, /* G */
<0x1f KEY_H>, /* H */
<0x20 KEY_J>, /* J */
<0x28 KEY_K>, /* K */
<0x2e KEY_L>, /* L */
<0x05 KEY_LEFTSHIFT>,
<0x04 KEY_Z>, /* Z */
<0x0a KEY_X>, /* X */
<0x10 KEY_C>, /* C */
<0x14 KEY_V>, /* V */
<0x18 KEY_B>, /* B */
<0x21 KEY_N>, /* N */
<0x29 KEY_M>, /* M */
<0x2f KEY_MINUS>, /* Minus */
<0x31 KEY_BACKSPACE>, /* Backspace */
<0x0b KEY_LEFTCTRL>, /* Page Up */
/* <0x0c KEY_PAGEDOWN>, */ /* Page Down */
<0x06 KEY_LEFTALT>, /* Switch characters */
/* < 0x19, Symbols>, */
<0x1b KEY_ESC>, /* Go Back */
<0x1c KEY_SPACE>, /* Space */
<0x23 KEY_ENTER>, /* Enter */
<0x1a KEY_LEFT>, /* Left */
<0x22 KEY_UP>, /* Up */
<0x2a KEY_DOWN>, /* Down */
<0x30 KEY_RIGHT>; /* Right */
keymap-symbol =
<0x01 KEY_POWER>, /* Power */
<0x07 KEY_ESC>, /* Search */
<0x0d KEY_TAB>, /* Kokugo */
<0x11 KEY_PAGEUP>, /* Eiwa Waei */
<0x15 KEY_PAGEDOWN>, /* My Dictionary */
<0x25 KEY_INSERT>, /* History / Bookmark */
<0x1d KEY_DELETE>, /* Marker test */
<0x02 KEY_1>, /* Q */
<0x08 KEY_2>, /* W */
<0x0e KEY_3>, /* E */
<0x12 KEY_4>, /* R */
<0x16 KEY_5>, /* T */
<0x1e KEY_6>, /* Y */
<0x26 KEY_7>, /* U */
<0x2c KEY_8>, /* I */
<0x27 KEY_9>, /* O */
<0x2d KEY_0>, /* P */
<0x0f KEY_GRAVE>, /* D */
<0x13 KEY_EQUAL>, /* F */
<0x17 KEY_BACKSLASH>, /* G */
<0x1f KEY_SEMICOLON>, /* H */
<0x20 KEY_APOSTROPHE>, /* J */
<0x28 KEY_LEFTBRACE>, /* K */
<0x2e KEY_RIGHTBRACE>, /* L */
<0x05 KEY_LEFTSHIFT>,
<0x21 KEY_COMMA>, /* N */
<0x29 KEY_DOT>, /* M */
<0x2f KEY_SLASH>, /* Minus */
<0x31 KEY_BACKSPACE>, /* Backspace */
<0x0b KEY_LEFTCTRL>, /* Page Up */
/* <0x0c KEY_PAGEDOWN>, */ /* Page Down */
<0x06 KEY_LEFTALT>; /* Switch characters */
};

View File

@ -0,0 +1,207 @@
CONFIG_SYSVIPC=y
CONFIG_NO_HZ=y
CONFIG_HIGH_RES_TIMERS=y
CONFIG_PREEMPT=y
CONFIG_TASKSTATS=y
CONFIG_TASK_DELAY_ACCT=y
CONFIG_TASK_XACCT=y
CONFIG_TASK_IO_ACCOUNTING=y
CONFIG_IKCONFIG=y
CONFIG_IKCONFIG_PROC=y
CONFIG_CGROUPS=y
# CONFIG_UTS_NS is not set
# CONFIG_IPC_NS is not set
# CONFIG_PID_NS is not set
# CONFIG_NET_NS is not set
CONFIG_BLK_DEV_INITRD=y
CONFIG_PERF_EVENTS=y
# CONFIG_COMPAT_BRK is not set
# CONFIG_ARCH_MULTI_V7 is not set
CONFIG_ARCH_MXS=y
CONFIG_AEABI=y
CONFIG_MODULES=y
CONFIG_MODULE_FORCE_LOAD=y
CONFIG_MODULE_UNLOAD=y
CONFIG_MODULE_FORCE_UNLOAD=y
CONFIG_MODVERSIONS=y
CONFIG_TRIM_UNUSED_KSYMS=y
CONFIG_BLK_DEV_INTEGRITY=y
CONFIG_FRONTSWAP=y
CONFIG_ZSWAP=y
CONFIG_ZBUD=y
CONFIG_Z3FOLD=y
CONFIG_NET=y
CONFIG_PACKET=y
CONFIG_UNIX=y
CONFIG_INET=y
CONFIG_IP_PNP=y
CONFIG_IP_PNP_DHCP=y
CONFIG_SYN_COOKIES=y
# CONFIG_INET_DIAG is not set
CONFIG_CFG80211=y
CONFIG_CFG80211_WEXT=y
CONFIG_MAC80211=y
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
CONFIG_MTD=y
CONFIG_MTD_CMDLINE_PARTS=y
CONFIG_MTD_BLOCK=y
CONFIG_MTD_DATAFLASH=y
CONFIG_MTD_SST25L=y
CONFIG_MTD_RAW_NAND=y
CONFIG_MTD_NAND_GPMI_NAND=y
CONFIG_MTD_SPI_NOR=y
CONFIG_MTD_UBI=y
# CONFIG_BLK_DEV is not set
CONFIG_EEPROM_AT24=y
CONFIG_SCSI=y
CONFIG_BLK_DEV_SD=y
CONFIG_NETDEVICES=y
CONFIG_ENC28J60=y
CONFIG_ICPLUS_PHY=y
CONFIG_MICREL_PHY=y
CONFIG_REALTEK_PHY=y
CONFIG_SMSC_PHY=y
CONFIG_USB_RTL8150=y
CONFIG_USB_RTL8152=y
CONFIG_USB_USBNET=y
CONFIG_USB_NET_SMSC95XX=y
CONFIG_USB_NET_RNDIS_HOST=y
# CONFIG_WLAN_VENDOR_ADMTEK is not set
CONFIG_ATH9K=y
CONFIG_ATH9K_HTC=y
CONFIG_ATH6KL=y
CONFIG_ATH6KL_USB=y
CONFIG_AR5523=y
CONFIG_ATH10K=y
CONFIG_ATH10K_USB=y
CONFIG_WCN36XX=y
# CONFIG_WLAN_VENDOR_ATMEL is not set
# CONFIG_WLAN_VENDOR_BROADCOM is not set
# CONFIG_WLAN_VENDOR_CISCO is not set
# CONFIG_WLAN_VENDOR_INTEL is not set
# CONFIG_WLAN_VENDOR_INTERSIL is not set
# CONFIG_WLAN_VENDOR_MARVELL is not set
CONFIG_MT7601U=y
CONFIG_MT76x0U=y
CONFIG_MT76x2U=y
CONFIG_RT2X00=y
CONFIG_RT2500USB=y
CONFIG_RT73USB=y
CONFIG_RT2800USB=y
CONFIG_RT2800USB_RT3573=y
CONFIG_RT2800USB_RT53XX=y
CONFIG_RT2800USB_RT55XX=y
CONFIG_RTL8187=y
CONFIG_RTL8XXXU=y
CONFIG_RTW88=y
# CONFIG_WLAN_VENDOR_RSI is not set
# CONFIG_WLAN_VENDOR_ST is not set
# CONFIG_WLAN_VENDOR_TI is not set
# CONFIG_WLAN_VENDOR_ZYDAS is not set
# CONFIG_WLAN_VENDOR_QUANTENNA is not set
CONFIG_INPUT_EVDEV=y
CONFIG_KEYBOARD_BRAIN_I2C=y
# CONFIG_INPUT_MOUSE is not set
CONFIG_INPUT_TOUCHSCREEN=y
CONFIG_TOUCHSCREEN_MXS_LRADC=y
CONFIG_TOUCHSCREEN_TSC2007=m
# CONFIG_LEGACY_PTYS is not set
CONFIG_SERIAL_AMBA_PL011=y
CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
CONFIG_SERIAL_MXS_AUART=y
# CONFIG_HW_RANDOM is not set
# CONFIG_I2C_COMPAT is not set
CONFIG_I2C_CHARDEV=y
CONFIG_I2C_MXS=y
CONFIG_SPI=y
CONFIG_SPI_GPIO=m
CONFIG_SPI_MXS=y
CONFIG_GPIO_SYSFS=y
# CONFIG_HWMON is not set
CONFIG_WATCHDOG=y
CONFIG_STMP3XXX_RTC_WATCHDOG=y
CONFIG_MFD_MXS_LRADC=y
CONFIG_REGULATOR=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
CONFIG_DRM=y
CONFIG_DRM_UDL=y
CONFIG_DRM_PANEL_SEIKO_43WVF1G=y
CONFIG_TINYDRM_BRAIN_2G=y
CONFIG_FB_MODE_HELPERS=y
CONFIG_LCD_CLASS_DEVICE=y
CONFIG_BACKLIGHT_PWM=y
CONFIG_LOGO=y
# CONFIG_LOGO_LINUX_MONO is not set
# CONFIG_LOGO_LINUX_VGA16 is not set
# CONFIG_LOGO_LINUX_CLUT224 is not set
CONFIG_SOUND=y
CONFIG_SND=y
CONFIG_SND_SOC=y
CONFIG_SND_MXS_SOC=y
CONFIG_SND_SOC_MXS_SGTL5000=y
CONFIG_USB_EHCI_HCD=y
CONFIG_USB_STORAGE=y
CONFIG_USB_CHIPIDEA=y
CONFIG_USB_CHIPIDEA_UDC=y
CONFIG_USB_CHIPIDEA_HOST=y
CONFIG_USB_MXS_PHY=y
CONFIG_USB_GADGET=y
CONFIG_USB_CONFIGFS=y
CONFIG_USB_CONFIGFS_RNDIS=y
CONFIG_MMC=y
CONFIG_MMC_MXS=y
CONFIG_LEDS_GPIO=y
CONFIG_LEDS_TRIGGER_TIMER=y
CONFIG_LEDS_TRIGGER_ONESHOT=y
CONFIG_LEDS_TRIGGER_HEARTBEAT=y
CONFIG_LEDS_TRIGGER_BACKLIGHT=y
CONFIG_LEDS_TRIGGER_GPIO=y
CONFIG_RTC_CLASS=y
CONFIG_RTC_DRV_DS1307=m
CONFIG_RTC_DRV_STMP=y
CONFIG_DMADEVICES=y
CONFIG_MXS_DMA=y
CONFIG_STAGING=y
CONFIG_WILC1000_SDIO=y
CONFIG_EXFAT_FS=y
CONFIG_IIO=y
CONFIG_MXS_LRADC_ADC=y
CONFIG_IIO_SYSFS_TRIGGER=y
CONFIG_PWM=y
CONFIG_PWM_MXS=y
CONFIG_NVMEM_MXS_OCOTP=y
CONFIG_EXT4_FS=y
# CONFIG_DNOTIFY is not set
CONFIG_FSCACHE=m
CONFIG_FSCACHE_STATS=y
CONFIG_CACHEFILES=m
CONFIG_VFAT_FS=y
CONFIG_TMPFS=y
CONFIG_TMPFS_POSIX_ACL=y
CONFIG_JFFS2_FS=y
CONFIG_JFFS2_COMPRESSION_OPTIONS=y
CONFIG_JFFS2_LZO=y
CONFIG_JFFS2_RUBIN=y
CONFIG_UBIFS_FS=y
CONFIG_UBIFS_FS_ADVANCED_COMPR=y
CONFIG_NFS_FS=y
CONFIG_NFS_V3_ACL=y
CONFIG_NFS_V4=y
CONFIG_ROOT_NFS=y
CONFIG_NLS_CODEPAGE_437=y
CONFIG_NLS_CODEPAGE_850=y
CONFIG_NLS_ISO8859_1=y
CONFIG_NLS_ISO8859_15=y
CONFIG_CRYPTO_DEV_MXS_DCP=y
CONFIG_CRC7=m
CONFIG_FONTS=y
CONFIG_FONT_8x16=y
CONFIG_FONT_6x11=y
CONFIG_PRINTK_TIME=y
CONFIG_FRAME_WARN=2048
CONFIG_DEBUG_FS=y
CONFIG_STACKTRACE=y
# CONFIG_RUNTIME_TESTING_MENU is not set
CONFIG_STRICT_DEVMEM=y
CONFIG_DEBUG_USER=y

View File

@ -59,6 +59,18 @@ config TINYDRM_BRAIN
If M is selected the module will be called brain.
config TINYDRM_BRAIN_2G
tristate "DRM support for LQ050J1UG01 display panels on Sharp Brain"
depends on DRM
select DRM_KMS_HELPER
select DRM_KMS_CMA_HELPER
select BACKLIGHT_CLASS_DEVICE
help
DRM driver for the following Sharp LQ050J1UG01 panels:
* Sharp Brain 480x320 TFT
If M is selected the module will be called brain.
config TINYDRM_MI0283QT
tristate "DRM support for MI0283QT"
depends on DRM && SPI

View File

@ -5,6 +5,7 @@ obj-$(CONFIG_TINYDRM_HX8357D) += hx8357d.o
obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o
obj-$(CONFIG_TINYDRM_ILI9341) += ili9341.o
obj-$(CONFIG_TINYDRM_BRAIN) += brain.o
obj-$(CONFIG_TINYDRM_BRAIN_2G) += brain-lq050j1ug01.o
obj-$(CONFIG_TINYDRM_MI0283QT) += mi0283qt.o
obj-$(CONFIG_TINYDRM_REPAPER) += repaper.o
obj-$(CONFIG_TINYDRM_ST7586) += st7586.o

View File

@ -0,0 +1,698 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* DRM driver for Sharp LQ050J1UG01 panels on Sharp Brain
*
* Copyright 2021 Suguru Saito <sg.sgch07@gmail.com>
*
* Based on brain.c
* Copyright 2020 Takumi Sueda <puhitaku@gmail.com>
*
* Based on ili9341.c
* Copyright 2018 David Lechner <david@lechnology.com>
*
* Based on mi0283qt.c:
* Copyright 2016 Noralf Trønnes
*/
#include <linux/backlight.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_damage_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_mipi_dbi.h>
#include <drm/drm_modeset_helper.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include <video/mipi_display.h>
#include "mxsfb_regs.h"
#define CTRL1_RESET (1 << 0)
#define RESET_TIMEOUT 1000000
#define TX_TIMEOUT 1000000
struct brain_drm_private {
void __iomem *base;
struct platform_device *pdev;
struct clk *clk_lcdif;
bool enabled;
struct drm_simple_display_pipe pipe;
struct drm_connector connector;
struct drm_panel *panel;
};
static const struct {
u8 payload;
u8 data;
u32 delay;
} lcd_regs[] = {
{ 0x3a, 0, 0 },
{ 0x55, 1, 0 },
{ 0xb2, 0, 0 },
{ 0x45, 1, 0 }, { 0x00, 1, 0 }, { 0xd9, 1, 0 }, { 0x00, 1, 0 },
{ 0x00, 1, 0 },
{ 0xb3, 0, 0 },
{ 0x81, 1, 0 }, { 0x00, 1, 0 }, { 0x01, 1, 0 },
{ 0xb4, 0, 0 },
{ 0x00, 1, 0 },
{ 0xb5, 0, 0 },
{ 0x02, 1, 0 }, { 0x11, 1, 0 }, { 0x50, 1, 0 }, { 0x00, 1, 0 },
{ 0x80, 1, 0 }, { 0x45, 1, 0 }, { 0x45, 1, 0 }, { 0x00, 1, 0 },
{ 0xb6, 0, 0 },
{ 0x1e, 1, 0 }, { 0x01, 1, 0 }, { 0x90, 1, 0 }, { 0x0a, 1, 0 },
{ 0x02, 1, 0 }, { 0x58, 1, 0 },
{ 0xb7, 0, 0 },
{ 0x2a, 1, 0 }, { 0x91, 1, 0 }, { 0x5c, 1, 0 }, { 0x06, 1, 0 },
{ 0x08, 1, 0 }, { 0x0c, 1, 0 }, { 0x00, 1, 0 }, { 0x1c, 1, 0 },
{ 0x06, 1, 0 }, { 0x02, 1, 0 }, { 0x09, 1, 0 },
{ 0xb9, 0, 0 },
{ 0x00, 1, 0 }, { 0x32, 1, 0 }, { 0x01, 1, 0 }, { 0x40, 1, 0 },
{ 0x00, 1, 0 },
{ 0xc0, 0, 0 },
{ 0xb7, 1, 0 }, { 0x03, 1, 0 },
{ 0xc1, 0, 0 },
{ 0x72, 1, 0 }, { 0x01, 1, 0 },
{ 0xc2, 0, 0 },
{ 0x37, 1, 0 }, { 0x2f, 1, 0 }, { 0x0c, 1, 0 },
{ 0xc3, 0, 0 },
{ 0x37, 1, 0 }, { 0x03, 1, 0 },
{ 0xc7, 0, 0 },
{ 0x01, 1, 0 }, { 0x33, 1, 0 }, { 0x03, 1, 0 },
{ 0xca, 0, 0 },
{ 0xbd, 1, 0 }, { 0x17, 1, 0 }, { 0x5b, 1, 0 }, { 0x5b, 1, 0 },
{ 0x64, 1, 0 }, { 0x11, 1, 0 }, { 0x66, 1, 0 },
{ 0xde, 0, 0 },
{ 0x11, 1, 0 }, { 0x00, 1, 0 },
{ 0xe0, 0, 0 },
{ 0x24, 1, 0 }, { 0x3f, 1, 0 }, { 0x0e, 1, 0 }, { 0x0e, 1, 0 },
{ 0x67, 1, 0 }, { 0xee, 1, 0 }, { 0xee, 1, 0 }, { 0xa3, 1, 0 },
{ 0x04, 1, 0 },
{ 0xe1, 0, 0 },
{ 0x24, 1, 0 }, { 0x3f, 1, 0 }, { 0x0f, 1, 0 }, { 0x0e, 1, 0 },
{ 0x78, 1, 0 }, { 0xee, 1, 0 }, { 0xed, 1, 0 }, { 0x93, 1, 0 },
{ 0x04, 1, 0 },
{ 0xe2, 0, 0 },
{ 0x24, 1, 0 }, { 0x29, 1, 0 }, { 0x14, 1, 0 }, { 0x1c, 1, 0 },
{ 0x67, 1, 0 }, { 0xdd, 1, 0 }, { 0xdd, 1, 0 }, { 0x97, 1, 0 },
{ 0x0b, 1, 0 },
{ 0xe3, 0, 0 },
{ 0x24, 1, 0 }, { 0x29, 1, 0 }, { 0x14, 1, 0 }, { 0x1c, 1, 0 },
{ 0x67, 1, 0 }, { 0xdd, 1, 0 }, { 0xdd, 1, 0 }, { 0x97, 1, 0 },
{ 0x0a, 1, 0 },
{ 0xe4, 0, 0 },
{ 0x24, 1, 0 }, { 0x2a, 1, 0 }, { 0x15, 1, 0 }, { 0x1a, 1, 0 },
{ 0x99, 1, 0 }, { 0xdd, 1, 0 }, { 0xed, 1, 0 }, { 0xa6, 1, 0 },
{ 0x09, 1, 0 },
{ 0xe5, 0, 0 },
{ 0x24, 1, 0 }, { 0x2a, 1, 0 }, { 0x15, 1, 0 }, { 0x1a, 1, 0 },
{ 0x88, 1, 0 }, { 0xdd, 1, 0 }, { 0xdd, 1, 0 }, { 0x97, 1, 0 },
{ 0x0c, 1, 0 },
{ 0x36, 0, 0 },
{ 0x28, 1, 0 },
{ 0x2c, 0, 0 },
{ 0x00, 1, 0 },
};
static struct brain_drm_private *pipe_to_private(struct drm_simple_display_pipe *pipe)
{
return container_of(pipe, struct brain_drm_private, pipe);
}
static struct brain_drm_private *connector_to_private(struct drm_connector *connector)
{
return container_of(connector, struct brain_drm_private, connector);
}
static int brain_clear_poll(const void __iomem *addr, u32 mask, u32 timeout)
{
u32 reg;
return readl_poll_timeout(addr, reg, !(reg & mask), 0, timeout);
}
static int brain_write_clear_poll(void __iomem *addr, u32 mask, u32 timeout)
{
u32 reg;
writel(mask, addr + REG_CLR);
return readl_poll_timeout(addr, reg, !(reg & mask), 0, timeout);
}
static int brain_write_byte(struct brain_drm_private *priv, u8 payload, const u8 is_data)
{
if (brain_clear_poll(priv->base + LCDC_CTRL, CTRL_RUN, TX_TIMEOUT)) {
return -ETIMEDOUT;
}
writel(TRANSFER_COUNT_SET_VCOUNT(1) | TRANSFER_COUNT_SET_HCOUNT(1),
priv->base + LCDC_V4_TRANSFER_COUNT);
writel(CTRL_DATA_SELECT | CTRL_RUN, priv->base + LCDC_CTRL + REG_CLR);
if (is_data) {
writel(CTRL_DATA_SELECT, priv->base + LCDC_CTRL + REG_SET);
}
writel(CTRL_RUN, priv->base + LCDC_CTRL + REG_SET);
if (brain_clear_poll(priv->base + LCDC_STAT, STAT_LFIFO_FULL, TX_TIMEOUT)) {
return -ETIMEDOUT;
}
writel(payload, priv->base + LCDC_DATA);
if (brain_clear_poll(priv->base + LCDC_CTRL, CTRL_RUN, TX_TIMEOUT)) {
return -ETIMEDOUT;
}
return 0;
}
static int brain_reset_block(void __iomem *reset_addr)
{
int ret;
ret = brain_write_clear_poll(reset_addr, CTRL_SFTRST, RESET_TIMEOUT);
if (ret)
return ret;
writel(CTRL_CLKGATE, reset_addr + REG_CLR);
ret = brain_write_clear_poll(reset_addr, CTRL_SFTRST, RESET_TIMEOUT);
if (ret)
return ret;
return brain_write_clear_poll(reset_addr, CTRL_CLKGATE, RESET_TIMEOUT);
}
static int brain_set_pixel_fmt(struct brain_drm_private *ili, struct drm_plane_state *plane_state)
{
struct drm_crtc *crtc = &ili->pipe.crtc;
struct drm_device *drm = crtc->dev;
const u32 format = plane_state->fb->format->format;
u32 ctrl, ctrl1;
ctrl = CTRL_BYPASS_COUNT | CTRL_MASTER;
/* CTRL1 contains IRQ config and status bits, preserve those. */
ctrl1 = readl(ili->base + LCDC_CTRL1);
ctrl1 &= CTRL1_CUR_FRAME_DONE_IRQ_EN | CTRL1_CUR_FRAME_DONE_IRQ;
switch (format) {
case DRM_FORMAT_RGB565:
dev_dbg(drm->dev, "Setting up RGB565 mode\n");
ctrl |= CTRL_SET_WORD_LENGTH(0);
ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0xf);
break;
case DRM_FORMAT_XRGB8888:
dev_dbg(drm->dev, "Setting up XRGB8888 mode\n");
ctrl |= CTRL_SET_WORD_LENGTH(3);
/* Do not use packed pixels = one pixel per word instead. */
ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0x7);
break;
default:
dev_err(drm->dev, "Unhandled pixel format %08x\n", format);
return -EINVAL;
}
writel(ctrl1, ili->base + LCDC_CTRL1);
writel(ctrl, ili->base + LCDC_CTRL);
return 0;
}
static void brain_set_bus_fmt(struct brain_drm_private *ili)
{
struct drm_crtc *crtc = &ili->pipe.crtc;
struct drm_device *drm = crtc->dev;
u32 bus_format = MEDIA_BUS_FMT_RGB565_1X16;
u32 reg;
reg = readl(ili->base + LCDC_CTRL);
if (ili->connector.display_info.num_bus_formats)
bus_format = ili->connector.display_info.bus_formats[0];
reg &= ~CTRL_BUS_WIDTH_MASK;
switch (bus_format) {
case MEDIA_BUS_FMT_RGB565_1X16:
reg |= CTRL_SET_BUS_WIDTH(STMLCDIF_16BIT);
break;
case MEDIA_BUS_FMT_RGB666_1X18:
reg |= CTRL_SET_BUS_WIDTH(STMLCDIF_18BIT);
break;
case MEDIA_BUS_FMT_RGB888_1X24:
reg |= CTRL_SET_BUS_WIDTH(STMLCDIF_24BIT);
break;
default:
dev_err(drm->dev, "Unknown media bus format %d\n", bus_format);
break;
}
writel(reg, ili->base + LCDC_CTRL);
}
static void brain_enable(struct drm_simple_display_pipe *pipe,
struct drm_crtc_state *crtc_state,
struct drm_plane_state *plane_state)
{
struct brain_drm_private *ili;
struct drm_display_mode *m = &crtc_state->adjusted_mode;
int i, ret, idx;
u32 valid;
#if 0
u8 mac = 0;
#endif
ili = pipe_to_private(pipe);
m = &ili->pipe.crtc.state->adjusted_mode;
if (!drm_dev_enter(pipe->crtc.dev, &idx))
return;
clk_prepare_enable(ili->clk_lcdif);
ret = brain_reset_block(ili->base);
if (ret)
return;
writel(CTRL1_FIFO_CLEAR, ili->base + LCDC_CTRL1 + REG_SET);
ret = brain_set_pixel_fmt(ili, plane_state);
if (ret)
return;
/* Unset DOTCLK */
writel(CTRL_MASTER | CTRL_DOTCLK_MODE | CTRL_BYPASS_COUNT, ili->base + LCDC_CTRL + REG_CLR);
brain_set_bus_fmt(ili);
/* Timing */
writel(TIMING_CMD_HOLD(1) | TIMING_CMD_SETUP(1) | TIMING_DATA_HOLD(1) | TIMING_DATA_SETUP(1),
ili->base + LCDC_TIMING);
/* Initialize LCD */
/* Decrease the bus width to 8-bit temporarily */
valid = CTRL1_GET_BYTE_PACKAGING(readl(ili->base + LCDC_CTRL1));
writel(CTRL1_SET_BYTE_PACKAGING(0xf), ili->base + LCDC_CTRL1 + REG_CLR);
writel(CTRL1_SET_BYTE_PACKAGING(0x3), ili->base + LCDC_CTRL1 + REG_SET);
/* Reset LCD Controller */
writel(CTRL1_RESET, ili->base + LCDC_CTRL1 + REG_SET);
mdelay(30);
writel(CTRL1_RESET, ili->base + LCDC_CTRL1 + REG_CLR);
mdelay(30);
writel(CTRL1_RESET, ili->base + LCDC_CTRL1 + REG_SET);
mdelay(30);
for (i = 0; i < ARRAY_SIZE(lcd_regs); i++) {
brain_write_byte(ili, lcd_regs[i].payload, lcd_regs[i].data);
mdelay(lcd_regs[i].delay);
}
#if 0
if (of_property_read_bool(ili->pdev->dev.of_node, "sharp,mac-flip-x")) {
mac |= ILI9805_MADCTL_MX;
}
if (of_property_read_bool(ili->pdev->dev.of_node, "sharp,mac-flip-y")) {
mac |= ILI9805_MADCTL_MY;
}
if (of_property_read_bool(ili->pdev->dev.of_node, "sharp,mac-flip-y-gs")) {
mac |= ILI9805_MADCTL_GS;
}
if (of_property_read_bool(ili->pdev->dev.of_node, "sharp,mac-transpose")) {
mac |= ILI9805_MADCTL_MV;
}
if (of_property_read_bool(ili->pdev->dev.of_node, "sharp,mac-bgr")) {
mac |= ILI9805_MADCTL_BGR;
}
brain_write_byte(ili, 0x36, 0); /* Memory Access Control */
brain_write_byte(ili, mac, 1);
if (of_property_read_bool(ili->pdev->dev.of_node, "sharp,mac-inversion")) {
brain_write_byte(ili, 0x21, 0); /* Display Inversion On */
}
#endif
brain_write_byte(ili, 0x11, 0); /* Sleep Out */
mdelay(120);
brain_write_byte(ili, 0x34, 0);
brain_write_byte(ili, 0x29, 0); /* Display On */
mdelay(120);
brain_write_byte(ili, 0x2a, 0); /* Column Address Set */
brain_write_byte(ili, 0x00, 1); /* Start Column in 2 Bytes */
brain_write_byte(ili, 0x00, 1);
#if 0
brain_write_byte(ili, (m->hdisplay & 0xff00) >> 8, 1); /* End Column in 2 Bytes */
brain_write_byte(ili, (m->hdisplay & 0x00ff) >> 0, 1);
#else
brain_write_byte(ili, (m->vdisplay & 0xff00) >> 8, 1);
brain_write_byte(ili, (m->vdisplay & 0x00ff) >> 0, 1);
#endif
brain_write_byte(ili, 0x2b, 0); /* Page Address Set */
brain_write_byte(ili, 0x00, 1); /* Start Page in 2 Bytes */
brain_write_byte(ili, 0x00, 1);
#if 0
brain_write_byte(ili, (m->vdisplay & 0xff00) >> 8, 1); /* End Page in 2 Bytes */
brain_write_byte(ili, (m->vdisplay & 0x00ff) >> 0, 1);
#else
brain_write_byte(ili, (m->hdisplay & 0xff00) >> 8, 1);
brain_write_byte(ili, (m->hdisplay & 0x00ff) >> 0, 1);
#endif
brain_write_byte(ili, 0x2c, 0); /* Memory Write */
writel(CTRL1_SET_BYTE_PACKAGING(0xf), ili->base + LCDC_CTRL1 + REG_CLR);
writel(CTRL1_SET_BYTE_PACKAGING(valid), ili->base + LCDC_CTRL1 + REG_SET);
/* Ready to transmit */
writel(CTRL_MASTER, ili->base + LCDC_CTRL + REG_SET);
ili->enabled = true;
}
static void brain_disable(struct drm_simple_display_pipe *pipe)
{
struct brain_drm_private *ili = pipe_to_private(pipe);
clk_disable_unprepare(ili->clk_lcdif);
return;
}
static void brain_fb_dirty_full(struct brain_drm_private *ili, struct drm_framebuffer *fb, struct drm_rect *rect) {
struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
int idx;
u16 width = fb->width;
u16 height = fb->height;
//
u32 valid;
//
if (!ili->enabled)
return;
if (!drm_dev_enter(fb->dev, &idx))
return;
if (brain_clear_poll(ili->base + LCDC_CTRL, CTRL_RUN, TX_TIMEOUT)) {
dev_warn(&ili->pdev->dev, "Exceeded timeout\n");
goto timeout_exit;
}
#if 1
writel(CTRL_MASTER | CTRL_DATA_SELECT, ili->base + LCDC_CTRL + REG_CLR);
valid = CTRL1_GET_BYTE_PACKAGING(readl(ili->base + LCDC_CTRL1));
writel(CTRL1_SET_BYTE_PACKAGING(0xf), ili->base + LCDC_CTRL1 + REG_CLR);
writel(CTRL1_SET_BYTE_PACKAGING(0x3), ili->base + LCDC_CTRL1 + REG_SET);
brain_write_byte(ili, 0x2a, 0); /* Column Address Set */
brain_write_byte(ili, 0x00, 1); /* Start Column in 2 Bytes */
brain_write_byte(ili, 0x00, 1);
brain_write_byte(ili, (height & 0xff00) >> 8, 1);
brain_write_byte(ili, (height & 0x00ff) >> 0, 1);
brain_write_byte(ili, 0x2b, 0); /* Page Address Set */
brain_write_byte(ili, 0x00, 1); /* Start Page in 2 Bytes */
brain_write_byte(ili, 0x00, 1);
brain_write_byte(ili, (width & 0xff00) >> 8, 1);
brain_write_byte(ili, (width & 0x00ff) >> 0, 1);
brain_write_byte(ili, 0x2c, 0); /* Memory Write */
writel(CTRL1_SET_BYTE_PACKAGING(0xf), ili->base + LCDC_CTRL1 + REG_CLR);
writel(CTRL1_SET_BYTE_PACKAGING(valid), ili->base + LCDC_CTRL1 + REG_SET);
writel(CTRL_MASTER, ili->base + LCDC_CTRL + REG_SET);
#endif
writel(cma_obj->paddr, ili->base + LCDC_V4_CUR_BUF);
writel(cma_obj->paddr, ili->base + LCDC_V4_NEXT_BUF);
writel(TRANSFER_COUNT_SET_VCOUNT(height) | TRANSFER_COUNT_SET_HCOUNT(width),
ili->base + LCDC_V4_TRANSFER_COUNT);
writel(CTRL_DATA_SELECT | CTRL_RUN, ili->base + LCDC_CTRL + REG_SET);
timeout_exit:
drm_dev_exit(idx);
}
static void brain_update(struct drm_simple_display_pipe *pipe,
struct drm_plane_state *old_state)
{
struct brain_drm_private *ili = pipe_to_private(pipe);
struct drm_plane_state *state = pipe->plane.state;
struct drm_crtc *crtc = &pipe->crtc;
struct drm_rect rect;
if (drm_atomic_helper_damage_merged(old_state, state, &rect))
brain_fb_dirty_full(ili, state->fb, &rect);
if (crtc->state->event) {
spin_lock_irq(&crtc->dev->event_lock);
drm_crtc_send_vblank_event(crtc, crtc->state->event);
spin_unlock_irq(&crtc->dev->event_lock);
crtc->state->event = NULL;
}
}
static const struct drm_simple_display_pipe_funcs brain_pipe_funcs = {
.enable = brain_enable,
.disable = brain_disable,
.update = brain_update,
.prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb,
};
DEFINE_DRM_GEM_CMA_FOPS(brain_fops);
static int brain_connector_get_modes(struct drm_connector *connector)
{
struct brain_drm_private *ili;
static struct drm_display_mode modei;
struct drm_display_mode* mode;
u32 hd, vd, hd_mm, vd_mm;
ili = connector_to_private(connector);
of_property_read_u32(ili->pdev->dev.of_node, "sharp,lcd-width", &hd);
of_property_read_u32(ili->pdev->dev.of_node, "sharp,lcd-height", &vd);
of_property_read_u32(ili->pdev->dev.of_node, "sharp,lcd-width-mm", &hd_mm);
of_property_read_u32(ili->pdev->dev.of_node, "sharp,lcd-height-mm", &vd_mm);
// Equivalent to DRM_SIMPLE_MODE macro
modei.type = DRM_MODE_TYPE_DRIVER;
modei.clock = 1;
modei.hdisplay = hd;
modei.hsync_start = hd;
modei.hsync_end = hd;
modei.htotal = hd;
modei.vdisplay = vd;
modei.vsync_start = vd;
modei.vsync_end = vd;
modei.vtotal = vd;
modei.width_mm = hd_mm;
modei.height_mm = vd_mm;
mode = drm_mode_duplicate(connector->dev, &modei);
if (!mode) {
DRM_ERROR("Failed to duplicate mode\n");
return 0;
}
if (mode->name[0] == '\0')
drm_mode_set_name(mode);
mode->type |= DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
if (mode->width_mm) {
connector->display_info.width_mm = mode->width_mm;
connector->display_info.height_mm = mode->height_mm;
}
return 1;
}
static const struct drm_connector_helper_funcs brain_connector_helper_funcs = {
.get_modes = brain_connector_get_modes,
};
static const struct drm_connector_funcs brain_connector_funcs = {
.reset = drm_atomic_helper_connector_reset,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static const struct drm_mode_config_funcs brain_mode_config_funcs = {
.fb_create = drm_gem_fb_create_with_dirty,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
static struct drm_driver brain_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
.fops = &brain_fops,
DRM_GEM_CMA_VMAP_DRIVER_OPS,
.name = "brain",
.desc = "Sharp LQ050J1UG01",
.date = "20210417",
.major = 1,
.minor = 0,
};
static const struct of_device_id brain_of_match[] = {
{ .compatible = "sharp,brainlcd" },
{},
};
MODULE_DEVICE_TABLE(of, brain_of_match);
static int brain_probe(struct platform_device *pdev)
{
struct drm_device *drm;
struct brain_drm_private *ili;
struct resource *res;
struct gpio_desc *en;
u32 width, height;
int i, ret;
const struct of_device_id *of_id =
of_match_device(brain_of_match, &pdev->dev);
static const uint32_t formats[] = {
DRM_FORMAT_RGB565,
};
if (!pdev->dev.of_node)
return -ENODEV;
if (of_id)
pdev->id_entry = of_id->data;
drm = drm_dev_alloc(&brain_driver, &pdev->dev);
if (IS_ERR(drm))
return PTR_ERR(drm);
ili = devm_kzalloc(&pdev->dev, sizeof(*ili), GFP_KERNEL);
if (!ili) {
drm_dev_put(drm);
return -ENOMEM;
}
ili->pdev = pdev;
drm->dev_private = ili;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ili->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(ili->base)) {
drm_dev_put(drm);
return PTR_ERR(ili->base);
}
ili->enabled = false;
ili->clk_lcdif = devm_clk_get(drm->dev, NULL);
if (IS_ERR(ili->clk_lcdif)) {
drm_dev_put(drm);
return PTR_ERR(ili->clk_lcdif);
}
drm_mode_config_init(drm);
drm->mode_config.funcs = &brain_mode_config_funcs;
for (i = 0; i < gpiod_count(&pdev->dev, "sharp,en"); i++) {
en = devm_gpiod_get_index(&pdev->dev, "sharp,en", i, GPIOD_OUT_HIGH);
if (IS_ERR(en))
dev_err(&pdev->dev, "failed to get gpio %d\n", i);
continue;
ret = gpiod_direction_output(en, 1);
if (IS_ERR(en))
dev_err(&pdev->dev, "failed to output gpio %d\n", i);
}
of_property_read_u32(ili->pdev->dev.of_node, "sharp,lcd-width", &width);
of_property_read_u32(ili->pdev->dev.of_node, "sharp,lcd-height", &height);
drm->mode_config.min_width = width;
drm->mode_config.max_width = width;
drm->mode_config.min_height = height;
drm->mode_config.max_height = height;
drm_connector_helper_add(&ili->connector, &brain_connector_helper_funcs);
ret = drm_connector_init(drm, &ili->connector, &brain_connector_funcs, DRM_MODE_CONNECTOR_DPI);
if (ret) {
drm_dev_put(drm);
return ret;
}
ret = drm_simple_display_pipe_init(drm, &ili->pipe, &brain_pipe_funcs,
formats, ARRAY_SIZE(formats),
NULL, &ili->connector);
if (ret) {
drm_dev_put(drm);
return ret;
}
drm_mode_config_reset(drm);
ret = drm_dev_register(drm, 0);
if (ret) {
drm_dev_put(drm);
return ret;
}
platform_set_drvdata(pdev, drm);
drm_fbdev_generic_setup(drm, 16);
return 0;
}
static int brain_remove(struct platform_device *pdev)
{
struct drm_device *drm = platform_get_drvdata(pdev);
drm_dev_unplug(drm);
drm_atomic_helper_shutdown(drm);
return 0;
}
static void brain_shutdown(struct platform_device *pdev)
{
drm_atomic_helper_shutdown(platform_get_drvdata(pdev));
}
static struct platform_driver brain_platform_driver = {
.probe = brain_probe,
.remove = brain_remove,
.shutdown = brain_shutdown,
.driver = {
.name = "brain",
.of_match_table = brain_of_match,
},
};
module_platform_driver(brain_platform_driver);
MODULE_DESCRIPTION("DRM driver for Sharp LQ050J1UG01 panels on Sharp Brain");
MODULE_AUTHOR("Suguru Saito <sg.sgch07@gmail.com>");
MODULE_LICENSE("GPL");