// SPDX-License-Identifier: GPL-2.0-only /* * Ilitek ILI9322 TFT LCD drm_panel driver. * * This panel can be configured to support: * - 8-bit serial RGB interface * - 24-bit parallel RGB interface * - 8-bit ITU-R BT.601 interface * - 8-bit ITU-R BT.656 interface * - Up to 320RGBx240 dots resolution TFT LCD displays * - Scaling, brightness and contrast * * The scaling means that the display accepts a 640x480 or 720x480 * input and rescales it to fit to the 320x240 display. So what we * present to the system is something else than what comes out on the * actual display. * * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> * Derived from drivers/drm/gpu/panel/panel-samsung-ld9040.c
*/
/* * Voltage on the communication interface, from 0.7 (0x00) * to 1.32 (0x1f) times the VREG1OUT voltage in 2% increments. * 1.00 (0x0f) is the default.
*/ #define ILI9322_VCOM_AMP 0x01
/* * High voltage on the communication signals, from 0.37 (0x00) to * 1.0 (0x3f) times the VREGOUT1 voltage in 1% increments. * 0.83 (0x2e) is the default.
*/ #define ILI9322_VCOM_HIGH 0x02
/* * VREG1 voltage regulator from 3.6V (0x00) to 6.0V (0x18) in 0.1V * increments. 5.4V (0x12) is the default. This is the reference * voltage for the VCOM levels and the greyscale level.
*/ #define ILI9322_VREG1_VOLTAGE 0x03
/* * enum ili9322_input - the format of the incoming signal to the panel * * The panel can be connected to various input streams and four of them can * be selected by electronic straps on the display. However it is possible * to select another mode or override the electronic default with this * setting.
*/ enum ili9322_input {
ILI9322_INPUT_SRGB_THROUGH = 0x0,
ILI9322_INPUT_SRGB_ALIGNED = 0x1,
ILI9322_INPUT_SRGB_DUMMY_320X240 = 0x2,
ILI9322_INPUT_SRGB_DUMMY_360X240 = 0x3,
ILI9322_INPUT_DISABLED_1 = 0x4,
ILI9322_INPUT_PRGB_THROUGH = 0x5,
ILI9322_INPUT_PRGB_ALIGNED = 0x6,
ILI9322_INPUT_YUV_640X320_YCBCR = 0x7,
ILI9322_INPUT_YUV_720X360_YCBCR = 0x8,
ILI9322_INPUT_DISABLED_2 = 0x9,
ILI9322_INPUT_ITU_R_BT656_720X360_YCBCR = 0xa,
ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR = 0xb,
ILI9322_INPUT_UNKNOWN = 0xc,
};
staticconstchar * const ili9322_inputs[] = { "8 bit serial RGB through", "8 bit serial RGB aligned", "8 bit serial RGB dummy 320x240", "8 bit serial RGB dummy 360x240", "disabled 1", "24 bit parallel RGB through", "24 bit parallel RGB aligned", "24 bit YUV 640Y 320CbCr", "24 bit YUV 720Y 360CbCr", "disabled 2", "8 bit ITU-R BT.656 720Y 360CbCr", "8 bit ITU-R BT.656 640Y 320CbCr",
};
/** * struct ili9322_config - the system specific ILI9322 configuration * @width_mm: physical panel width [mm] * @height_mm: physical panel height [mm] * @flip_horizontal: flip the image horizontally (right-to-left scan) * (only in RGB and YUV modes) * @flip_vertical: flip the image vertically (down-to-up scan) * (only in RGB and YUV modes) * @input: the input/entry type used in this system, if this is set to * ILI9322_INPUT_UNKNOWN the driver will try to figure it out by probing * the hardware * @vreg1out_mv: the output in microvolts for the VREGOUT1 regulator used * to drive the physical display. Valid ranges are 3600 thru 6000 in 100 * microvolt increments. If not specified, hardware defaults will be * used (4.5V). * @vcom_high_percent: the percentage of VREGOUT1 used for the peak * voltage on the communications link. Valid ranges are 37 thru 100 * percent. If not specified, hardware defaults will be used (91%). * @vcom_amplitude_percent: the percentage of VREGOUT1 used for the * peak-to-peak amplitude of the communcation signals to the physical * display. Valid ranges are 70 thru 132 percent in increments if two * percent. Odd percentages will be truncated. If not specified, hardware * defaults will be used (114%). * @dclk_active_high: data/pixel clock active high, data will be clocked * in on the rising edge of the DCLK (this is usually the case). * @syncmode: The synchronization mode, what sync signals are emitted. * See the enum for details. * @de_active_high: DE (data entry) is active high * @hsync_active_high: HSYNC is active high * @vsync_active_high: VSYNC is active high * @gamma_corr_pos: a set of 8 nybbles describing positive * gamma correction for voltages V1 thru V8. Valid range 0..15 * @gamma_corr_neg: a set of 8 nybbles describing negative * gamma correction for voltages V1 thru V8. Valid range 0..15 * * These adjust what grayscale voltage will be output for input data V1 = 0, * V2 = 16, V3 = 48, V4 = 96, V5 = 160, V6 = 208, V7 = 240 and V8 = 255. * The curve is shaped like this: * * ^ * | V8 * | V7 * | V6 * | V5 * | V4 * | V3 * | V2 * | V1 * +-----------------------------------------------------------> * 0 16 48 96 160 208 240 255 * * The negative and postive gamma values adjust the V1 thru V8 up/down * according to the datasheet specifications. This is a property of the * physical display connected to the display controller and may vary. * If defined, both arrays must be supplied in full. If the properties * are not supplied, hardware defaults will be used.
*/ struct ili9322_config {
u32 width_mm;
u32 height_mm; bool flip_horizontal; bool flip_vertical; enum ili9322_input input;
u32 vreg1out_mv;
u32 vcom_high_percent;
u32 vcom_amplitude_percent; bool dclk_active_high; bool de_active_high; bool hsync_active_high; bool vsync_active_high;
u8 syncmode;
u8 gamma_corr_pos[8];
u8 gamma_corr_neg[8];
};
/* Set up the main voltage regulator */ if (ili->vreg1out != U8_MAX) {
ret = regmap_write(ili->regmap, ILI9322_VREG1_VOLTAGE,
ili->vreg1out); if (ret) {
dev_err(ili->dev, "can't set up VREG1OUT (%d)\n", ret); return ret;
}
}
if (ili->vcom_amplitude != U8_MAX) {
ret = regmap_write(ili->regmap, ILI9322_VCOM_AMP,
ili->vcom_amplitude); if (ret) {
dev_err(ili->dev, "can't set up VCOM amplitude (%d)\n", ret); return ret;
}
}
if (ili->vcom_high != U8_MAX) {
ret = regmap_write(ili->regmap, ILI9322_VCOM_HIGH,
ili->vcom_high); if (ret) {
dev_err(ili->dev, "can't set up VCOM high (%d)\n", ret); return ret;
}
}
/* Set up gamma correction */ for (i = 0; i < ARRAY_SIZE(ili->gamma); i++) {
ret = regmap_write(ili->regmap, ILI9322_GAMMA_1 + i,
ili->gamma[i]); if (ret) {
dev_err(ili->dev, "can't write gamma V%d to 0x%02x (%d)\n",
i + 1, ILI9322_GAMMA_1 + i, ret); return ret;
}
}
/* * Polarity and inverted color order for RGB input. * None of this applies in the BT.656 mode.
*/
reg = 0; if (ili->conf->dclk_active_high)
reg = ILI9322_POL_DCLK; if (ili->conf->de_active_high)
reg |= ILI9322_POL_DE; if (ili->conf->hsync_active_high)
reg |= ILI9322_POL_HSYNC; if (ili->conf->vsync_active_high)
reg |= ILI9322_POL_VSYNC;
ret = regmap_write(ili->regmap, ILI9322_POL, reg); if (ret) {
dev_err(ili->dev, "can't write POL register (%d)\n", ret); return ret;
}
/* * Set up interface control. * This is not used in the BT.656 mode (no H/Vsync or DE signals).
*/
reg = ili->conf->syncmode;
reg |= ILI9322_IF_CTRL_LINE_INVERSION;
ret = regmap_write(ili->regmap, ILI9322_IF_CTRL, reg); if (ret) {
dev_err(ili->dev, "can't write IF CTRL register (%d)\n", ret); return ret;
}
/* Set up the input mode */
reg = (ili->input << 4); /* These are inverted, setting to 1 is the default, clearing flips */ if (!ili->conf->flip_horizontal)
reg |= ILI9322_ENTRY_HDIR; if (!ili->conf->flip_vertical)
reg |= ILI9322_ENTRY_VDIR;
reg |= ILI9322_ENTRY_AUTODETECT;
ret = regmap_write(ili->regmap, ILI9322_ENTRY, reg); if (ret) {
dev_err(ili->dev, "can't write ENTRY reg (%d)\n", ret); return ret;
}
dev_info(ili->dev, "display is in %s mode, syncmode %02x\n",
ili9322_inputs[ili->input],
ili->conf->syncmode);
dev_info(ili->dev, "initialized display\n");
return 0;
}
/* * This power-on sequence if from the datasheet, page 57.
*/ staticint ili9322_power_on(struct ili9322 *ili)
{ int ret;
ret = regmap_write(ili->regmap, ILI9322_POW_CTRL,
ILI9322_POW_CTRL_STANDBY); if (ret) {
dev_err(ili->dev, "unable to go to standby mode\n"); return ret;
}
info = &connector->display_info;
info->width_mm = ili->conf->width_mm;
info->height_mm = ili->conf->height_mm; if (ili->conf->dclk_active_high)
info->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE; else
info->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
if (ili->conf->de_active_high)
info->bus_flags |= DRM_BUS_FLAG_DE_HIGH; else
info->bus_flags |= DRM_BUS_FLAG_DE_LOW;
switch (ili->input) { case ILI9322_INPUT_SRGB_DUMMY_320X240:
mode = drm_mode_duplicate(drm, &srgb_320x240_mode); break; case ILI9322_INPUT_SRGB_DUMMY_360X240:
mode = drm_mode_duplicate(drm, &srgb_360x240_mode); break; case ILI9322_INPUT_PRGB_THROUGH: case ILI9322_INPUT_PRGB_ALIGNED:
mode = drm_mode_duplicate(drm, &prgb_320x240_mode); break; case ILI9322_INPUT_YUV_640X320_YCBCR:
mode = drm_mode_duplicate(drm, &yuv_640x320_mode); break; case ILI9322_INPUT_YUV_720X360_YCBCR:
mode = drm_mode_duplicate(drm, &yuv_720x360_mode); break; case ILI9322_INPUT_ITU_R_BT656_720X360_YCBCR:
mode = drm_mode_duplicate(drm, &itu_r_bt_656_720_mode); break; case ILI9322_INPUT_ITU_R_BT656_640X320_YCBCR:
mode = drm_mode_duplicate(drm, &itu_r_bt_656_640_mode); break; default:
mode = NULL; break;
} if (!mode) {
dev_err(panel->dev, "bad mode or failed to add mode\n"); return -EINVAL;
}
drm_mode_set_name(mode); /* * This is the preferred mode because most people are going * to want to use the display with VGA type graphics.
*/
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
/* Set up the polarity */ if (ili->conf->hsync_active_high)
mode->flags |= DRM_MODE_FLAG_PHSYNC; else
mode->flags |= DRM_MODE_FLAG_NHSYNC; if (ili->conf->vsync_active_high)
mode->flags |= DRM_MODE_FLAG_PVSYNC; else
mode->flags |= DRM_MODE_FLAG_NVSYNC;
ili = devm_drm_panel_alloc(dev, struct ili9322, panel,
&ili9322_drm_funcs, DRM_MODE_CONNECTOR_DPI); if (IS_ERR(ili)) return PTR_ERR(ili);
spi_set_drvdata(spi, ili);
ili->dev = dev;
/* * Every new incarnation of this display must have a unique * data entry for the system in this driver.
*/
ili->conf = of_device_get_match_data(dev); if (!ili->conf) {
dev_err(dev, "missing device configuration\n"); return -ENODEV;
}
val = ili->conf->vreg1out_mv; if (!val) { /* Default HW value, do not touch (should be 4.5V) */
ili->vreg1out = U8_MAX;
} else { if (val < 3600) {
dev_err(dev, "too low VREG1OUT\n"); return -EINVAL;
} if (val > 6000) {
dev_err(dev, "too high VREG1OUT\n"); return -EINVAL;
} if ((val % 100) != 0) {
dev_err(dev, "VREG1OUT is no even 100 microvolt\n"); return -EINVAL;
}
val -= 3600;
val /= 100;
dev_dbg(dev, "VREG1OUT = 0x%02x\n", val);
ili->vreg1out = val;
}
val = ili->conf->vcom_high_percent; if (!val) { /* Default HW value, do not touch (should be 91%) */
ili->vcom_high = U8_MAX;
} else { if (val < 37) {
dev_err(dev, "too low VCOM high\n"); return -EINVAL;
} if (val > 100) {
dev_err(dev, "too high VCOM high\n"); return -EINVAL;
}
val -= 37;
dev_dbg(dev, "VCOM high = 0x%02x\n", val);
ili->vcom_high = val;
}
val = ili->conf->vcom_amplitude_percent; if (!val) { /* Default HW value, do not touch (should be 114%) */
ili->vcom_high = U8_MAX;
} else { if (val < 70) {
dev_err(dev, "too low VCOM amplitude\n"); return -EINVAL;
} if (val > 132) {
dev_err(dev, "too high VCOM amplitude\n"); return -EINVAL;
}
val -= 70;
val >>= 1; /* Increments of 2% */
dev_dbg(dev, "VCOM amplitude = 0x%02x\n", val);
ili->vcom_amplitude = val;
}
for (i = 0; i < ARRAY_SIZE(ili->gamma); i++) {
val = ili->conf->gamma_corr_neg[i]; if (val > 15) {
dev_err(dev, "negative gamma %u > 15, capping\n", val);
val = 15;
}
gamma = val << 4;
val = ili->conf->gamma_corr_pos[i]; if (val > 15) {
dev_err(dev, "positive gamma %u > 15, capping\n", val);
val = 15;
}
gamma |= val;
ili->gamma[i] = gamma;
dev_dbg(dev, "gamma V%d: 0x%02x\n", i + 1, gamma);
}
ili->supplies[0].supply = "vcc"; /* 2.7-3.6 V */
ili->supplies[1].supply = "iovcc"; /* 1.65-3.6V */
ili->supplies[2].supply = "vci"; /* 2.7-3.6V */
ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ili->supplies),
ili->supplies); if (ret < 0) return ret;
ret = regulator_set_voltage(ili->supplies[0].consumer,
2700000, 3600000); if (ret) return ret;
ret = regulator_set_voltage(ili->supplies[1].consumer,
1650000, 3600000); if (ret) return ret;
ret = regulator_set_voltage(ili->supplies[2].consumer,
2700000, 3600000); if (ret) return ret;
ili->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(ili->reset_gpio)) {
dev_err(dev, "failed to get RESET GPIO\n"); return PTR_ERR(ili->reset_gpio);
}
spi->bits_per_word = 8;
ret = spi_setup(spi); if (ret < 0) {
dev_err(dev, "spi setup failed.\n"); return ret;
}
regmap_config = &ili9322_regmap_config;
ili->regmap = devm_regmap_init(dev, &ili9322_regmap_bus, dev,
regmap_config); if (IS_ERR(ili->regmap)) {
dev_err(dev, "failed to allocate register map\n"); return PTR_ERR(ili->regmap);
}
ret = regmap_read(ili->regmap, ILI9322_CHIP_ID, &val); if (ret) {
dev_err(dev, "can't get chip ID (%d)\n", ret); return ret;
} if (val != ILI9322_CHIP_ID_MAGIC) {
dev_err(dev, "chip ID 0x%0x2, expected 0x%02x\n", val,
ILI9322_CHIP_ID_MAGIC); return -ENODEV;
}
/* Probe the system to find the display setting */ if (ili->conf->input == ILI9322_INPUT_UNKNOWN) {
ret = regmap_read(ili->regmap, ILI9322_ENTRY, &val); if (ret) {
dev_err(dev, "can't get entry setting (%d)\n", ret); return ret;
} /* Input enum corresponds to HW setting */
ili->input = (val >> 4) & 0x0f; if (ili->input >= ILI9322_INPUT_UNKNOWN)
ili->input = ILI9322_INPUT_UNKNOWN;
} else {
ili->input = ili->conf->input;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.