162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Ilitek ILI9322 TFT LCD drm_panel driver. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * This panel can be configured to support: 662306a36Sopenharmony_ci * - 8-bit serial RGB interface 762306a36Sopenharmony_ci * - 24-bit parallel RGB interface 862306a36Sopenharmony_ci * - 8-bit ITU-R BT.601 interface 962306a36Sopenharmony_ci * - 8-bit ITU-R BT.656 interface 1062306a36Sopenharmony_ci * - Up to 320RGBx240 dots resolution TFT LCD displays 1162306a36Sopenharmony_ci * - Scaling, brightness and contrast 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * The scaling means that the display accepts a 640x480 or 720x480 1462306a36Sopenharmony_ci * input and rescales it to fit to the 320x240 display. So what we 1562306a36Sopenharmony_ci * present to the system is something else than what comes out on the 1662306a36Sopenharmony_ci * actual display. 1762306a36Sopenharmony_ci * 1862306a36Sopenharmony_ci * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> 1962306a36Sopenharmony_ci * Derived from drivers/drm/gpu/panel/panel-samsung-ld9040.c 2062306a36Sopenharmony_ci */ 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#include <linux/bitops.h> 2362306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 2462306a36Sopenharmony_ci#include <linux/module.h> 2562306a36Sopenharmony_ci#include <linux/of.h> 2662306a36Sopenharmony_ci#include <linux/platform_device.h> 2762306a36Sopenharmony_ci#include <linux/regmap.h> 2862306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 2962306a36Sopenharmony_ci#include <linux/spi/spi.h> 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#include <video/mipi_display.h> 3262306a36Sopenharmony_ci#include <video/of_videomode.h> 3362306a36Sopenharmony_ci#include <video/videomode.h> 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#include <drm/drm_modes.h> 3662306a36Sopenharmony_ci#include <drm/drm_panel.h> 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define ILI9322_CHIP_ID 0x00 3962306a36Sopenharmony_ci#define ILI9322_CHIP_ID_MAGIC 0x96 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci/* 4262306a36Sopenharmony_ci * Voltage on the communication interface, from 0.7 (0x00) 4362306a36Sopenharmony_ci * to 1.32 (0x1f) times the VREG1OUT voltage in 2% increments. 4462306a36Sopenharmony_ci * 1.00 (0x0f) is the default. 4562306a36Sopenharmony_ci */ 4662306a36Sopenharmony_ci#define ILI9322_VCOM_AMP 0x01 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci/* 4962306a36Sopenharmony_ci * High voltage on the communication signals, from 0.37 (0x00) to 5062306a36Sopenharmony_ci * 1.0 (0x3f) times the VREGOUT1 voltage in 1% increments. 5162306a36Sopenharmony_ci * 0.83 (0x2e) is the default. 5262306a36Sopenharmony_ci */ 5362306a36Sopenharmony_ci#define ILI9322_VCOM_HIGH 0x02 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci/* 5662306a36Sopenharmony_ci * VREG1 voltage regulator from 3.6V (0x00) to 6.0V (0x18) in 0.1V 5762306a36Sopenharmony_ci * increments. 5.4V (0x12) is the default. This is the reference 5862306a36Sopenharmony_ci * voltage for the VCOM levels and the greyscale level. 5962306a36Sopenharmony_ci */ 6062306a36Sopenharmony_ci#define ILI9322_VREG1_VOLTAGE 0x03 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci/* Describes the incoming signal */ 6362306a36Sopenharmony_ci#define ILI9322_ENTRY 0x06 6462306a36Sopenharmony_ci/* 0 = right-to-left, 1 = left-to-right (default), horizontal flip */ 6562306a36Sopenharmony_ci#define ILI9322_ENTRY_HDIR BIT(0) 6662306a36Sopenharmony_ci/* 0 = down-to-up, 1 = up-to-down (default), vertical flip */ 6762306a36Sopenharmony_ci#define ILI9322_ENTRY_VDIR BIT(1) 6862306a36Sopenharmony_ci/* NTSC, PAL or autodetect */ 6962306a36Sopenharmony_ci#define ILI9322_ENTRY_NTSC (0 << 2) 7062306a36Sopenharmony_ci#define ILI9322_ENTRY_PAL (1 << 2) 7162306a36Sopenharmony_ci#define ILI9322_ENTRY_AUTODETECT (3 << 2) 7262306a36Sopenharmony_ci/* Input format */ 7362306a36Sopenharmony_ci#define ILI9322_ENTRY_SERIAL_RGB_THROUGH (0 << 4) 7462306a36Sopenharmony_ci#define ILI9322_ENTRY_SERIAL_RGB_ALIGNED (1 << 4) 7562306a36Sopenharmony_ci#define ILI9322_ENTRY_SERIAL_RGB_DUMMY_320X240 (2 << 4) 7662306a36Sopenharmony_ci#define ILI9322_ENTRY_SERIAL_RGB_DUMMY_360X240 (3 << 4) 7762306a36Sopenharmony_ci#define ILI9322_ENTRY_DISABLE_1 (4 << 4) 7862306a36Sopenharmony_ci#define ILI9322_ENTRY_PARALLEL_RGB_THROUGH (5 << 4) 7962306a36Sopenharmony_ci#define ILI9322_ENTRY_PARALLEL_RGB_ALIGNED (6 << 4) 8062306a36Sopenharmony_ci#define ILI9322_ENTRY_YUV_640Y_320CBCR_25_54_MHZ (7 << 4) 8162306a36Sopenharmony_ci#define ILI9322_ENTRY_YUV_720Y_360CBCR_27_MHZ (8 << 4) 8262306a36Sopenharmony_ci#define ILI9322_ENTRY_DISABLE_2 (9 << 4) 8362306a36Sopenharmony_ci#define ILI9322_ENTRY_ITU_R_BT_656_720X360 (10 << 4) 8462306a36Sopenharmony_ci#define ILI9322_ENTRY_ITU_R_BT_656_640X320 (11 << 4) 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci/* Power control */ 8762306a36Sopenharmony_ci#define ILI9322_POW_CTRL 0x07 8862306a36Sopenharmony_ci#define ILI9322_POW_CTRL_STB BIT(0) /* 0 = standby, 1 = normal */ 8962306a36Sopenharmony_ci#define ILI9322_POW_CTRL_VGL BIT(1) /* 0 = off, 1 = on */ 9062306a36Sopenharmony_ci#define ILI9322_POW_CTRL_VGH BIT(2) /* 0 = off, 1 = on */ 9162306a36Sopenharmony_ci#define ILI9322_POW_CTRL_DDVDH BIT(3) /* 0 = off, 1 = on */ 9262306a36Sopenharmony_ci#define ILI9322_POW_CTRL_VCOM BIT(4) /* 0 = off, 1 = on */ 9362306a36Sopenharmony_ci#define ILI9322_POW_CTRL_VCL BIT(5) /* 0 = off, 1 = on */ 9462306a36Sopenharmony_ci#define ILI9322_POW_CTRL_AUTO BIT(6) /* 0 = interactive, 1 = auto */ 9562306a36Sopenharmony_ci#define ILI9322_POW_CTRL_STANDBY (ILI9322_POW_CTRL_VGL | \ 9662306a36Sopenharmony_ci ILI9322_POW_CTRL_VGH | \ 9762306a36Sopenharmony_ci ILI9322_POW_CTRL_DDVDH | \ 9862306a36Sopenharmony_ci ILI9322_POW_CTRL_VCL | \ 9962306a36Sopenharmony_ci ILI9322_POW_CTRL_AUTO | \ 10062306a36Sopenharmony_ci BIT(7)) 10162306a36Sopenharmony_ci#define ILI9322_POW_CTRL_DEFAULT (ILI9322_POW_CTRL_STANDBY | \ 10262306a36Sopenharmony_ci ILI9322_POW_CTRL_STB) 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci/* Vertical back porch bits 0..5 */ 10562306a36Sopenharmony_ci#define ILI9322_VBP 0x08 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci/* Horizontal back porch, 8 bits */ 10862306a36Sopenharmony_ci#define ILI9322_HBP 0x09 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci/* 11162306a36Sopenharmony_ci * Polarity settings: 11262306a36Sopenharmony_ci * 1 = positive polarity 11362306a36Sopenharmony_ci * 0 = negative polarity 11462306a36Sopenharmony_ci */ 11562306a36Sopenharmony_ci#define ILI9322_POL 0x0a 11662306a36Sopenharmony_ci#define ILI9322_POL_DCLK BIT(0) /* 1 default */ 11762306a36Sopenharmony_ci#define ILI9322_POL_HSYNC BIT(1) /* 0 default */ 11862306a36Sopenharmony_ci#define ILI9322_POL_VSYNC BIT(2) /* 0 default */ 11962306a36Sopenharmony_ci#define ILI9322_POL_DE BIT(3) /* 1 default */ 12062306a36Sopenharmony_ci/* 12162306a36Sopenharmony_ci * 0 means YCBCR are ordered Cb0,Y0,Cr0,Y1,Cb2,Y2,Cr2,Y3 (default) 12262306a36Sopenharmony_ci * in RGB mode this means RGB comes in RGBRGB 12362306a36Sopenharmony_ci * 1 means YCBCR are ordered Cr0,Y0,Cb0,Y1,Cr2,Y2,Cb2,Y3 12462306a36Sopenharmony_ci * in RGB mode this means RGB comes in BGRBGR 12562306a36Sopenharmony_ci */ 12662306a36Sopenharmony_ci#define ILI9322_POL_YCBCR_MODE BIT(4) 12762306a36Sopenharmony_ci/* Formula A for YCbCR->RGB = 0, Formula B = 1 */ 12862306a36Sopenharmony_ci#define ILI9322_POL_FORMULA BIT(5) 12962306a36Sopenharmony_ci/* Reverse polarity: 0 = 0..255, 1 = 255..0 */ 13062306a36Sopenharmony_ci#define ILI9322_POL_REV BIT(6) 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci#define ILI9322_IF_CTRL 0x0b 13362306a36Sopenharmony_ci#define ILI9322_IF_CTRL_HSYNC_VSYNC 0x00 13462306a36Sopenharmony_ci#define ILI9322_IF_CTRL_HSYNC_VSYNC_DE BIT(2) 13562306a36Sopenharmony_ci#define ILI9322_IF_CTRL_DE_ONLY BIT(3) 13662306a36Sopenharmony_ci#define ILI9322_IF_CTRL_SYNC_DISABLED (BIT(2) | BIT(3)) 13762306a36Sopenharmony_ci#define ILI9322_IF_CTRL_LINE_INVERSION BIT(0) /* Not set means frame inv */ 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci#define ILI9322_GLOBAL_RESET 0x04 14062306a36Sopenharmony_ci#define ILI9322_GLOBAL_RESET_ASSERT 0x00 /* bit 0 = 0 -> reset */ 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci/* 14362306a36Sopenharmony_ci * 4+4 bits of negative and positive gamma correction 14462306a36Sopenharmony_ci * Upper nybble, bits 4-7 are negative gamma 14562306a36Sopenharmony_ci * Lower nybble, bits 0-3 are positive gamma 14662306a36Sopenharmony_ci */ 14762306a36Sopenharmony_ci#define ILI9322_GAMMA_1 0x10 14862306a36Sopenharmony_ci#define ILI9322_GAMMA_2 0x11 14962306a36Sopenharmony_ci#define ILI9322_GAMMA_3 0x12 15062306a36Sopenharmony_ci#define ILI9322_GAMMA_4 0x13 15162306a36Sopenharmony_ci#define ILI9322_GAMMA_5 0x14 15262306a36Sopenharmony_ci#define ILI9322_GAMMA_6 0x15 15362306a36Sopenharmony_ci#define ILI9322_GAMMA_7 0x16 15462306a36Sopenharmony_ci#define ILI9322_GAMMA_8 0x17 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci/* 15762306a36Sopenharmony_ci * enum ili9322_input - the format of the incoming signal to the panel 15862306a36Sopenharmony_ci * 15962306a36Sopenharmony_ci * The panel can be connected to various input streams and four of them can 16062306a36Sopenharmony_ci * be selected by electronic straps on the display. However it is possible 16162306a36Sopenharmony_ci * to select another mode or override the electronic default with this 16262306a36Sopenharmony_ci * setting. 16362306a36Sopenharmony_ci */ 16462306a36Sopenharmony_cienum ili9322_input { 16562306a36Sopenharmony_ci ILI9322_INPUT_SRGB_THROUGH = 0x0, 16662306a36Sopenharmony_ci ILI9322_INPUT_SRGB_ALIGNED = 0x1, 16762306a36Sopenharmony_ci ILI9322_INPUT_SRGB_DUMMY_320X240 = 0x2, 16862306a36Sopenharmony_ci ILI9322_INPUT_SRGB_DUMMY_360X240 = 0x3, 16962306a36Sopenharmony_ci ILI9322_INPUT_DISABLED_1 = 0x4, 17062306a36Sopenharmony_ci ILI9322_INPUT_PRGB_THROUGH = 0x5, 17162306a36Sopenharmony_ci ILI9322_INPUT_PRGB_ALIGNED = 0x6, 17262306a36Sopenharmony_ci ILI9322_INPUT_YUV_640X320_YCBCR = 0x7, 17362306a36Sopenharmony_ci ILI9322_INPUT_YUV_720X360_YCBCR = 0x8, 17462306a36Sopenharmony_ci ILI9322_INPUT_DISABLED_2 = 0x9, 17562306a36Sopenharmony_ci ILI9322_INPUT_ITU_R_BT656_720X360_YCBCR = 0xa, 17662306a36Sopenharmony_ci ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR = 0xb, 17762306a36Sopenharmony_ci ILI9322_INPUT_UNKNOWN = 0xc, 17862306a36Sopenharmony_ci}; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_cistatic const char * const ili9322_inputs[] = { 18162306a36Sopenharmony_ci "8 bit serial RGB through", 18262306a36Sopenharmony_ci "8 bit serial RGB aligned", 18362306a36Sopenharmony_ci "8 bit serial RGB dummy 320x240", 18462306a36Sopenharmony_ci "8 bit serial RGB dummy 360x240", 18562306a36Sopenharmony_ci "disabled 1", 18662306a36Sopenharmony_ci "24 bit parallel RGB through", 18762306a36Sopenharmony_ci "24 bit parallel RGB aligned", 18862306a36Sopenharmony_ci "24 bit YUV 640Y 320CbCr", 18962306a36Sopenharmony_ci "24 bit YUV 720Y 360CbCr", 19062306a36Sopenharmony_ci "disabled 2", 19162306a36Sopenharmony_ci "8 bit ITU-R BT.656 720Y 360CbCr", 19262306a36Sopenharmony_ci "8 bit ITU-R BT.656 640Y 320CbCr", 19362306a36Sopenharmony_ci}; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci/** 19662306a36Sopenharmony_ci * struct ili9322_config - the system specific ILI9322 configuration 19762306a36Sopenharmony_ci * @width_mm: physical panel width [mm] 19862306a36Sopenharmony_ci * @height_mm: physical panel height [mm] 19962306a36Sopenharmony_ci * @flip_horizontal: flip the image horizontally (right-to-left scan) 20062306a36Sopenharmony_ci * (only in RGB and YUV modes) 20162306a36Sopenharmony_ci * @flip_vertical: flip the image vertically (down-to-up scan) 20262306a36Sopenharmony_ci * (only in RGB and YUV modes) 20362306a36Sopenharmony_ci * @input: the input/entry type used in this system, if this is set to 20462306a36Sopenharmony_ci * ILI9322_INPUT_UNKNOWN the driver will try to figure it out by probing 20562306a36Sopenharmony_ci * the hardware 20662306a36Sopenharmony_ci * @vreg1out_mv: the output in microvolts for the VREGOUT1 regulator used 20762306a36Sopenharmony_ci * to drive the physical display. Valid ranges are 3600 thru 6000 in 100 20862306a36Sopenharmony_ci * microvolt increments. If not specified, hardware defaults will be 20962306a36Sopenharmony_ci * used (4.5V). 21062306a36Sopenharmony_ci * @vcom_high_percent: the percentage of VREGOUT1 used for the peak 21162306a36Sopenharmony_ci * voltage on the communications link. Valid ranges are 37 thru 100 21262306a36Sopenharmony_ci * percent. If not specified, hardware defaults will be used (91%). 21362306a36Sopenharmony_ci * @vcom_amplitude_percent: the percentage of VREGOUT1 used for the 21462306a36Sopenharmony_ci * peak-to-peak amplitude of the communcation signals to the physical 21562306a36Sopenharmony_ci * display. Valid ranges are 70 thru 132 percent in increments if two 21662306a36Sopenharmony_ci * percent. Odd percentages will be truncated. If not specified, hardware 21762306a36Sopenharmony_ci * defaults will be used (114%). 21862306a36Sopenharmony_ci * @dclk_active_high: data/pixel clock active high, data will be clocked 21962306a36Sopenharmony_ci * in on the rising edge of the DCLK (this is usually the case). 22062306a36Sopenharmony_ci * @syncmode: The synchronization mode, what sync signals are emitted. 22162306a36Sopenharmony_ci * See the enum for details. 22262306a36Sopenharmony_ci * @de_active_high: DE (data entry) is active high 22362306a36Sopenharmony_ci * @hsync_active_high: HSYNC is active high 22462306a36Sopenharmony_ci * @vsync_active_high: VSYNC is active high 22562306a36Sopenharmony_ci * @gamma_corr_pos: a set of 8 nybbles describing positive 22662306a36Sopenharmony_ci * gamma correction for voltages V1 thru V8. Valid range 0..15 22762306a36Sopenharmony_ci * @gamma_corr_neg: a set of 8 nybbles describing negative 22862306a36Sopenharmony_ci * gamma correction for voltages V1 thru V8. Valid range 0..15 22962306a36Sopenharmony_ci * 23062306a36Sopenharmony_ci * These adjust what grayscale voltage will be output for input data V1 = 0, 23162306a36Sopenharmony_ci * V2 = 16, V3 = 48, V4 = 96, V5 = 160, V6 = 208, V7 = 240 and V8 = 255. 23262306a36Sopenharmony_ci * The curve is shaped like this: 23362306a36Sopenharmony_ci * 23462306a36Sopenharmony_ci * ^ 23562306a36Sopenharmony_ci * | V8 23662306a36Sopenharmony_ci * | V7 23762306a36Sopenharmony_ci * | V6 23862306a36Sopenharmony_ci * | V5 23962306a36Sopenharmony_ci * | V4 24062306a36Sopenharmony_ci * | V3 24162306a36Sopenharmony_ci * | V2 24262306a36Sopenharmony_ci * | V1 24362306a36Sopenharmony_ci * +-----------------------------------------------------------> 24462306a36Sopenharmony_ci * 0 16 48 96 160 208 240 255 24562306a36Sopenharmony_ci * 24662306a36Sopenharmony_ci * The negative and postive gamma values adjust the V1 thru V8 up/down 24762306a36Sopenharmony_ci * according to the datasheet specifications. This is a property of the 24862306a36Sopenharmony_ci * physical display connected to the display controller and may vary. 24962306a36Sopenharmony_ci * If defined, both arrays must be supplied in full. If the properties 25062306a36Sopenharmony_ci * are not supplied, hardware defaults will be used. 25162306a36Sopenharmony_ci */ 25262306a36Sopenharmony_cistruct ili9322_config { 25362306a36Sopenharmony_ci u32 width_mm; 25462306a36Sopenharmony_ci u32 height_mm; 25562306a36Sopenharmony_ci bool flip_horizontal; 25662306a36Sopenharmony_ci bool flip_vertical; 25762306a36Sopenharmony_ci enum ili9322_input input; 25862306a36Sopenharmony_ci u32 vreg1out_mv; 25962306a36Sopenharmony_ci u32 vcom_high_percent; 26062306a36Sopenharmony_ci u32 vcom_amplitude_percent; 26162306a36Sopenharmony_ci bool dclk_active_high; 26262306a36Sopenharmony_ci bool de_active_high; 26362306a36Sopenharmony_ci bool hsync_active_high; 26462306a36Sopenharmony_ci bool vsync_active_high; 26562306a36Sopenharmony_ci u8 syncmode; 26662306a36Sopenharmony_ci u8 gamma_corr_pos[8]; 26762306a36Sopenharmony_ci u8 gamma_corr_neg[8]; 26862306a36Sopenharmony_ci}; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cistruct ili9322 { 27162306a36Sopenharmony_ci struct device *dev; 27262306a36Sopenharmony_ci const struct ili9322_config *conf; 27362306a36Sopenharmony_ci struct drm_panel panel; 27462306a36Sopenharmony_ci struct regmap *regmap; 27562306a36Sopenharmony_ci struct regulator_bulk_data supplies[3]; 27662306a36Sopenharmony_ci struct gpio_desc *reset_gpio; 27762306a36Sopenharmony_ci enum ili9322_input input; 27862306a36Sopenharmony_ci struct videomode vm; 27962306a36Sopenharmony_ci u8 gamma[8]; 28062306a36Sopenharmony_ci u8 vreg1out; 28162306a36Sopenharmony_ci u8 vcom_high; 28262306a36Sopenharmony_ci u8 vcom_amplitude; 28362306a36Sopenharmony_ci}; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cistatic inline struct ili9322 *panel_to_ili9322(struct drm_panel *panel) 28662306a36Sopenharmony_ci{ 28762306a36Sopenharmony_ci return container_of(panel, struct ili9322, panel); 28862306a36Sopenharmony_ci} 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_cistatic int ili9322_regmap_spi_write(void *context, const void *data, 29162306a36Sopenharmony_ci size_t count) 29262306a36Sopenharmony_ci{ 29362306a36Sopenharmony_ci struct device *dev = context; 29462306a36Sopenharmony_ci struct spi_device *spi = to_spi_device(dev); 29562306a36Sopenharmony_ci u8 buf[2]; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci /* Clear bit 7 to write */ 29862306a36Sopenharmony_ci memcpy(buf, data, 2); 29962306a36Sopenharmony_ci buf[0] &= ~0x80; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci dev_dbg(dev, "WRITE: %02x %02x\n", buf[0], buf[1]); 30262306a36Sopenharmony_ci return spi_write_then_read(spi, buf, 2, NULL, 0); 30362306a36Sopenharmony_ci} 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_cistatic int ili9322_regmap_spi_read(void *context, const void *reg, 30662306a36Sopenharmony_ci size_t reg_size, void *val, size_t val_size) 30762306a36Sopenharmony_ci{ 30862306a36Sopenharmony_ci struct device *dev = context; 30962306a36Sopenharmony_ci struct spi_device *spi = to_spi_device(dev); 31062306a36Sopenharmony_ci u8 buf[1]; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci /* Set bit 7 to 1 to read */ 31362306a36Sopenharmony_ci memcpy(buf, reg, 1); 31462306a36Sopenharmony_ci dev_dbg(dev, "READ: %02x reg size = %zu, val size = %zu\n", 31562306a36Sopenharmony_ci buf[0], reg_size, val_size); 31662306a36Sopenharmony_ci buf[0] |= 0x80; 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci return spi_write_then_read(spi, buf, 1, val, 1); 31962306a36Sopenharmony_ci} 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_cistatic struct regmap_bus ili9322_regmap_bus = { 32262306a36Sopenharmony_ci .write = ili9322_regmap_spi_write, 32362306a36Sopenharmony_ci .read = ili9322_regmap_spi_read, 32462306a36Sopenharmony_ci .reg_format_endian_default = REGMAP_ENDIAN_BIG, 32562306a36Sopenharmony_ci .val_format_endian_default = REGMAP_ENDIAN_BIG, 32662306a36Sopenharmony_ci}; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_cistatic bool ili9322_volatile_reg(struct device *dev, unsigned int reg) 32962306a36Sopenharmony_ci{ 33062306a36Sopenharmony_ci return false; 33162306a36Sopenharmony_ci} 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_cistatic bool ili9322_writeable_reg(struct device *dev, unsigned int reg) 33462306a36Sopenharmony_ci{ 33562306a36Sopenharmony_ci /* Just register 0 is read-only */ 33662306a36Sopenharmony_ci if (reg == 0x00) 33762306a36Sopenharmony_ci return false; 33862306a36Sopenharmony_ci return true; 33962306a36Sopenharmony_ci} 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_cistatic const struct regmap_config ili9322_regmap_config = { 34262306a36Sopenharmony_ci .reg_bits = 8, 34362306a36Sopenharmony_ci .val_bits = 8, 34462306a36Sopenharmony_ci .max_register = 0x44, 34562306a36Sopenharmony_ci .cache_type = REGCACHE_RBTREE, 34662306a36Sopenharmony_ci .volatile_reg = ili9322_volatile_reg, 34762306a36Sopenharmony_ci .writeable_reg = ili9322_writeable_reg, 34862306a36Sopenharmony_ci}; 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_cistatic int ili9322_init(struct drm_panel *panel, struct ili9322 *ili) 35162306a36Sopenharmony_ci{ 35262306a36Sopenharmony_ci u8 reg; 35362306a36Sopenharmony_ci int ret; 35462306a36Sopenharmony_ci int i; 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci /* Reset display */ 35762306a36Sopenharmony_ci ret = regmap_write(ili->regmap, ILI9322_GLOBAL_RESET, 35862306a36Sopenharmony_ci ILI9322_GLOBAL_RESET_ASSERT); 35962306a36Sopenharmony_ci if (ret) { 36062306a36Sopenharmony_ci dev_err(ili->dev, "can't issue GRESET (%d)\n", ret); 36162306a36Sopenharmony_ci return ret; 36262306a36Sopenharmony_ci } 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci /* Set up the main voltage regulator */ 36562306a36Sopenharmony_ci if (ili->vreg1out != U8_MAX) { 36662306a36Sopenharmony_ci ret = regmap_write(ili->regmap, ILI9322_VREG1_VOLTAGE, 36762306a36Sopenharmony_ci ili->vreg1out); 36862306a36Sopenharmony_ci if (ret) { 36962306a36Sopenharmony_ci dev_err(ili->dev, "can't set up VREG1OUT (%d)\n", ret); 37062306a36Sopenharmony_ci return ret; 37162306a36Sopenharmony_ci } 37262306a36Sopenharmony_ci } 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci if (ili->vcom_amplitude != U8_MAX) { 37562306a36Sopenharmony_ci ret = regmap_write(ili->regmap, ILI9322_VCOM_AMP, 37662306a36Sopenharmony_ci ili->vcom_amplitude); 37762306a36Sopenharmony_ci if (ret) { 37862306a36Sopenharmony_ci dev_err(ili->dev, 37962306a36Sopenharmony_ci "can't set up VCOM amplitude (%d)\n", ret); 38062306a36Sopenharmony_ci return ret; 38162306a36Sopenharmony_ci } 38262306a36Sopenharmony_ci } 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci if (ili->vcom_high != U8_MAX) { 38562306a36Sopenharmony_ci ret = regmap_write(ili->regmap, ILI9322_VCOM_HIGH, 38662306a36Sopenharmony_ci ili->vcom_high); 38762306a36Sopenharmony_ci if (ret) { 38862306a36Sopenharmony_ci dev_err(ili->dev, "can't set up VCOM high (%d)\n", ret); 38962306a36Sopenharmony_ci return ret; 39062306a36Sopenharmony_ci } 39162306a36Sopenharmony_ci } 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci /* Set up gamma correction */ 39462306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(ili->gamma); i++) { 39562306a36Sopenharmony_ci ret = regmap_write(ili->regmap, ILI9322_GAMMA_1 + i, 39662306a36Sopenharmony_ci ili->gamma[i]); 39762306a36Sopenharmony_ci if (ret) { 39862306a36Sopenharmony_ci dev_err(ili->dev, 39962306a36Sopenharmony_ci "can't write gamma V%d to 0x%02x (%d)\n", 40062306a36Sopenharmony_ci i + 1, ILI9322_GAMMA_1 + i, ret); 40162306a36Sopenharmony_ci return ret; 40262306a36Sopenharmony_ci } 40362306a36Sopenharmony_ci } 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci /* 40662306a36Sopenharmony_ci * Polarity and inverted color order for RGB input. 40762306a36Sopenharmony_ci * None of this applies in the BT.656 mode. 40862306a36Sopenharmony_ci */ 40962306a36Sopenharmony_ci reg = 0; 41062306a36Sopenharmony_ci if (ili->conf->dclk_active_high) 41162306a36Sopenharmony_ci reg = ILI9322_POL_DCLK; 41262306a36Sopenharmony_ci if (ili->conf->de_active_high) 41362306a36Sopenharmony_ci reg |= ILI9322_POL_DE; 41462306a36Sopenharmony_ci if (ili->conf->hsync_active_high) 41562306a36Sopenharmony_ci reg |= ILI9322_POL_HSYNC; 41662306a36Sopenharmony_ci if (ili->conf->vsync_active_high) 41762306a36Sopenharmony_ci reg |= ILI9322_POL_VSYNC; 41862306a36Sopenharmony_ci ret = regmap_write(ili->regmap, ILI9322_POL, reg); 41962306a36Sopenharmony_ci if (ret) { 42062306a36Sopenharmony_ci dev_err(ili->dev, "can't write POL register (%d)\n", ret); 42162306a36Sopenharmony_ci return ret; 42262306a36Sopenharmony_ci } 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci /* 42562306a36Sopenharmony_ci * Set up interface control. 42662306a36Sopenharmony_ci * This is not used in the BT.656 mode (no H/Vsync or DE signals). 42762306a36Sopenharmony_ci */ 42862306a36Sopenharmony_ci reg = ili->conf->syncmode; 42962306a36Sopenharmony_ci reg |= ILI9322_IF_CTRL_LINE_INVERSION; 43062306a36Sopenharmony_ci ret = regmap_write(ili->regmap, ILI9322_IF_CTRL, reg); 43162306a36Sopenharmony_ci if (ret) { 43262306a36Sopenharmony_ci dev_err(ili->dev, "can't write IF CTRL register (%d)\n", ret); 43362306a36Sopenharmony_ci return ret; 43462306a36Sopenharmony_ci } 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci /* Set up the input mode */ 43762306a36Sopenharmony_ci reg = (ili->input << 4); 43862306a36Sopenharmony_ci /* These are inverted, setting to 1 is the default, clearing flips */ 43962306a36Sopenharmony_ci if (!ili->conf->flip_horizontal) 44062306a36Sopenharmony_ci reg |= ILI9322_ENTRY_HDIR; 44162306a36Sopenharmony_ci if (!ili->conf->flip_vertical) 44262306a36Sopenharmony_ci reg |= ILI9322_ENTRY_VDIR; 44362306a36Sopenharmony_ci reg |= ILI9322_ENTRY_AUTODETECT; 44462306a36Sopenharmony_ci ret = regmap_write(ili->regmap, ILI9322_ENTRY, reg); 44562306a36Sopenharmony_ci if (ret) { 44662306a36Sopenharmony_ci dev_err(ili->dev, "can't write ENTRY reg (%d)\n", ret); 44762306a36Sopenharmony_ci return ret; 44862306a36Sopenharmony_ci } 44962306a36Sopenharmony_ci dev_info(ili->dev, "display is in %s mode, syncmode %02x\n", 45062306a36Sopenharmony_ci ili9322_inputs[ili->input], 45162306a36Sopenharmony_ci ili->conf->syncmode); 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci dev_info(ili->dev, "initialized display\n"); 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci return 0; 45662306a36Sopenharmony_ci} 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci/* 45962306a36Sopenharmony_ci * This power-on sequence if from the datasheet, page 57. 46062306a36Sopenharmony_ci */ 46162306a36Sopenharmony_cistatic int ili9322_power_on(struct ili9322 *ili) 46262306a36Sopenharmony_ci{ 46362306a36Sopenharmony_ci int ret; 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci /* Assert RESET */ 46662306a36Sopenharmony_ci gpiod_set_value(ili->reset_gpio, 1); 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci ret = regulator_bulk_enable(ARRAY_SIZE(ili->supplies), ili->supplies); 46962306a36Sopenharmony_ci if (ret < 0) { 47062306a36Sopenharmony_ci dev_err(ili->dev, "unable to enable regulators\n"); 47162306a36Sopenharmony_ci return ret; 47262306a36Sopenharmony_ci } 47362306a36Sopenharmony_ci msleep(20); 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci /* De-assert RESET */ 47662306a36Sopenharmony_ci gpiod_set_value(ili->reset_gpio, 0); 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci msleep(10); 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci return 0; 48162306a36Sopenharmony_ci} 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_cistatic int ili9322_power_off(struct ili9322 *ili) 48462306a36Sopenharmony_ci{ 48562306a36Sopenharmony_ci return regulator_bulk_disable(ARRAY_SIZE(ili->supplies), ili->supplies); 48662306a36Sopenharmony_ci} 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_cistatic int ili9322_disable(struct drm_panel *panel) 48962306a36Sopenharmony_ci{ 49062306a36Sopenharmony_ci struct ili9322 *ili = panel_to_ili9322(panel); 49162306a36Sopenharmony_ci int ret; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci ret = regmap_write(ili->regmap, ILI9322_POW_CTRL, 49462306a36Sopenharmony_ci ILI9322_POW_CTRL_STANDBY); 49562306a36Sopenharmony_ci if (ret) { 49662306a36Sopenharmony_ci dev_err(ili->dev, "unable to go to standby mode\n"); 49762306a36Sopenharmony_ci return ret; 49862306a36Sopenharmony_ci } 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci return 0; 50162306a36Sopenharmony_ci} 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_cistatic int ili9322_unprepare(struct drm_panel *panel) 50462306a36Sopenharmony_ci{ 50562306a36Sopenharmony_ci struct ili9322 *ili = panel_to_ili9322(panel); 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci return ili9322_power_off(ili); 50862306a36Sopenharmony_ci} 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_cistatic int ili9322_prepare(struct drm_panel *panel) 51162306a36Sopenharmony_ci{ 51262306a36Sopenharmony_ci struct ili9322 *ili = panel_to_ili9322(panel); 51362306a36Sopenharmony_ci int ret; 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ci ret = ili9322_power_on(ili); 51662306a36Sopenharmony_ci if (ret < 0) 51762306a36Sopenharmony_ci return ret; 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci ret = ili9322_init(panel, ili); 52062306a36Sopenharmony_ci if (ret < 0) 52162306a36Sopenharmony_ci ili9322_unprepare(panel); 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci return ret; 52462306a36Sopenharmony_ci} 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_cistatic int ili9322_enable(struct drm_panel *panel) 52762306a36Sopenharmony_ci{ 52862306a36Sopenharmony_ci struct ili9322 *ili = panel_to_ili9322(panel); 52962306a36Sopenharmony_ci int ret; 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci ret = regmap_write(ili->regmap, ILI9322_POW_CTRL, 53262306a36Sopenharmony_ci ILI9322_POW_CTRL_DEFAULT); 53362306a36Sopenharmony_ci if (ret) { 53462306a36Sopenharmony_ci dev_err(ili->dev, "unable to enable panel\n"); 53562306a36Sopenharmony_ci return ret; 53662306a36Sopenharmony_ci } 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci return 0; 53962306a36Sopenharmony_ci} 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci/* Serial RGB modes */ 54262306a36Sopenharmony_cistatic const struct drm_display_mode srgb_320x240_mode = { 54362306a36Sopenharmony_ci .clock = 24535, 54462306a36Sopenharmony_ci .hdisplay = 320, 54562306a36Sopenharmony_ci .hsync_start = 320 + 359, 54662306a36Sopenharmony_ci .hsync_end = 320 + 359 + 1, 54762306a36Sopenharmony_ci .htotal = 320 + 359 + 1 + 241, 54862306a36Sopenharmony_ci .vdisplay = 240, 54962306a36Sopenharmony_ci .vsync_start = 240 + 4, 55062306a36Sopenharmony_ci .vsync_end = 240 + 4 + 1, 55162306a36Sopenharmony_ci .vtotal = 262, 55262306a36Sopenharmony_ci .flags = 0, 55362306a36Sopenharmony_ci}; 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_cistatic const struct drm_display_mode srgb_360x240_mode = { 55662306a36Sopenharmony_ci .clock = 27000, 55762306a36Sopenharmony_ci .hdisplay = 360, 55862306a36Sopenharmony_ci .hsync_start = 360 + 35, 55962306a36Sopenharmony_ci .hsync_end = 360 + 35 + 1, 56062306a36Sopenharmony_ci .htotal = 360 + 35 + 1 + 241, 56162306a36Sopenharmony_ci .vdisplay = 240, 56262306a36Sopenharmony_ci .vsync_start = 240 + 21, 56362306a36Sopenharmony_ci .vsync_end = 240 + 21 + 1, 56462306a36Sopenharmony_ci .vtotal = 262, 56562306a36Sopenharmony_ci .flags = 0, 56662306a36Sopenharmony_ci}; 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci/* This is the only mode listed for parallel RGB in the datasheet */ 56962306a36Sopenharmony_cistatic const struct drm_display_mode prgb_320x240_mode = { 57062306a36Sopenharmony_ci .clock = 64000, 57162306a36Sopenharmony_ci .hdisplay = 320, 57262306a36Sopenharmony_ci .hsync_start = 320 + 38, 57362306a36Sopenharmony_ci .hsync_end = 320 + 38 + 1, 57462306a36Sopenharmony_ci .htotal = 320 + 38 + 1 + 50, 57562306a36Sopenharmony_ci .vdisplay = 240, 57662306a36Sopenharmony_ci .vsync_start = 240 + 4, 57762306a36Sopenharmony_ci .vsync_end = 240 + 4 + 1, 57862306a36Sopenharmony_ci .vtotal = 262, 57962306a36Sopenharmony_ci .flags = 0, 58062306a36Sopenharmony_ci}; 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci/* YUV modes */ 58362306a36Sopenharmony_cistatic const struct drm_display_mode yuv_640x320_mode = { 58462306a36Sopenharmony_ci .clock = 24540, 58562306a36Sopenharmony_ci .hdisplay = 640, 58662306a36Sopenharmony_ci .hsync_start = 640 + 252, 58762306a36Sopenharmony_ci .hsync_end = 640 + 252 + 1, 58862306a36Sopenharmony_ci .htotal = 640 + 252 + 1 + 28, 58962306a36Sopenharmony_ci .vdisplay = 320, 59062306a36Sopenharmony_ci .vsync_start = 320 + 4, 59162306a36Sopenharmony_ci .vsync_end = 320 + 4 + 1, 59262306a36Sopenharmony_ci .vtotal = 320 + 4 + 1 + 18, 59362306a36Sopenharmony_ci .flags = 0, 59462306a36Sopenharmony_ci}; 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_cistatic const struct drm_display_mode yuv_720x360_mode = { 59762306a36Sopenharmony_ci .clock = 27000, 59862306a36Sopenharmony_ci .hdisplay = 720, 59962306a36Sopenharmony_ci .hsync_start = 720 + 252, 60062306a36Sopenharmony_ci .hsync_end = 720 + 252 + 1, 60162306a36Sopenharmony_ci .htotal = 720 + 252 + 1 + 24, 60262306a36Sopenharmony_ci .vdisplay = 360, 60362306a36Sopenharmony_ci .vsync_start = 360 + 4, 60462306a36Sopenharmony_ci .vsync_end = 360 + 4 + 1, 60562306a36Sopenharmony_ci .vtotal = 360 + 4 + 1 + 18, 60662306a36Sopenharmony_ci .flags = 0, 60762306a36Sopenharmony_ci}; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci/* BT.656 VGA mode, 640x480 */ 61062306a36Sopenharmony_cistatic const struct drm_display_mode itu_r_bt_656_640_mode = { 61162306a36Sopenharmony_ci .clock = 24540, 61262306a36Sopenharmony_ci .hdisplay = 640, 61362306a36Sopenharmony_ci .hsync_start = 640 + 3, 61462306a36Sopenharmony_ci .hsync_end = 640 + 3 + 1, 61562306a36Sopenharmony_ci .htotal = 640 + 3 + 1 + 272, 61662306a36Sopenharmony_ci .vdisplay = 480, 61762306a36Sopenharmony_ci .vsync_start = 480 + 4, 61862306a36Sopenharmony_ci .vsync_end = 480 + 4 + 1, 61962306a36Sopenharmony_ci .vtotal = 500, 62062306a36Sopenharmony_ci .flags = 0, 62162306a36Sopenharmony_ci}; 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci/* BT.656 D1 mode 720x480 */ 62462306a36Sopenharmony_cistatic const struct drm_display_mode itu_r_bt_656_720_mode = { 62562306a36Sopenharmony_ci .clock = 27000, 62662306a36Sopenharmony_ci .hdisplay = 720, 62762306a36Sopenharmony_ci .hsync_start = 720 + 3, 62862306a36Sopenharmony_ci .hsync_end = 720 + 3 + 1, 62962306a36Sopenharmony_ci .htotal = 720 + 3 + 1 + 272, 63062306a36Sopenharmony_ci .vdisplay = 480, 63162306a36Sopenharmony_ci .vsync_start = 480 + 4, 63262306a36Sopenharmony_ci .vsync_end = 480 + 4 + 1, 63362306a36Sopenharmony_ci .vtotal = 500, 63462306a36Sopenharmony_ci .flags = 0, 63562306a36Sopenharmony_ci}; 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_cistatic int ili9322_get_modes(struct drm_panel *panel, 63862306a36Sopenharmony_ci struct drm_connector *connector) 63962306a36Sopenharmony_ci{ 64062306a36Sopenharmony_ci struct ili9322 *ili = panel_to_ili9322(panel); 64162306a36Sopenharmony_ci struct drm_device *drm = connector->dev; 64262306a36Sopenharmony_ci struct drm_display_mode *mode; 64362306a36Sopenharmony_ci struct drm_display_info *info; 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci info = &connector->display_info; 64662306a36Sopenharmony_ci info->width_mm = ili->conf->width_mm; 64762306a36Sopenharmony_ci info->height_mm = ili->conf->height_mm; 64862306a36Sopenharmony_ci if (ili->conf->dclk_active_high) 64962306a36Sopenharmony_ci info->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE; 65062306a36Sopenharmony_ci else 65162306a36Sopenharmony_ci info->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci if (ili->conf->de_active_high) 65462306a36Sopenharmony_ci info->bus_flags |= DRM_BUS_FLAG_DE_HIGH; 65562306a36Sopenharmony_ci else 65662306a36Sopenharmony_ci info->bus_flags |= DRM_BUS_FLAG_DE_LOW; 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci switch (ili->input) { 65962306a36Sopenharmony_ci case ILI9322_INPUT_SRGB_DUMMY_320X240: 66062306a36Sopenharmony_ci mode = drm_mode_duplicate(drm, &srgb_320x240_mode); 66162306a36Sopenharmony_ci break; 66262306a36Sopenharmony_ci case ILI9322_INPUT_SRGB_DUMMY_360X240: 66362306a36Sopenharmony_ci mode = drm_mode_duplicate(drm, &srgb_360x240_mode); 66462306a36Sopenharmony_ci break; 66562306a36Sopenharmony_ci case ILI9322_INPUT_PRGB_THROUGH: 66662306a36Sopenharmony_ci case ILI9322_INPUT_PRGB_ALIGNED: 66762306a36Sopenharmony_ci mode = drm_mode_duplicate(drm, &prgb_320x240_mode); 66862306a36Sopenharmony_ci break; 66962306a36Sopenharmony_ci case ILI9322_INPUT_YUV_640X320_YCBCR: 67062306a36Sopenharmony_ci mode = drm_mode_duplicate(drm, &yuv_640x320_mode); 67162306a36Sopenharmony_ci break; 67262306a36Sopenharmony_ci case ILI9322_INPUT_YUV_720X360_YCBCR: 67362306a36Sopenharmony_ci mode = drm_mode_duplicate(drm, &yuv_720x360_mode); 67462306a36Sopenharmony_ci break; 67562306a36Sopenharmony_ci case ILI9322_INPUT_ITU_R_BT656_720X360_YCBCR: 67662306a36Sopenharmony_ci mode = drm_mode_duplicate(drm, &itu_r_bt_656_720_mode); 67762306a36Sopenharmony_ci break; 67862306a36Sopenharmony_ci case ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR: 67962306a36Sopenharmony_ci mode = drm_mode_duplicate(drm, &itu_r_bt_656_640_mode); 68062306a36Sopenharmony_ci break; 68162306a36Sopenharmony_ci default: 68262306a36Sopenharmony_ci mode = NULL; 68362306a36Sopenharmony_ci break; 68462306a36Sopenharmony_ci } 68562306a36Sopenharmony_ci if (!mode) { 68662306a36Sopenharmony_ci dev_err(panel->dev, "bad mode or failed to add mode\n"); 68762306a36Sopenharmony_ci return -EINVAL; 68862306a36Sopenharmony_ci } 68962306a36Sopenharmony_ci drm_mode_set_name(mode); 69062306a36Sopenharmony_ci /* 69162306a36Sopenharmony_ci * This is the preferred mode because most people are going 69262306a36Sopenharmony_ci * to want to use the display with VGA type graphics. 69362306a36Sopenharmony_ci */ 69462306a36Sopenharmony_ci mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 69562306a36Sopenharmony_ci 69662306a36Sopenharmony_ci /* Set up the polarity */ 69762306a36Sopenharmony_ci if (ili->conf->hsync_active_high) 69862306a36Sopenharmony_ci mode->flags |= DRM_MODE_FLAG_PHSYNC; 69962306a36Sopenharmony_ci else 70062306a36Sopenharmony_ci mode->flags |= DRM_MODE_FLAG_NHSYNC; 70162306a36Sopenharmony_ci if (ili->conf->vsync_active_high) 70262306a36Sopenharmony_ci mode->flags |= DRM_MODE_FLAG_PVSYNC; 70362306a36Sopenharmony_ci else 70462306a36Sopenharmony_ci mode->flags |= DRM_MODE_FLAG_NVSYNC; 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci mode->width_mm = ili->conf->width_mm; 70762306a36Sopenharmony_ci mode->height_mm = ili->conf->height_mm; 70862306a36Sopenharmony_ci drm_mode_probed_add(connector, mode); 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_ci return 1; /* Number of modes */ 71162306a36Sopenharmony_ci} 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_cistatic const struct drm_panel_funcs ili9322_drm_funcs = { 71462306a36Sopenharmony_ci .disable = ili9322_disable, 71562306a36Sopenharmony_ci .unprepare = ili9322_unprepare, 71662306a36Sopenharmony_ci .prepare = ili9322_prepare, 71762306a36Sopenharmony_ci .enable = ili9322_enable, 71862306a36Sopenharmony_ci .get_modes = ili9322_get_modes, 71962306a36Sopenharmony_ci}; 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_cistatic int ili9322_probe(struct spi_device *spi) 72262306a36Sopenharmony_ci{ 72362306a36Sopenharmony_ci struct device *dev = &spi->dev; 72462306a36Sopenharmony_ci struct ili9322 *ili; 72562306a36Sopenharmony_ci const struct regmap_config *regmap_config; 72662306a36Sopenharmony_ci u8 gamma; 72762306a36Sopenharmony_ci u32 val; 72862306a36Sopenharmony_ci int ret; 72962306a36Sopenharmony_ci int i; 73062306a36Sopenharmony_ci 73162306a36Sopenharmony_ci ili = devm_kzalloc(dev, sizeof(struct ili9322), GFP_KERNEL); 73262306a36Sopenharmony_ci if (!ili) 73362306a36Sopenharmony_ci return -ENOMEM; 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_ci spi_set_drvdata(spi, ili); 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_ci ili->dev = dev; 73862306a36Sopenharmony_ci 73962306a36Sopenharmony_ci /* 74062306a36Sopenharmony_ci * Every new incarnation of this display must have a unique 74162306a36Sopenharmony_ci * data entry for the system in this driver. 74262306a36Sopenharmony_ci */ 74362306a36Sopenharmony_ci ili->conf = of_device_get_match_data(dev); 74462306a36Sopenharmony_ci if (!ili->conf) { 74562306a36Sopenharmony_ci dev_err(dev, "missing device configuration\n"); 74662306a36Sopenharmony_ci return -ENODEV; 74762306a36Sopenharmony_ci } 74862306a36Sopenharmony_ci 74962306a36Sopenharmony_ci val = ili->conf->vreg1out_mv; 75062306a36Sopenharmony_ci if (!val) { 75162306a36Sopenharmony_ci /* Default HW value, do not touch (should be 4.5V) */ 75262306a36Sopenharmony_ci ili->vreg1out = U8_MAX; 75362306a36Sopenharmony_ci } else { 75462306a36Sopenharmony_ci if (val < 3600) { 75562306a36Sopenharmony_ci dev_err(dev, "too low VREG1OUT\n"); 75662306a36Sopenharmony_ci return -EINVAL; 75762306a36Sopenharmony_ci } 75862306a36Sopenharmony_ci if (val > 6000) { 75962306a36Sopenharmony_ci dev_err(dev, "too high VREG1OUT\n"); 76062306a36Sopenharmony_ci return -EINVAL; 76162306a36Sopenharmony_ci } 76262306a36Sopenharmony_ci if ((val % 100) != 0) { 76362306a36Sopenharmony_ci dev_err(dev, "VREG1OUT is no even 100 microvolt\n"); 76462306a36Sopenharmony_ci return -EINVAL; 76562306a36Sopenharmony_ci } 76662306a36Sopenharmony_ci val -= 3600; 76762306a36Sopenharmony_ci val /= 100; 76862306a36Sopenharmony_ci dev_dbg(dev, "VREG1OUT = 0x%02x\n", val); 76962306a36Sopenharmony_ci ili->vreg1out = val; 77062306a36Sopenharmony_ci } 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci val = ili->conf->vcom_high_percent; 77362306a36Sopenharmony_ci if (!val) { 77462306a36Sopenharmony_ci /* Default HW value, do not touch (should be 91%) */ 77562306a36Sopenharmony_ci ili->vcom_high = U8_MAX; 77662306a36Sopenharmony_ci } else { 77762306a36Sopenharmony_ci if (val < 37) { 77862306a36Sopenharmony_ci dev_err(dev, "too low VCOM high\n"); 77962306a36Sopenharmony_ci return -EINVAL; 78062306a36Sopenharmony_ci } 78162306a36Sopenharmony_ci if (val > 100) { 78262306a36Sopenharmony_ci dev_err(dev, "too high VCOM high\n"); 78362306a36Sopenharmony_ci return -EINVAL; 78462306a36Sopenharmony_ci } 78562306a36Sopenharmony_ci val -= 37; 78662306a36Sopenharmony_ci dev_dbg(dev, "VCOM high = 0x%02x\n", val); 78762306a36Sopenharmony_ci ili->vcom_high = val; 78862306a36Sopenharmony_ci } 78962306a36Sopenharmony_ci 79062306a36Sopenharmony_ci val = ili->conf->vcom_amplitude_percent; 79162306a36Sopenharmony_ci if (!val) { 79262306a36Sopenharmony_ci /* Default HW value, do not touch (should be 114%) */ 79362306a36Sopenharmony_ci ili->vcom_high = U8_MAX; 79462306a36Sopenharmony_ci } else { 79562306a36Sopenharmony_ci if (val < 70) { 79662306a36Sopenharmony_ci dev_err(dev, "too low VCOM amplitude\n"); 79762306a36Sopenharmony_ci return -EINVAL; 79862306a36Sopenharmony_ci } 79962306a36Sopenharmony_ci if (val > 132) { 80062306a36Sopenharmony_ci dev_err(dev, "too high VCOM amplitude\n"); 80162306a36Sopenharmony_ci return -EINVAL; 80262306a36Sopenharmony_ci } 80362306a36Sopenharmony_ci val -= 70; 80462306a36Sopenharmony_ci val >>= 1; /* Increments of 2% */ 80562306a36Sopenharmony_ci dev_dbg(dev, "VCOM amplitude = 0x%02x\n", val); 80662306a36Sopenharmony_ci ili->vcom_amplitude = val; 80762306a36Sopenharmony_ci } 80862306a36Sopenharmony_ci 80962306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(ili->gamma); i++) { 81062306a36Sopenharmony_ci val = ili->conf->gamma_corr_neg[i]; 81162306a36Sopenharmony_ci if (val > 15) { 81262306a36Sopenharmony_ci dev_err(dev, "negative gamma %u > 15, capping\n", val); 81362306a36Sopenharmony_ci val = 15; 81462306a36Sopenharmony_ci } 81562306a36Sopenharmony_ci gamma = val << 4; 81662306a36Sopenharmony_ci val = ili->conf->gamma_corr_pos[i]; 81762306a36Sopenharmony_ci if (val > 15) { 81862306a36Sopenharmony_ci dev_err(dev, "positive gamma %u > 15, capping\n", val); 81962306a36Sopenharmony_ci val = 15; 82062306a36Sopenharmony_ci } 82162306a36Sopenharmony_ci gamma |= val; 82262306a36Sopenharmony_ci ili->gamma[i] = gamma; 82362306a36Sopenharmony_ci dev_dbg(dev, "gamma V%d: 0x%02x\n", i + 1, gamma); 82462306a36Sopenharmony_ci } 82562306a36Sopenharmony_ci 82662306a36Sopenharmony_ci ili->supplies[0].supply = "vcc"; /* 2.7-3.6 V */ 82762306a36Sopenharmony_ci ili->supplies[1].supply = "iovcc"; /* 1.65-3.6V */ 82862306a36Sopenharmony_ci ili->supplies[2].supply = "vci"; /* 2.7-3.6V */ 82962306a36Sopenharmony_ci ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ili->supplies), 83062306a36Sopenharmony_ci ili->supplies); 83162306a36Sopenharmony_ci if (ret < 0) 83262306a36Sopenharmony_ci return ret; 83362306a36Sopenharmony_ci ret = regulator_set_voltage(ili->supplies[0].consumer, 83462306a36Sopenharmony_ci 2700000, 3600000); 83562306a36Sopenharmony_ci if (ret) 83662306a36Sopenharmony_ci return ret; 83762306a36Sopenharmony_ci ret = regulator_set_voltage(ili->supplies[1].consumer, 83862306a36Sopenharmony_ci 1650000, 3600000); 83962306a36Sopenharmony_ci if (ret) 84062306a36Sopenharmony_ci return ret; 84162306a36Sopenharmony_ci ret = regulator_set_voltage(ili->supplies[2].consumer, 84262306a36Sopenharmony_ci 2700000, 3600000); 84362306a36Sopenharmony_ci if (ret) 84462306a36Sopenharmony_ci return ret; 84562306a36Sopenharmony_ci 84662306a36Sopenharmony_ci ili->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); 84762306a36Sopenharmony_ci if (IS_ERR(ili->reset_gpio)) { 84862306a36Sopenharmony_ci dev_err(dev, "failed to get RESET GPIO\n"); 84962306a36Sopenharmony_ci return PTR_ERR(ili->reset_gpio); 85062306a36Sopenharmony_ci } 85162306a36Sopenharmony_ci 85262306a36Sopenharmony_ci spi->bits_per_word = 8; 85362306a36Sopenharmony_ci ret = spi_setup(spi); 85462306a36Sopenharmony_ci if (ret < 0) { 85562306a36Sopenharmony_ci dev_err(dev, "spi setup failed.\n"); 85662306a36Sopenharmony_ci return ret; 85762306a36Sopenharmony_ci } 85862306a36Sopenharmony_ci regmap_config = &ili9322_regmap_config; 85962306a36Sopenharmony_ci ili->regmap = devm_regmap_init(dev, &ili9322_regmap_bus, dev, 86062306a36Sopenharmony_ci regmap_config); 86162306a36Sopenharmony_ci if (IS_ERR(ili->regmap)) { 86262306a36Sopenharmony_ci dev_err(dev, "failed to allocate register map\n"); 86362306a36Sopenharmony_ci return PTR_ERR(ili->regmap); 86462306a36Sopenharmony_ci } 86562306a36Sopenharmony_ci 86662306a36Sopenharmony_ci ret = regmap_read(ili->regmap, ILI9322_CHIP_ID, &val); 86762306a36Sopenharmony_ci if (ret) { 86862306a36Sopenharmony_ci dev_err(dev, "can't get chip ID (%d)\n", ret); 86962306a36Sopenharmony_ci return ret; 87062306a36Sopenharmony_ci } 87162306a36Sopenharmony_ci if (val != ILI9322_CHIP_ID_MAGIC) { 87262306a36Sopenharmony_ci dev_err(dev, "chip ID 0x%0x2, expected 0x%02x\n", val, 87362306a36Sopenharmony_ci ILI9322_CHIP_ID_MAGIC); 87462306a36Sopenharmony_ci return -ENODEV; 87562306a36Sopenharmony_ci } 87662306a36Sopenharmony_ci 87762306a36Sopenharmony_ci /* Probe the system to find the display setting */ 87862306a36Sopenharmony_ci if (ili->conf->input == ILI9322_INPUT_UNKNOWN) { 87962306a36Sopenharmony_ci ret = regmap_read(ili->regmap, ILI9322_ENTRY, &val); 88062306a36Sopenharmony_ci if (ret) { 88162306a36Sopenharmony_ci dev_err(dev, "can't get entry setting (%d)\n", ret); 88262306a36Sopenharmony_ci return ret; 88362306a36Sopenharmony_ci } 88462306a36Sopenharmony_ci /* Input enum corresponds to HW setting */ 88562306a36Sopenharmony_ci ili->input = (val >> 4) & 0x0f; 88662306a36Sopenharmony_ci if (ili->input >= ILI9322_INPUT_UNKNOWN) 88762306a36Sopenharmony_ci ili->input = ILI9322_INPUT_UNKNOWN; 88862306a36Sopenharmony_ci } else { 88962306a36Sopenharmony_ci ili->input = ili->conf->input; 89062306a36Sopenharmony_ci } 89162306a36Sopenharmony_ci 89262306a36Sopenharmony_ci drm_panel_init(&ili->panel, dev, &ili9322_drm_funcs, 89362306a36Sopenharmony_ci DRM_MODE_CONNECTOR_DPI); 89462306a36Sopenharmony_ci 89562306a36Sopenharmony_ci drm_panel_add(&ili->panel); 89662306a36Sopenharmony_ci 89762306a36Sopenharmony_ci return 0; 89862306a36Sopenharmony_ci} 89962306a36Sopenharmony_ci 90062306a36Sopenharmony_cistatic void ili9322_remove(struct spi_device *spi) 90162306a36Sopenharmony_ci{ 90262306a36Sopenharmony_ci struct ili9322 *ili = spi_get_drvdata(spi); 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_ci ili9322_power_off(ili); 90562306a36Sopenharmony_ci drm_panel_remove(&ili->panel); 90662306a36Sopenharmony_ci} 90762306a36Sopenharmony_ci 90862306a36Sopenharmony_ci/* 90962306a36Sopenharmony_ci * The D-Link DIR-685 panel is marked LM918A01-1A SY-B4-091116-E0199 91062306a36Sopenharmony_ci */ 91162306a36Sopenharmony_cistatic const struct ili9322_config ili9322_dir_685 = { 91262306a36Sopenharmony_ci .width_mm = 65, 91362306a36Sopenharmony_ci .height_mm = 50, 91462306a36Sopenharmony_ci .input = ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR, 91562306a36Sopenharmony_ci .vreg1out_mv = 4600, 91662306a36Sopenharmony_ci .vcom_high_percent = 91, 91762306a36Sopenharmony_ci .vcom_amplitude_percent = 114, 91862306a36Sopenharmony_ci .syncmode = ILI9322_IF_CTRL_SYNC_DISABLED, 91962306a36Sopenharmony_ci .dclk_active_high = true, 92062306a36Sopenharmony_ci .gamma_corr_neg = { 0xa, 0x5, 0x7, 0x7, 0x7, 0x5, 0x1, 0x6 }, 92162306a36Sopenharmony_ci .gamma_corr_pos = { 0x7, 0x7, 0x3, 0x2, 0x3, 0x5, 0x7, 0x2 }, 92262306a36Sopenharmony_ci}; 92362306a36Sopenharmony_ci 92462306a36Sopenharmony_cistatic const struct of_device_id ili9322_of_match[] = { 92562306a36Sopenharmony_ci { 92662306a36Sopenharmony_ci .compatible = "dlink,dir-685-panel", 92762306a36Sopenharmony_ci .data = &ili9322_dir_685, 92862306a36Sopenharmony_ci }, 92962306a36Sopenharmony_ci { 93062306a36Sopenharmony_ci .compatible = "ilitek,ili9322", 93162306a36Sopenharmony_ci .data = NULL, 93262306a36Sopenharmony_ci }, 93362306a36Sopenharmony_ci { } 93462306a36Sopenharmony_ci}; 93562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ili9322_of_match); 93662306a36Sopenharmony_ci 93762306a36Sopenharmony_cistatic struct spi_driver ili9322_driver = { 93862306a36Sopenharmony_ci .probe = ili9322_probe, 93962306a36Sopenharmony_ci .remove = ili9322_remove, 94062306a36Sopenharmony_ci .driver = { 94162306a36Sopenharmony_ci .name = "panel-ilitek-ili9322", 94262306a36Sopenharmony_ci .of_match_table = ili9322_of_match, 94362306a36Sopenharmony_ci }, 94462306a36Sopenharmony_ci}; 94562306a36Sopenharmony_cimodule_spi_driver(ili9322_driver); 94662306a36Sopenharmony_ci 94762306a36Sopenharmony_ciMODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); 94862306a36Sopenharmony_ciMODULE_DESCRIPTION("ILI9322 LCD panel driver"); 94962306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 950