diff options
Diffstat (limited to 'drivers/gpu')
192 files changed, 49154 insertions, 648 deletions
diff --git a/drivers/gpu/Makefile b/drivers/gpu/Makefile index 835c88318cec..b8a7b5c5a029 100644 --- a/drivers/gpu/Makefile +++ b/drivers/gpu/Makefile @@ -3,6 +3,6 @@ # taken to initialize them in the correct order. Link order is the only way # to ensure this currently. obj-$(CONFIG_TEGRA_HOST1X) += host1x/ +obj-y += imx/ obj-y += drm/ vga/ -obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/ obj-$(CONFIG_TRACE_GPU_MEM) += trace/ diff --git a/drivers/gpu/drm/arm/malidp_crtc.c b/drivers/gpu/drm/arm/malidp_crtc.c index b5928b52e279..081beb102150 100644 --- a/drivers/gpu/drm/arm/malidp_crtc.c +++ b/drivers/gpu/drm/arm/malidp_crtc.c @@ -31,7 +31,7 @@ static enum drm_mode_status malidp_crtc_mode_valid(struct drm_crtc *crtc, * check that the hardware can drive the required clock rate, * but skip the check if the clock is meant to be disabled (req_rate = 0) */ - long rate, req_rate = mode->crtc_clock * 1000; + long rate, req_rate = mode->clock * 1000; if (req_rate) { rate = clk_round_rate(hwdev->pxlclk, req_rate); diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 68ec45abc1fb..3d81471f89d3 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -126,6 +126,15 @@ config DRM_ITE_IT66121 help Support for ITE IT66121 HDMI bridge. +config DRM_FSL_IMX_LVDS_BRIDGE + tristate "Freescale i.MX LVDS display bridge" + depends on MFD_SYSCON + depends on OF + select DRM_PANEL_BRIDGE + help + Support Freescale i.MX parallel to LVDS Display Bridge (LDB). + This bridge is embedded in a SoC. + config DRM_LVDS_CODEC tristate "Transparent LVDS encoders and decoders support" depends on OF @@ -162,6 +171,20 @@ config DRM_NWL_MIPI_DSI This enables the Northwest Logic MIPI DSI Host controller as for example found on NXP's i.MX8 Processors. +config DRM_SEC_MIPI_DSIM + tristate "Samsung MIPI DSIM Bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL + help + The Samsung MPI DSIM Bridge driver. + +config DRM_NXP_SEIKO_43WVFIG + tristate "Legacy Freescale Seiko 43WVFIG panel DPI adapter bridge" + select DRM_KMS_HELPER + select DRM_PANEL + config DRM_NXP_PTN3460 tristate "NXP PTN3460 DP/LVDS bridge" depends on OF @@ -323,4 +346,20 @@ source "drivers/gpu/drm/bridge/cadence/Kconfig" source "drivers/gpu/drm/bridge/synopsys/Kconfig" +config DRM_ITE_IT6263 + tristate "ITE IT6263 LVDS/HDMI bridge" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + help + ITE IT6263 bridge chip driver. + +config DRM_ITE_IT6161 + tristate "ITE IT6161 MIPI/HDMI bridge" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + help + ITE IT6161 bridge chip driver. + endmenu diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index f2c73683cfcb..0f93f88d1815 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o +obj-$(CONFIG_DRM_FSL_IMX_LVDS_BRIDGE) += fsl-imx-ldb.o obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o @@ -33,3 +34,7 @@ obj-$(CONFIG_DRM_ITE_IT66121) += ite-it66121.o obj-y += analogix/ obj-y += cadence/ obj-y += synopsys/ +obj-$(CONFIG_DRM_ITE_IT6263) += it6263.o +obj-$(CONFIG_DRM_ITE_IT6263) += it6161.o +obj-$(CONFIG_DRM_SEC_MIPI_DSIM) += sec-dsim.o +obj-$(CONFIG_DRM_NXP_SEIKO_43WVFIG) += nxp-seiko-43wvfig.o diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h index fdd8e3d3232e..4aa8c601223f 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511.h +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -221,6 +221,9 @@ #define ADV7511_REG_CEC_SOFT_RESET 0x50 #define ADV7533_REG_CEC_OFFSET 0x70 +#define FORMAT_RATIO(x, y) (((x) * 100) / (y)) +#define RATIO_16_9 FORMAT_RATIO(16, 9) +#define RATIO_4_3 FORMAT_RATIO(4, 3) enum adv7511_input_clock { ADV7511_INPUT_CLOCK_1X, @@ -333,6 +336,10 @@ struct adv7511 { struct i2c_client *i2c_packet; struct i2c_client *i2c_cec; + u32 addr_cec; + u32 addr_edid; + u32 addr_pkt; + struct regmap *regmap; struct regmap *regmap_cec; enum drm_connector_status status; @@ -368,6 +375,7 @@ struct adv7511 { struct device_node *host_node; struct mipi_dsi_device *dsi; u8 num_dsi_lanes; + u8 channel_id; bool use_timing_gen; enum adv7511_type type; diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c index ddd1305b82b2..bd2628de9be1 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c @@ -302,7 +302,8 @@ static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511) int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511) { - unsigned int offset = adv7511->type == ADV7533 ? + unsigned int offset = (adv7511->type == ADV7533 || + adv7511->type == ADV7535) ? ADV7533_REG_CEC_OFFSET : 0; int ret = adv7511_cec_parse_dt(dev, adv7511); diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index 44762116aac9..dfff98fb2fdc 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -9,7 +9,9 @@ #include <linux/device.h> #include <linux/gpio/consumer.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/of_device.h> +#include <linux/of_graph.h> #include <linux/slab.h> #include <media/cec.h> @@ -74,6 +76,25 @@ static const uint8_t adv7511_register_defaults[] = { 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +/* + * TODO: Currently, filter-out unsupported modes by their clocks. + * Need to find a better way to do this. + * These are the pixel clocks that the converter can handle successfully. + */ + +static const int valid_clocks[] = { + 148500, + 135000, + 132000, + 108000, + 78750, + 74250, + 65000, + 49500, + 40000, + 31500, +}; + static bool adv7511_register_volatile(struct device *dev, unsigned int reg) { switch (reg) { @@ -329,6 +350,7 @@ static void __adv7511_power_on(struct adv7511 *adv7511) { adv7511->current_edid_segment = -1; + /* 01-02 Power */ regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, ADV7511_POWER_POWER_DOWN, 0); if (adv7511->i2c_main->irq) { @@ -346,6 +368,7 @@ static void __adv7511_power_on(struct adv7511 *adv7511) } /* + * 01-01 HPD Manual Override * Per spec it is allowed to pulse the HPD signal to indicate that the * EDID information has changed. Some monitors do this when they wakeup * from standby or are enabled. When the HPD goes low the adv7511 is @@ -427,17 +450,16 @@ static bool adv7511_hpd(struct adv7511 *adv7511) static void adv7511_hpd_work(struct work_struct *work) { struct adv7511 *adv7511 = container_of(work, struct adv7511, hpd_work); - enum drm_connector_status status; + enum drm_connector_status status = connector_status_disconnected; unsigned int val; int ret; ret = regmap_read(adv7511->regmap, ADV7511_REG_STATUS, &val); - if (ret < 0) - status = connector_status_disconnected; - else if (val & ADV7511_STATUS_HPD) + if (ret >= 0 && (val & ADV7511_STATUS_HPD)) status = connector_status_connected; - else - status = connector_status_disconnected; + + DRM_DEV_DEBUG_DRIVER(adv7511->connector.kdev, "HDMI HPD event: %s\n", + drm_get_connector_status_name(status)); /* * The bridge resets its registers on unplug. So when we get a plug @@ -639,6 +661,8 @@ static int adv7511_get_modes(struct adv7511 *adv7511, { struct edid *edid; unsigned int count; + u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + int ret; edid = adv7511_get_edid(adv7511, connector); @@ -647,6 +671,14 @@ static int adv7511_get_modes(struct adv7511 *adv7511, kfree(edid); + connector->display_info.bus_flags = DRM_BUS_FLAG_DE_LOW | + DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE; + + ret = drm_display_info_set_bus_formats(&connector->display_info, + &bus_format, 1); + if (ret) + return ret; + return count; } @@ -699,9 +731,21 @@ adv7511_detect(struct adv7511 *adv7511, struct drm_connector *connector) static enum drm_mode_status adv7511_mode_valid(struct adv7511 *adv7511, const struct drm_display_mode *mode) { + size_t i, num_modes = ARRAY_SIZE(valid_clocks); + bool clock_ok = false; + if (mode->clock > 165000) return MODE_CLOCK_HIGH; + for (i = 0; i < num_modes; i++) + if (mode->clock == valid_clocks[i]) { + clock_ok = true; + break; + } + + if (!clock_ok) + return MODE_NOCLOCK; + return MODE_OK; } @@ -786,8 +830,13 @@ static void adv7511_mode_set(struct adv7511 *adv7511, else low_refresh_rate = ADV7511_LOW_REFRESH_RATE_NONE; - regmap_update_bits(adv7511->regmap, 0xfb, - 0x6, low_refresh_rate << 1); + if (adv7511->type == ADV7535) + regmap_update_bits(adv7511->regmap, 0x4a, + 0xc, low_refresh_rate << 2); + else + regmap_update_bits(adv7511->regmap, 0xfb, + 0x6, low_refresh_rate << 1); + regmap_update_bits(adv7511->regmap, 0x17, 0x60, (vsync_polarity << 6) | (hsync_polarity << 5)); @@ -901,6 +950,14 @@ static void adv7511_bridge_disable(struct drm_bridge *bridge) adv7511_power_off(adv); } +static int adv7511_bridge_get_modes(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + return adv7511_get_modes(adv, connector); +} + static void adv7511_bridge_mode_set(struct drm_bridge *bridge, const struct drm_display_mode *mode, const struct drm_display_mode *adj_mode) @@ -968,15 +1025,31 @@ static void adv7511_bridge_hpd_notify(struct drm_bridge *bridge, cec_phys_addr_invalidate(adv->cec_adap); } +static void adv7511_bridge_detach(struct drm_bridge *bridge) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + if (adv->i2c_main->irq) + regmap_write(adv->regmap, ADV7511_REG_INT_ENABLE(0), 0); + + if (adv->type == ADV7533 || adv->type == ADV7535) + adv7533_detach_dsi(adv); + + drm_connector_cleanup(&adv->connector); +} + static const struct drm_bridge_funcs adv7511_bridge_funcs = { .enable = adv7511_bridge_enable, .disable = adv7511_bridge_disable, + .get_modes = adv7511_bridge_get_modes, + .mode_valid = adv7511_bridge_mode_valid, .mode_set = adv7511_bridge_mode_set, .mode_valid = adv7511_bridge_mode_valid, .attach = adv7511_bridge_attach, .detect = adv7511_bridge_detect, .get_edid = adv7511_bridge_get_edid, .hpd_notify = adv7511_bridge_hpd_notify, + .detach = adv7511_bridge_detach, }; /* ----------------------------------------------------------------------------- @@ -1070,7 +1143,7 @@ static int adv7511_init_cec_regmap(struct adv7511 *adv) int ret; adv->i2c_cec = i2c_new_ancillary_device(adv->i2c_main, "cec", - ADV7511_CEC_I2C_ADDR_DEFAULT); + adv->addr_cec); if (IS_ERR(adv->i2c_cec)) return PTR_ERR(adv->i2c_cec); @@ -1186,6 +1259,14 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) struct adv7511_link_config link_config; struct adv7511 *adv7511; struct device *dev = &i2c->dev; +#if IS_ENABLED(CONFIG_OF_DYNAMIC) + struct device_node *remote_node = NULL, *endpoint = NULL; + struct of_changeset ocs; +#endif + unsigned int main_i2c_addr = i2c->addr << 1; + unsigned int edid_i2c_addr = main_i2c_addr + 4; + unsigned int cec_i2c_addr = main_i2c_addr - 2; + unsigned int pkt_i2c_addr = main_i2c_addr - 0xa; unsigned int val; int ret; @@ -1220,6 +1301,21 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) return ret; } + if (adv7511->addr_cec != 0) + cec_i2c_addr = adv7511->addr_cec << 1; + else + adv7511->addr_cec = cec_i2c_addr >> 1; + + if (adv7511->addr_edid != 0) + edid_i2c_addr = adv7511->addr_edid << 1; + else + adv7511->addr_edid = edid_i2c_addr >> 1; + + if (adv7511->addr_pkt != 0) + pkt_i2c_addr = adv7511->addr_pkt << 1; + else + adv7511->addr_pkt = pkt_i2c_addr >> 1; + /* * The power down GPIO is optional. If present, toggle it from active to * inactive to wake up the encoder. @@ -1257,25 +1353,28 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) adv7511_packet_disable(adv7511, 0xffff); + regmap_write(adv7511->regmap, ADV7511_REG_EDID_I2C_ADDR, + edid_i2c_addr); + adv7511->i2c_edid = i2c_new_ancillary_device(i2c, "edid", - ADV7511_EDID_I2C_ADDR_DEFAULT); + adv7511->addr_edid); if (IS_ERR(adv7511->i2c_edid)) { ret = PTR_ERR(adv7511->i2c_edid); goto uninit_regulators; } - regmap_write(adv7511->regmap, ADV7511_REG_EDID_I2C_ADDR, - adv7511->i2c_edid->addr << 1); + regmap_write(adv7511->regmap, ADV7511_REG_PACKET_I2C_ADDR, + pkt_i2c_addr); adv7511->i2c_packet = i2c_new_ancillary_device(i2c, "packet", - ADV7511_PACKET_I2C_ADDR_DEFAULT); + adv7511->addr_pkt); if (IS_ERR(adv7511->i2c_packet)) { ret = PTR_ERR(adv7511->i2c_packet); goto err_i2c_unregister_edid; } - regmap_write(adv7511->regmap, ADV7511_REG_PACKET_I2C_ADDR, - adv7511->i2c_packet->addr << 1); + regmap_write(adv7511->regmap, ADV7511_REG_CEC_I2C_ADDR, + cec_i2c_addr); ret = adv7511_init_cec_regmap(adv7511); if (ret) @@ -1326,6 +1425,37 @@ err_i2c_unregister_edid: i2c_unregister_device(adv7511->i2c_edid); uninit_regulators: adv7511_uninit_regulators(adv7511); +#if IS_ENABLED(CONFIG_OF_DYNAMIC) + endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (endpoint) + remote_node = of_graph_get_remote_port_parent(endpoint); + + if (!remote_node) + return ret; + + /* Find remote's endpoint connected to us and detach it */ + endpoint = NULL; + while ((endpoint = of_graph_get_next_endpoint(remote_node, + endpoint))) { + struct device_node *us; + + us = of_graph_get_remote_port_parent(endpoint); + if (us == dev->of_node) + break; + } + of_node_put(remote_node); + + if (!endpoint) + return ret; + + of_changeset_init(&ocs); + of_changeset_detach_node(&ocs, endpoint); + ret = of_changeset_apply(&ocs); + if (!ret) + dev_warn(dev, + "Probe failed. Remote port '%s' disabled\n", + remote_node->full_name); +#endif return ret; } @@ -1334,6 +1464,8 @@ static int adv7511_remove(struct i2c_client *i2c) { struct adv7511 *adv7511 = i2c_get_clientdata(i2c); + i2c_unregister_device(adv7511->i2c_cec); + clk_disable_unprepare(adv7511->cec_clk); if (adv7511->type == ADV7533 || adv7511->type == ADV7535) adv7533_detach_dsi(adv7511); diff --git a/drivers/gpu/drm/bridge/adv7511/adv7533.c b/drivers/gpu/drm/bridge/adv7511/adv7533.c index babc0be0bbb5..731e3d907787 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7533.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7533.c @@ -26,10 +26,8 @@ static const struct reg_sequence adv7533_cec_fixed_registers[] = { static void adv7511_dsi_config_timing_gen(struct adv7511 *adv) { - struct mipi_dsi_device *dsi = adv->dsi; struct drm_display_mode *mode = &adv->curr_mode; unsigned int hsw, hfp, hbp, vsw, vfp, vbp; - u8 clock_div_by_lanes[] = { 6, 4, 3 }; /* 2, 3, 4 lanes */ hsw = mode->hsync_end - mode->hsync_start; hfp = mode->hsync_start - mode->hdisplay; @@ -38,9 +36,10 @@ static void adv7511_dsi_config_timing_gen(struct adv7511 *adv) vfp = mode->vsync_start - mode->vdisplay; vbp = mode->vtotal - mode->vsync_end; - /* set pixel clock divider mode */ - regmap_write(adv->regmap_cec, 0x16, - clock_div_by_lanes[dsi->lanes - 2] << 3); + /* 03-01 Enable Internal Timing Generator */ + regmap_write(adv->regmap_cec, 0x27, 0xcb); + + /* 03-08 Timing Configuration */ /* horizontal porch params */ regmap_write(adv->regmap_cec, 0x28, mode->htotal >> 4); @@ -61,35 +60,66 @@ static void adv7511_dsi_config_timing_gen(struct adv7511 *adv) regmap_write(adv->regmap_cec, 0x35, (vfp << 4) & 0xff); regmap_write(adv->regmap_cec, 0x36, vbp >> 4); regmap_write(adv->regmap_cec, 0x37, (vbp << 4) & 0xff); + + /* 03-03 Reset Internal Timing Generator */ + regmap_write(adv->regmap_cec, 0x27, 0xcb); + regmap_write(adv->regmap_cec, 0x27, 0x8b); + regmap_write(adv->regmap_cec, 0x27, 0xcb); + } void adv7533_dsi_power_on(struct adv7511 *adv) { struct mipi_dsi_device *dsi = adv->dsi; + struct drm_display_mode *mode = &adv->curr_mode; + u8 clock_div_by_lanes[] = { 6, 4, 3 }; /* 2, 3, 4 lanes */ - if (adv->use_timing_gen) - adv7511_dsi_config_timing_gen(adv); + /* Gate DSI LP Oscillator */ + regmap_update_bits(adv->regmap_cec, 0x03, 0x02, 0x00); + + /* 01-03 Initialisation (Fixed) Registers */ + regmap_register_patch(adv->regmap_cec, adv7533_cec_fixed_registers, + ARRAY_SIZE(adv7533_cec_fixed_registers)); - /* set number of dsi lanes */ + /* 02-04 DSI Lanes */ regmap_write(adv->regmap_cec, 0x1c, dsi->lanes << 4); - if (adv->use_timing_gen) { - /* reset internal timing generator */ - regmap_write(adv->regmap_cec, 0x27, 0xcb); - regmap_write(adv->regmap_cec, 0x27, 0x8b); - regmap_write(adv->regmap_cec, 0x27, 0xcb); - } else { - /* disable internal timing generator */ + /* 02-05 DSI Pixel Clock Divider */ + regmap_write(adv->regmap_cec, 0x16, + clock_div_by_lanes[dsi->lanes - 2] << 3); + + if (adv->use_timing_gen) + adv7511_dsi_config_timing_gen(adv); + else regmap_write(adv->regmap_cec, 0x27, 0x0b); - } - /* enable hdmi */ + /* 04-01 HDMI Output */ + regmap_write(adv->regmap, 0xaf, 0x16); + + /* 09-03 AVI Infoframe - RGB - 16-9 Aspect Ratio */ + regmap_write(adv->regmap, ADV7511_REG_AVI_INFOFRAME(0), 0x10); + if (FORMAT_RATIO(mode->hdisplay, mode->vdisplay) == RATIO_16_9) + regmap_write(adv->regmap, ADV7511_REG_AVI_INFOFRAME(1), 0x28); + else if (FORMAT_RATIO(mode->hdisplay, mode->vdisplay) == RATIO_4_3) + regmap_write(adv->regmap, ADV7511_REG_AVI_INFOFRAME(1), 0x18); + + /* 04-04 GC Packet Enable */ + regmap_write(adv->regmap, ADV7511_REG_PACKET_ENABLE0, 0x80); + + /* 04-06 GC Colour Depth - 24 Bit */ + regmap_write(adv->regmap, 0x4c, 0x04); + + /* 04-09 Down Dither Output Colour Depth - 8 Bit (default) */ + regmap_write(adv->regmap, 0x49, 0x00); + + /* 07-01 CEC Power Mode - Always Active */ + regmap_write(adv->regmap_cec, 0xbe, 0x3d); + + /* 04-03 HDMI Output Enable */ regmap_write(adv->regmap_cec, 0x03, 0x89); /* disable test mode */ regmap_write(adv->regmap_cec, 0x55, 0x00); - regmap_register_patch(adv->regmap_cec, adv7533_cec_fixed_registers, - ARRAY_SIZE(adv7533_cec_fixed_registers)); } void adv7533_dsi_power_off(struct adv7511 *adv) @@ -141,7 +171,7 @@ int adv7533_attach_dsi(struct adv7511 *adv) struct mipi_dsi_device *dsi; int ret = 0; const struct mipi_dsi_device_info info = { .type = "adv7533", - .channel = 0, + .channel = adv->channel_id, .node = NULL, }; @@ -187,14 +217,24 @@ void adv7533_detach_dsi(struct adv7511 *adv) int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv) { - u32 num_lanes; + struct device *dev = &adv->i2c_main->dev; + u32 num_lanes = 0, channel_id = 0; + of_property_read_u32(np, "adi,dsi-channel", &channel_id); of_property_read_u32(np, "adi,dsi-lanes", &num_lanes); - if (num_lanes < 1 || num_lanes > 4) + if (num_lanes < 1 || num_lanes > 4) { + dev_err(dev, "Invalid dsi-lanes: %d\n", num_lanes); + return -EINVAL; + } + + if (channel_id > 3) { + dev_err(dev, "Invalid dsi-channel: %d\n", channel_id); return -EINVAL; + } adv->num_dsi_lanes = num_lanes; + adv->channel_id = channel_id; adv->host_node = of_graph_get_remote_node(np, 0, 0); if (!adv->host_node) @@ -205,6 +245,10 @@ int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv) adv->use_timing_gen = !of_property_read_bool(np, "adi,disable-timing-generator"); + of_property_read_u32(np, "adi,addr-cec", &adv->addr_cec); + of_property_read_u32(np, "adi,addr-edid", &adv->addr_edid); + of_property_read_u32(np, "adi,addr-pkt", &adv->addr_pkt); + /* TODO: Check if these need to be parsed by DT or not */ adv->rgb = true; adv->embedded_sync = false; diff --git a/drivers/gpu/drm/bridge/cadence/Kconfig b/drivers/gpu/drm/bridge/cadence/Kconfig index ef8c230e0f62..d08aadf32414 100644 --- a/drivers/gpu/drm/bridge/cadence/Kconfig +++ b/drivers/gpu/drm/bridge/cadence/Kconfig @@ -22,3 +22,36 @@ config DRM_CDNS_MHDP8546_J721E initializes the J721E Display Port and sets up the clock and data muxes. endif + +config DRM_CDNS_MHDP + tristate "Cadence MHDP COMMON API driver" + select DRM_KMS_HELPER + select DRM_PANEL_BRIDGE + depends on OF + help + Support Cadence MHDP API library. + +config DRM_CDNS_HDMI + tristate "Cadence HDMI DRM driver" + depends on DRM_CDNS_MHDP + +config DRM_CDNS_DP + tristate "Cadence DP DRM driver" + depends on DRM_CDNS_MHDP + +config DRM_CDNS_AUDIO + tristate "Cadence MHDP Audio driver" + depends on DRM_CDNS_MHDP + +config DRM_CDNS_HDCP + tristate "Cadence HDMI/DP HDCP driver" + depends on DRM_CDNS_MHDP + help + Support HDCP for either HDMI or DisplayPort. This + requires that the SOC has HDCP keys programmed + in production. + +config DRM_CDNS_HDMI_CEC + tristate "Cadence MHDP HDMI CEC driver" + select CEC_CORE + select CEC_NOTIFIER diff --git a/drivers/gpu/drm/bridge/cadence/Makefile b/drivers/gpu/drm/bridge/cadence/Makefile index 4d2db8df1bc6..4e6df43065c7 100644 --- a/drivers/gpu/drm/bridge/cadence/Makefile +++ b/drivers/gpu/drm/bridge/cadence/Makefile @@ -2,3 +2,13 @@ obj-$(CONFIG_DRM_CDNS_MHDP8546) += cdns-mhdp8546.o cdns-mhdp8546-y := cdns-mhdp8546-core.o cdns-mhdp8546-hdcp.o cdns-mhdp8546-$(CONFIG_DRM_CDNS_MHDP8546_J721E) += cdns-mhdp8546-j721e.o + +cdns_mhdp_drmcore-y := cdns-mhdp-common.o cdns-mhdp-dp.o cdns-mhdp-hdmi.o + +cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_HDMI) += cdns-hdmi-core.o +cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_DP) += cdns-dp-core.o +cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_AUDIO) += cdns-mhdp-audio.o +cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_HDCP) += cdns-mhdp-hdcp.o cdns-hdcp-common.o +cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_HDMI_CEC) += cdns-mhdp-cec.o + +obj-$(CONFIG_DRM_CDNS_MHDP) += cdns_mhdp_drmcore.o diff --git a/drivers/gpu/drm/bridge/cadence/cdns-dp-core.c b/drivers/gpu/drm/bridge/cadence/cdns-dp-core.c new file mode 100644 index 000000000000..0b7afd009995 --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-dp-core.c @@ -0,0 +1,881 @@ +/* + * Cadence Display Port Interface (DP) driver + * + * Copyright 2019 NXP + * + * 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. + * + */ +#include <drm/bridge/cdns-mhdp.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_encoder_slave.h> +#include <drm/drm_hdcp.h> +#include <drm/drm_of.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> +#include <drm/drm_print.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_device.h> + +#include "cdns-mhdp-hdcp.h" +#include "cdns-hdcp-common.h" + +#define CDNS_DP_HPD_POLL_DWN_LOOP 5 +#define CDNS_DP_HPD_POLL_DWN_DLY_US 200 +#define CDNS_DP_HPD_POLL_UP_LOOP 5 +#define CDNS_DP_HPD_POLL_UP_DLY_US 1000 + +/* + * This function only implements native DPDC reads and writes + */ +static ssize_t dp_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(aux->dev); + bool native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ); + int ret; + + /* Ignore address only message , for native */ + if ((native == true) && ((msg->size == 0) || (msg->buffer == NULL))) { + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; + } + + /* msg sanity check */ + if (msg->size > DP_AUX_MAX_PAYLOAD_BYTES) { + dev_err(mhdp->dev, "%s: invalid msg: size(%zu), request(%x)\n", + __func__, msg->size, (unsigned int)msg->request); + return -EINVAL; + } + + if (msg->request == DP_AUX_NATIVE_WRITE) { + const u8 *buf = msg->buffer; + int i; + for (i = 0; i < msg->size; ++i) { + ret = cdns_mhdp_dpcd_write(mhdp, + msg->address + i, buf[i]); + if (!ret) + continue; + + DRM_DEV_ERROR(mhdp->dev, "Failed to write DPCD\n"); + + return ret; + } + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; + } + + if (msg->request == DP_AUX_NATIVE_READ) { + ret = cdns_mhdp_dpcd_read(mhdp, msg->address, msg->buffer, + msg->size); + if (ret < 0) + return -EIO; + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; + } + + if (((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_WRITE) + || ((msg->request & ~DP_AUX_I2C_MOT) == + DP_AUX_I2C_WRITE_STATUS_UPDATE)) { + + u8 i2c_status = 0u; + u16 respSize = 0u; + + ret = cdns_mhdp_i2c_write(mhdp, msg->address, + msg->buffer, + !!(msg->request & DP_AUX_I2C_MOT), + msg->size, &respSize); + + if (ret < 0) { + dev_err(aux->dev, "cdns_mhdp_i2c_write status %d\n", + ret); + return -EIO; + } + + ret = cdns_mhdp_get_last_i2c_status(mhdp, &i2c_status); + if (ret < 0) { + dev_err(aux->dev, + "cdns_mhdp_get_last_i2c_status status %d\n", + ret); + return -EIO; + } + + switch (i2c_status) { + case 0u: + msg->reply = DP_AUX_I2C_REPLY_ACK; + break; + case 1u: + msg->reply = DP_AUX_I2C_REPLY_NACK; + break; + case 2u: + msg->reply = DP_AUX_I2C_REPLY_DEFER; + break; + default: + msg->reply = DP_AUX_I2C_REPLY_NACK; + break; + } + + return respSize; + } + + if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_READ) { + + u8 i2c_status = 0u; + u16 respSize = 0u; + + ret = cdns_mhdp_i2c_read(mhdp, msg->address, msg->buffer, + msg->size, + !!(msg->request & DP_AUX_I2C_MOT), + &respSize); + if (ret < 0) + return -EIO; + + ret = cdns_mhdp_get_last_i2c_status(mhdp, &i2c_status); + + if (ret < 0) { + dev_err(aux->dev, + "cdns_mhdp_get_last_i2c_status ret %d\n", ret); + return -EIO; + } + + switch (i2c_status) { + case 0u: + msg->reply = DP_AUX_I2C_REPLY_ACK; + break; + case 1u: + msg->reply = DP_AUX_I2C_REPLY_NACK; + break; + case 2u: + msg->reply = DP_AUX_I2C_REPLY_DEFER; + break; + default: + msg->reply = DP_AUX_I2C_REPLY_NACK; + break; + } + + return respSize; + } + + return 0; +} + +static int dp_aux_init(struct cdns_mhdp_device *mhdp, + struct device *dev) +{ + int ret; + + mhdp->dp.aux.name = "imx_dp_aux"; + mhdp->dp.aux.dev = dev; + mhdp->dp.aux.drm_dev = mhdp->drm_dev; + mhdp->dp.aux.transfer = dp_aux_transfer; + + ret = drm_dp_aux_register(&mhdp->dp.aux); + + return ret; +} + +static int dp_aux_destroy(struct cdns_mhdp_device *mhdp) +{ + drm_dp_aux_unregister(&mhdp->dp.aux); + return 0; +} + +static void dp_pixel_clk_reset(struct cdns_mhdp_device *mhdp) +{ + u32 val; + + /* reset pixel clk */ + val = cdns_mhdp_reg_read(mhdp, SOURCE_HDTX_CAR); + cdns_mhdp_reg_write(mhdp, SOURCE_HDTX_CAR, val & 0xFD); + cdns_mhdp_reg_write(mhdp, SOURCE_HDTX_CAR, val); +} + +static void cdns_dp_mode_set(struct cdns_mhdp_device *mhdp) +{ + u32 lane_mapping = mhdp->lane_mapping; + int ret; + + cdns_mhdp_plat_call(mhdp, pclk_rate); + + /* delay for DP FW stable after pixel clock relock */ + msleep(50); + + dp_pixel_clk_reset(mhdp); + + /* Get DP Caps */ + ret = drm_dp_dpcd_read(&mhdp->dp.aux, DP_DPCD_REV, mhdp->dp.dpcd, + DP_RECEIVER_CAP_SIZE); + if (ret < 0) { + DRM_ERROR("Failed to get caps %d\n", ret); + return; + } + + mhdp->dp.rate = drm_dp_max_link_rate(mhdp->dp.dpcd); + mhdp->dp.num_lanes = drm_dp_max_lane_count(mhdp->dp.dpcd); + mhdp->dp.link_training_type = DP_TX_FULL_LINK_TRAINING; + + /* check the max link rate */ + if (mhdp->dp.rate > CDNS_DP_MAX_LINK_RATE) + mhdp->dp.rate = CDNS_DP_MAX_LINK_RATE; + + /* Initialize link rate/num_lanes as panel max link rate/max_num_lanes */ + cdns_mhdp_plat_call(mhdp, phy_set); + + /* Video off */ + ret = cdns_mhdp_set_video_status(mhdp, CONTROL_VIDEO_IDLE); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed to valid video %d\n", ret); + return; + } + + /* Line swaping */ + cdns_mhdp_reg_write(mhdp, LANES_CONFIG, 0x00400000 | lane_mapping); + + /* Set DP host capability */ + ret = cdns_mhdp_set_host_cap(mhdp); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed to set host cap %d\n", ret); + return; + } + + ret = cdns_mhdp_config_video(mhdp); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed to config video %d\n", ret); + return; + } + + return; +} + +void cdns_dp_handle_hpd_irq(struct cdns_mhdp_device *mhdp) +{ + u8 status[6]; + + mutex_lock(&mhdp->lock); + cdns_mhdp_dpcd_read(mhdp, 0x200, &status[0], 6); + DRM_DEBUG_DRIVER("DPCD HPD IRQ STATUS: %08x\n", status[1]); + cdns_mhdp_dpcd_write(mhdp, 0x201, status[1]); + mutex_unlock(&mhdp->lock); +} + +/* ----------------------------------------------------------------------------- + * dp TX Setup + */ +static enum drm_connector_status +cdns_dp_connector_detect(struct drm_connector *connector, bool force) +{ + struct cdns_mhdp_device *mhdp = container_of(connector, + struct cdns_mhdp_device, connector.base); + u8 hpd = 0xf; + mutex_lock(&mhdp->lock); + hpd = cdns_mhdp_read_hpd(mhdp); + mutex_unlock(&mhdp->lock); + DRM_DEBUG_DRIVER("%s hpd = %d\n", __func__, hpd); + + if (mhdp->force_disconnected_sts && (hpd == 1)) { + DRM_DEBUG_DRIVER("Returning disconnect status until ready\n"); + return connector_status_disconnected; + } + if (hpd == 0) + /* Cable Disconnedted */ + return connector_status_disconnected; + else if (hpd == 3) { + /* Cable Connected and seen IRQ*/ + DRM_DEBUG_DRIVER("Warning! Missed HPD IRQ event seen\n"); + return connector_status_connected; + } else if (hpd == 1) + /* Cable Connected */ + return connector_status_connected; + + /* Cable status unknown */ + DRM_DEBUG_DRIVER("Unknown cable status, hdp=%u\n", hpd); + return connector_status_unknown; +} + +static int cdns_dp_connector_get_modes(struct drm_connector *connector) +{ + struct cdns_mhdp_device *mhdp = container_of(connector, + struct cdns_mhdp_device, connector.base); + int num_modes = 0; + struct edid *edid; + + edid = drm_do_get_edid(&mhdp->connector.base, + cdns_mhdp_get_edid_block, mhdp); + if (edid) { + DRM_DEBUG_DRIVER("%x,%x,%x,%x,%x,%x,%x,%x\n", + edid->header[0], edid->header[1], + edid->header[2], edid->header[3], + edid->header[4], edid->header[5], + edid->header[6], edid->header[7]); + drm_connector_update_edid_property(connector, edid); + num_modes = drm_add_edid_modes(connector, edid); + kfree(edid); + } + + if (num_modes == 0) + DRM_ERROR("Invalid edid\n"); + return num_modes; +} + +static bool blob_equal(const struct drm_property_blob *a, + const struct drm_property_blob *b) +{ + if (a && b) + return a->length == b->length && + !memcmp(a->data, b->data, a->length); + + return !a == !b; +} + +static int cdns_dp_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct drm_connector_state *new_con_state = + drm_atomic_get_new_connector_state(state, connector); + struct drm_connector_state *old_con_state = + drm_atomic_get_old_connector_state(state, connector); + struct drm_crtc *crtc = new_con_state->crtc; + struct drm_crtc_state *new_crtc_state; + + cdns_hdcp_atomic_check(connector, old_con_state, new_con_state); + if (!new_con_state->crtc) + return 0; + + new_crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(new_crtc_state)) + return PTR_ERR(new_crtc_state); + + if (!blob_equal(new_con_state->hdr_output_metadata, + old_con_state->hdr_output_metadata) || + new_con_state->colorspace != old_con_state->colorspace) { + + new_crtc_state->mode_changed = + !new_con_state->hdr_output_metadata || + !old_con_state->hdr_output_metadata || + new_con_state->colorspace != old_con_state->colorspace; + } + + /* + * These properties are handled by fastset, and might not end up in a + * modeset. + */ + if (new_con_state->picture_aspect_ratio != + old_con_state->picture_aspect_ratio || + new_con_state->content_type != old_con_state->content_type || + new_con_state->scaling_mode != old_con_state->scaling_mode) + new_crtc_state->mode_changed = true; + return 0; +} + +static const struct drm_connector_funcs cdns_dp_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = cdns_dp_connector_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_connector_helper_funcs cdns_dp_connector_helper_funcs = { + .get_modes = cdns_dp_connector_get_modes, + .atomic_check = cdns_dp_connector_atomic_check, +}; + +static int cdns_dp_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct cdns_mhdp_device *mhdp = bridge->driver_private; + struct drm_encoder *encoder = bridge->encoder; + struct drm_connector *connector = &mhdp->connector.base; + int ret; + + connector->interlace_allowed = 1; + + if (mhdp->is_hpd) + connector->polled = DRM_CONNECTOR_POLL_HPD; + else + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + drm_connector_helper_add(connector, &cdns_dp_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, &cdns_dp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret < 0) { + DRM_ERROR("Failed to initialize connector\n"); + return ret; + } + + drm_connector_attach_encoder(connector, encoder); + + drm_connector_attach_content_protection_property(connector, true); + + return 0; +} + +static enum drm_mode_status +cdns_dp_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + enum drm_mode_status mode_status = MODE_OK; + + /* We don't support double-clocked modes */ + if (mode->flags & DRM_MODE_FLAG_DBLCLK || + mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_BAD; + + /* MAX support pixel clock rate 594MHz */ + if (mode->clock > 594000) + return MODE_CLOCK_HIGH; + + /* 5120 x 2160 is the maximum supported resulution */ + if (mode->hdisplay > 5120) + return MODE_BAD_HVALUE; + + if (mode->vdisplay > 2160) + return MODE_BAD_VVALUE; + + return mode_status; +} + +static void cdns_dp_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *orig_mode, + const struct drm_display_mode *mode) +{ + struct cdns_mhdp_device *mhdp = bridge->driver_private; + struct drm_display_info *display_info = &mhdp->connector.base.display_info; + struct video_info *video = &mhdp->video_info; + + switch (display_info->bpc) { + case 10: + video->color_depth = 10; + break; + case 6: + video->color_depth = 6; + break; + default: + video->color_depth = 8; + break; + } + + video->color_fmt = PXL_RGB; + video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC); + video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC); + + DRM_DEBUG_DRIVER("Mode: %dx%dp%d\n", mode->hdisplay, mode->vdisplay, + mode->clock); + memcpy(&mhdp->mode, mode, sizeof(struct drm_display_mode)); + + mutex_lock(&mhdp->lock); + cdns_dp_mode_set(mhdp); + mutex_unlock(&mhdp->lock); + + /* reset force mode set flag */ + mhdp->force_mode_set = false; +} + +static void cdn_dp_bridge_enable(struct drm_bridge *bridge) +{ + struct cdns_mhdp_device *mhdp = bridge->driver_private; + struct drm_connector_state *conn_state = mhdp->connector.base.state; + int ret; + + mutex_lock(&mhdp->lock); + /* Link trainning */ + ret = cdns_mhdp_train_link(mhdp); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed link train %d\n", ret); + mutex_unlock(&mhdp->lock); + return; + } + mutex_unlock(&mhdp->lock); + + ret = cdns_mhdp_set_video_status(mhdp, CONTROL_VIDEO_VALID); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed to valid video %d\n", ret); + return; + } + + if (conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_DESIRED) + cdns_hdcp_enable(mhdp); + +} + +static void cdn_dp_bridge_disable(struct drm_bridge *bridge) +{ + struct cdns_mhdp_device *mhdp = bridge->driver_private; + + cdns_hdcp_disable(mhdp); + + cdns_mhdp_set_video_status(mhdp, CONTROL_VIDEO_IDLE); +} + +static const struct drm_bridge_funcs cdns_dp_bridge_funcs = { + .attach = cdns_dp_bridge_attach, + .enable = cdn_dp_bridge_enable, + .disable = cdn_dp_bridge_disable, + .mode_set = cdns_dp_bridge_mode_set, + .mode_valid = cdns_dp_bridge_mode_valid, +}; + +static void hotplug_work_func(struct work_struct *work) +{ + struct cdns_mhdp_device *mhdp = container_of(work, + struct cdns_mhdp_device, + hotplug_work.work); + struct drm_connector *connector = &mhdp->connector.base; + enum drm_connector_status connector_sts; + int loop_cnt, retry; + + DRM_DEBUG_DRIVER("Starting %s\n", __func__); + + if (connector->status == connector_status_connected) { + /* Need to check if we had an IRQ event */ + u8 hpd = 0xf; + + mutex_lock(&mhdp->lock); + hpd = cdns_mhdp_read_hpd(mhdp); + mutex_unlock(&mhdp->lock); + DRM_DEBUG_DRIVER("cdns_mhdp_read_hpd = %d\n", hpd); + if (hpd == 3) { + /* Cable Connected and seen IRQ*/ + DRM_DEBUG_DRIVER("HPD IRQ event seen\n"); + cdns_dp_handle_hpd_irq(mhdp); + connector_sts = connector_status_connected; + } else if (hpd == 1) + connector_sts = connector_status_connected; + else if (hpd == 0) + connector_sts = connector_status_disconnected; + else + connector_sts = connector_status_unknown; + + /* if was connected then there may have been unplug event, + * either wait 900us then call cdns_dp_connector_detect and if + * still connected then just ignore and finish or poll a + * certain number of times. Otherwise set status to unconnected + * and call the hotplug_event function. + */ + for (loop_cnt = 0; loop_cnt < CDNS_DP_HPD_POLL_DWN_LOOP; + loop_cnt++) { + udelay(CDNS_DP_HPD_POLL_DWN_DLY_US); + if (loop_cnt > 0) + connector_sts = + cdns_dp_connector_detect(connector, false); + if (connector_sts != connector_status_connected) { + DRM_DEBUG_DRIVER("HDMI/DP Cable Plug Out\n"); + break; + } + } + if (connector_sts == connector_status_connected) { + DRM_DEBUG_DRIVER("Finished %s - early\n", __func__); + return; + } + /* Disable HDCP when cable plugout, + * set content_protection to DESIRED, recovery HDCP state after cable plugin + */ + if (connector->state->content_protection + != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) { + connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + mhdp->hdcp.state = HDCP_STATE_DISABLING; + } + DRM_DEBUG_DRIVER("Calling drm_kms_helper_hotplug_event\n"); + /* Note that before we call the helper functions we need + * to force the cdns_dp_connector_detect function from + * returning a connected status since the DRM functions + * still end up calling that in a roundabout way when we + * signal a status change. We need to do this to ensure + * that a shutdown proper completes and don't end up in + * a strange state. + */ + mhdp->force_disconnected_sts = true; + connector->status = connector_sts; + drm_kms_helper_hotplug_event(connector->dev); + /* There was a disconnection so give some time to allow + * things to clean up + */ + DRM_DEBUG_DRIVER("Start sleep\n"); + msleep(75); + DRM_DEBUG_DRIVER("End sleep\n"); + mhdp->force_disconnected_sts = false; + } else { + /* Disconnected or unknown status */ + /* if was disconnected possibly a connect event, call + * cdns_dp_connector_detect multiple times to validate before + * updating to connected status and calling the hotplug event + * function. This is needed since the HW/FW can take some time + * to validate. + */ + for (loop_cnt = 0; loop_cnt < CDNS_DP_HPD_POLL_UP_LOOP; + loop_cnt++) { + msleep(CDNS_DP_HPD_POLL_UP_DLY_US / 1000); + connector_sts = + cdns_dp_connector_detect(connector, false); + if (connector_sts == connector_status_connected) { + DRM_DEBUG_DRIVER("HDMI/DP Cable Plug In\n"); + /* Recovery HDCP state */ + if (connector->state->content_protection + != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) + mhdp->hdcp.state = HDCP_STATE_ENABLING; + break; + } + } + if (connector_sts == connector_status_connected) { + DRM_DEBUG_DRIVER("Calling drm_kms_helper_hotplug_event\n"); + connector->status = connector_sts; + drm_kms_helper_hotplug_event(connector->dev); + } + } + + /* check connection status one more time in case there had been a short + * disconnect that might have caused re-connect to be missed, if so + * schedule again. + */ + retry = 1; + do { + connector_sts = cdns_dp_connector_detect(connector, false); + if (connector_sts != connector->status) { + DRM_DEBUG_DRIVER("Re-queuing work_func due to possible change\n"); + queue_delayed_work(system_wq, &mhdp->hotplug_work, + usecs_to_jiffies(50)); + break; + } + retry--; + } while (retry > 0); + DRM_DEBUG_DRIVER("Finished %s\n", __func__); +} + +static irqreturn_t cdns_dp_irq_thread(int irq, void *data) +{ + struct cdns_mhdp_device *mhdp = data; + + disable_irq_nosync(irq); + + /* Need to handle the enable HERE */ + if (irq == mhdp->irq[IRQ_IN]) { + DRM_DEBUG_DRIVER("HDMI/DP IRQ IN\n"); + enable_irq(mhdp->irq[IRQ_OUT]); + } else if (irq == mhdp->irq[IRQ_OUT]) { + DRM_DEBUG_DRIVER("HDMI/DP IRQ OUT\n"); + enable_irq(mhdp->irq[IRQ_IN]); + } + + /* Queue job as long as not already in queue */ + queue_delayed_work(system_wq, &mhdp->hotplug_work, + usecs_to_jiffies(5)); + + return IRQ_HANDLED; +} + +static void cdns_dp_parse_dt(struct cdns_mhdp_device *mhdp) +{ + struct device_node *of_node = mhdp->dev->of_node; + int ret; + + ret = of_property_read_u32(of_node, "lane-mapping", + &mhdp->lane_mapping); + if (ret) { + mhdp->lane_mapping = 0xc6; + dev_warn(mhdp->dev, "Failed to get lane_mapping - using default 0xc6\n"); + } + dev_info(mhdp->dev, "lane-mapping 0x%02x\n", mhdp->lane_mapping); +} + +static int __cdns_dp_probe(struct platform_device *pdev, + struct cdns_mhdp_device *mhdp) +{ + struct device *dev = &pdev->dev; + struct resource *iores = NULL; + int ret; + + mutex_init(&mhdp->lock); + mutex_init(&mhdp->api_lock); + mutex_init(&mhdp->iolock); + + INIT_DELAYED_WORK(&mhdp->hotplug_work, hotplug_work_func); + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (iores) { + mhdp->regs_base = devm_ioremap(dev, iores->start, + resource_size(iores)); + if (IS_ERR(mhdp->regs_base)) + return -ENOMEM; + } + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (iores) { + mhdp->regs_sec = devm_ioremap(dev, iores->start, + resource_size(iores)); + if (IS_ERR(mhdp->regs_sec)) + return -ENOMEM; + } + + mhdp->is_hpd = true; + mhdp->is_dp = true; + mhdp->is_ls1028a = false; + + mhdp->irq[IRQ_IN] = platform_get_irq_byname(pdev, "plug_in"); + if (mhdp->irq[IRQ_IN] < 0) { + mhdp->is_hpd = false; + dev_info(dev, "No plug_in irq number\n"); + } + + mhdp->irq[IRQ_OUT] = platform_get_irq_byname(pdev, "plug_out"); + if (mhdp->irq[IRQ_OUT] < 0) { + mhdp->is_hpd = false; + dev_info(dev, "No plug_out irq number\n"); + } + + cdns_dp_parse_dt(mhdp); + + cnds_hdcp_create_device_files(mhdp); + + if (of_device_is_compatible(dev->of_node, "cdn,ls1028a-dp")) + mhdp->is_ls1028a = true; + + cdns_mhdp_plat_call(mhdp, power_on); + + cdns_mhdp_plat_call(mhdp, firmware_init); + + /* DP FW alive check */ + ret = cdns_mhdp_check_alive(mhdp); + if (ret == false) { + DRM_ERROR("NO dp FW running\n"); + return -ENXIO; + } + + ret = cdns_hdcp_init(mhdp, pdev->dev.of_node); + if (ret < 0) + DRM_WARN("Failed to initialize HDCP\n"); + + /* DP PHY init before AUX init */ + cdns_mhdp_plat_call(mhdp, phy_set); + + /* Enable Hotplug Detect IRQ thread */ + if (mhdp->is_hpd) { + irq_set_status_flags(mhdp->irq[IRQ_IN], IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_IN], + NULL, cdns_dp_irq_thread, + IRQF_ONESHOT, dev_name(dev), + mhdp); + + if (ret) { + dev_err(dev, "can't claim irq %d\n", + mhdp->irq[IRQ_IN]); + return -EINVAL; + } + + irq_set_status_flags(mhdp->irq[IRQ_OUT], IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_OUT], + NULL, cdns_dp_irq_thread, + IRQF_ONESHOT, dev_name(dev), + mhdp); + + if (ret) { + dev_err(dev, "can't claim irq %d\n", + mhdp->irq[IRQ_OUT]); + return -EINVAL; + } + + // Call to clear any latched values first... + cdns_mhdp_read_hpd(mhdp); + if (cdns_mhdp_read_hpd(mhdp)) + enable_irq(mhdp->irq[IRQ_OUT]); + else + enable_irq(mhdp->irq[IRQ_IN]); + } + + mhdp->bridge.base.driver_private = mhdp; + mhdp->bridge.base.funcs = &cdns_dp_bridge_funcs; +#ifdef CONFIG_OF + mhdp->bridge.base.of_node = dev->of_node; +#endif + + dev_set_drvdata(dev, mhdp); + + /* register audio driver */ + cdns_mhdp_register_audio_driver(dev); + + dp_aux_init(mhdp, dev); + + return 0; +} + +static void __cdns_dp_remove(struct cdns_mhdp_device *mhdp) +{ + dp_aux_destroy(mhdp); + cdns_mhdp_unregister_audio_driver(mhdp->dev); +} + +/* ----------------------------------------------------------------------------- + * Probe/remove API, used from platforms based on the DRM bridge API. + */ +int cdns_dp_probe(struct platform_device *pdev, + struct cdns_mhdp_device *mhdp) +{ + int ret; + + ret = __cdns_dp_probe(pdev, mhdp); + if (ret) + return ret; + + drm_bridge_add(&mhdp->bridge.base); + + return 0; +} +EXPORT_SYMBOL_GPL(cdns_dp_probe); + +void cdns_dp_remove(struct platform_device *pdev) +{ + struct cdns_mhdp_device *mhdp = platform_get_drvdata(pdev); + + drm_bridge_remove(&mhdp->bridge.base); + + __cdns_dp_remove(mhdp); +} +EXPORT_SYMBOL_GPL(cdns_dp_remove); + +/* ----------------------------------------------------------------------------- + * Bind/unbind API, used from platforms based on the component framework. + */ +int cdns_dp_bind(struct platform_device *pdev, struct drm_encoder *encoder, + struct cdns_mhdp_device *mhdp) +{ + int ret; + + ret = __cdns_dp_probe(pdev, mhdp); + if (ret < 0) + return ret; + + ret = drm_bridge_attach(encoder, &mhdp->bridge.base, NULL, 0); + if (ret) { + cdns_dp_remove(pdev); + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cdns_dp_bind); + +void cdns_dp_unbind(struct device *dev) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + + __cdns_dp_remove(mhdp); +} +EXPORT_SYMBOL_GPL(cdns_dp_unbind); + +MODULE_AUTHOR("Sandor Yu <sandor.yu@nxp.com>"); +MODULE_DESCRIPTION("Cadence Display Port transmitter driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cdn-dp"); diff --git a/drivers/gpu/drm/bridge/cadence/cdns-hdcp-common.c b/drivers/gpu/drm/bridge/cadence/cdns-hdcp-common.c new file mode 100644 index 000000000000..73dace56bac9 --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-hdcp-common.c @@ -0,0 +1,1323 @@ +/* + * Cadence HDMI/DP HDCP driver + * + * Copyright 2021 NXP + * + * 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. + * + */ +#include <drm/bridge/cdns-mhdp.h> +#include <drm/drm_hdcp.h> +#include <drm/drm_print.h> +#include <linux/firmware.h> + +#include "cdns-mhdp-hdcp.h" + +/* Default will be to use KM unless it has been explicitly */ +#ifndef HDCP_USE_KMKEY + #define HDCP_USE_KMKEY 1 +#endif + +#define CDNS_HDCP_ACTIVATE (0x1 << 2) + +#define IMX_FW_TIMEOUT_MS (64 * 1000) +#define IMX_HDCP_PAIRING_FIRMWARE "imx/hdcp-pairing.bin" + +#define GENERAL_BUS_SETTINGS_DPCD_BUS_BIT 0 +#define GENERAL_BUS_SETTINGS_DPCD_BUS_LOCK_BIT 1 +#define GENERAL_BUS_SETTINGS_HDCP_BUS_BIT 2 +#define GENERAL_BUS_SETTINGS_HDCP_BUS_LOCK_BIT 3 +#define GENERAL_BUS_SETTINGS_CAPB_OWNER_BIT 4 +#define GENERAL_BUS_SETTINGS_CAPB_OWNER_LOCK_BIT 5 + +#define GENERAL_BUS_SETTINGS_RESP_DPCD_BUS_BIT 0 +#define GENERAL_BUS_SETTINGS_RESP_HDCP_BUS_BIT 1 +#define GENERAL_BUS_SETTINGS_RESP_CAPB_OWNER_BIT 2 + +/* HDCP TX ports working mode (HDCP 2.2 or 1.4) */ +enum { + HDCP_TX_2, /* lock only with HDCP2 */ + HDCP_TX_1, /* lock only with HDCP1 */ + HDCP_TX_BOTH, /* lock on HDCP2 or 1 depend on other side */ +}; + +/* HDCP TX ports stream type (relevant if receiver is repeater) */ +enum { + HDCP_CONTENT_TYPE_0, /* May be transmitted by + The HDCP Repeater to all HDCP Devices. */ + HDCP_CONTENT_TYPE_1, /* Must not be transmitted by the HDCP Repeater to + HDCP 1.x-compliant Devices and HDCP 2.0-compliant Repeaters */ +}; + +/* different error types for HDCP_TX_STATUS_CHANGE */ +enum { + HDCP_TRAN_ERR_NO_ERROR, + HDCP_TRAN_ERR_HPD_IS_DOWN, + HDCP_TRAN_ERR_SRM_FAILURE, + HDCP_TRAN_ERR_SIGNATURE_VERIFICATION, + HDCP_TRAN_ERR_H_TAG_DIFF_H, + HDCP_TRAN_ERR_V_TAG_DIFF_V, + HDCP_TRAN_ERR_LOCALITY_CHECK, + HDCP_TRAN_ERR_DDC, + HDCP_TRAN_ERR_REAUTH_REQ, + HDCP_TRAN_ERR_TOPOLOGY, + HDCP_TRAN_ERR_HDCP_RSVD1, + HDCP_TRAN_ERR_HDMI_CAPABILITY, + HDCP_TRAN_ERR_RI, + HDCP_TRAN_ERR_WATCHDOG_EXPIRED, +}; + +static char const *g_last_error[16] = { + "No Error", + "HPD is down", + "SRM failure", + "Signature verification error", + "h tag != h", + "V tag diff v", + "Locality check", + "DDC error", + "REAUTH_REQ", + "Topology error", + "Verify receiver ID list failed", + "HDCP_RSVD1 was not 0,0,0", + "HDMI capability or mode", + "RI result was different than expected", + "WatchDog expired", + "Repeater integrity failed" +}; + +#define HDCP_MAX_RECEIVERS 32 +#define HDCP_RECEIVER_ID_SIZE_BYTES 5 +#define HPD_EVENT 1 +#define HDCP_STATUS_SIZE 0x5 +#define HDCP_PORT_STS_AUTH 0x1 +#define HDCP_PORT_STS_REPEATER 0x2 +#define HDCP_PORT_STS_TYPE_MASK 0xc +#define HDCP_PORT_STS_TYPE_SHIFT 0x2 +#define HDCP_PORT_STS_AUTH_STREAM_ID_SHIFT 0x4 +#define HDCP_PORT_STS_AUTH_STREAM_ID_MASK 0x10 +#define HDCP_PORT_STS_LAST_ERR_SHIFT 0x5 +#define HDCP_PORT_STS_LAST_ERR_MASK (0x0F << 5) +#define GET_HDCP_PORT_STS_LAST_ERR(__sts__) \ + (((__sts__) & HDCP_PORT_STS_LAST_ERR_MASK) >> \ + HDCP_PORT_STS_LAST_ERR_SHIFT) +#define HDCP_PORT_STS_1_1_FEATURES 0x200 + +#define HDCP_CONFIG_NONE ((u8) 0) +#define HDCP_CONFIG_1_4 ((u8) 1) /* use HDCP 1.4 only */ +#define HDCP_CONFIG_2_2 ((u8) 2) /* use HDCP 2.2 only */ + +/* Default timeout to use for wait4event in milliseconds */ +#define HDCP_EVENT_TO_DEF 800 +/* Timeout value to use for repeater receiver ID check, spec says 3s */ +#define HDCP_EVENT_TO_RPT 3500 + +static int cdns_hdcp_check_link(struct cdns_mhdp_device *mhdp); + +static void print_port_status(u16 sts) +{ + char const *rx_type[4] = { "Unknown", "HDCP 1", "HDCP 2", "Unknown" }; + + DRM_DEBUG_KMS("INFO: HDCP Port Status: 0x%04x\n", sts); + DRM_DEBUG_KMS(" Authenticated: %d\n", sts & HDCP_PORT_STS_AUTH); + DRM_DEBUG_KMS(" Receiver is repeater: %d\n", sts & HDCP_PORT_STS_REPEATER); + DRM_DEBUG_KMS(" RX Type: %s\n", + rx_type[(sts & HDCP_PORT_STS_TYPE_MASK) >> HDCP_PORT_STS_TYPE_SHIFT]); + DRM_DEBUG_KMS(" AuthStreamId: %d\n", sts & HDCP_PORT_STS_AUTH_STREAM_ID_MASK); + DRM_DEBUG_KMS(" Last Error: %s\n", + g_last_error[(sts & HDCP_PORT_STS_LAST_ERR_MASK) >> HDCP_PORT_STS_LAST_ERR_SHIFT]); + DRM_DEBUG_KMS(" Enable 1.1 Features: %d\n", sts & HDCP_PORT_STS_1_1_FEATURES); +} + +static void print_events(u8 events) +{ + if (events & HDMI_TX_HPD_EVENT) + DRM_INFO("INFO: HDMI_TX_HPD_EVENT\n"); + if (events & HDCPTX_STATUS_EVENT) + DRM_INFO("INFO: HDCPTX_STATUS_EVENT\n"); + if (events & HDCPTX_IS_KM_STORED_EVENT) + DRM_INFO("INFO: HDCPTX_IS_KM_STORED_EVENT\n"); + if (events & HDCPTX_STORE_KM_EVENT) + DRM_INFO("INFO: HDCPTX_STORE_KM_EVENT\n"); + if (events & HDCPTX_IS_RECEIVER_ID_VALID_EVENT) + DRM_INFO("INFO: HDCPTX_IS_RECEIVER_ID_VALID_EVENT\n"); +} + +static u8 wait4event(struct cdns_mhdp_device *mhdp, u8 *events, + u32 event_to_wait, u32 timeout_ms) +{ + u8 reg_events; + u8 returned_events; + u8 event_mask = event_to_wait | HDCPTX_STATUS_EVENT; + unsigned timeout; + + timeout = timeout_ms; + do { + if (timeout == 0) + goto timeout_err; + timeout--; + udelay(1000); + reg_events = cdns_mhdp_get_event(mhdp); + *events |= reg_events; + } while (((event_mask & *events) == 0) && (event_to_wait > HDMI_TX_HPD_EVENT)); + + returned_events = *events & event_mask; + if (*events != returned_events) { + u32 unexpected_events = ~event_mask & *events; + + DRM_INFO("INFO: %s() all 0x%08x expected 0x%08x unexpected 0x%08x", + __func__, *events, returned_events, unexpected_events); + DRM_INFO("INFO: %s() All events:\n", __func__); + print_events(*events); + + DRM_INFO("INFO: %s() expected events:\n", __func__); + print_events(returned_events); + + DRM_INFO("INFO: %s() unexpected events:\n", __func__); + print_events(unexpected_events); + } else + print_events(*events); + + *events &= ~event_mask; + + return returned_events; + +timeout_err: + DRM_INFO("INFO: %s() Timed out with events:\n", __func__); + print_events(event_to_wait); + return 0; +} + +static u16 cdns_hdcp_get_status(struct cdns_mhdp_device *mhdp) +{ + u8 hdcp_status[HDCP_STATUS_SIZE]; + u16 hdcp_port_status; + + cdns_mhdp_hdcp_tx_status_req(mhdp, hdcp_status, HDCP_STATUS_SIZE); + hdcp_port_status = (hdcp_status[0] << 8) | hdcp_status[1]; + + return hdcp_port_status; +} + +static inline u8 check_event(u8 events, u8 tested) +{ + if ((events & tested) == 0) + return 0; + return 1; +} + +/* Prints status. Returns error code (0 = no error) */ +static u8 cdns_hdcp_handle_status(u16 status) +{ + print_port_status(status); + if (status & HDCP_PORT_STS_LAST_ERR_MASK) + DRM_ERROR("ERROR: HDCP error was set to %s\n", + g_last_error[((status & HDCP_PORT_STS_LAST_ERR_MASK) + >> HDCP_PORT_STS_LAST_ERR_SHIFT)]); + return GET_HDCP_PORT_STS_LAST_ERR(status); +} + +static int cdns_hdcp_set_config(struct cdns_mhdp_device *mhdp, u8 hdcp_config) +{ + u8 bus_config, retEvents; + u16 hdcp_port_status; + int ret; + + /* Clearing out existing events */ + wait4event(mhdp, &mhdp->hdcp.events, HDMI_TX_HPD_EVENT, HDCP_EVENT_TO_DEF); + mhdp->hdcp.events = 0; + + if (!strncmp("imx8mq-hdmi", mhdp->plat_data->plat_name, 11)) { + DRM_DEBUG_KMS("INFO: Switching HDCP Commands to SAPB.\n"); + bus_config = (1 << GENERAL_BUS_SETTINGS_HDCP_BUS_BIT); + ret = cdns_mhdp_apb_conf(mhdp, bus_config); + if (ret) { + DRM_ERROR("Failed to set APB configuration.\n"); + if (ret & (1 << GENERAL_BUS_SETTINGS_RESP_HDCP_BUS_BIT))/* 1 - locked */ + DRM_ERROR("Failed to switch HDCP to SAPB Mailbox\n"); + return -1; + } + DRM_DEBUG_KMS("INFO: HDCP switched to SAPB\n"); + } + + /* HDCP 2.2(and/or 1.4) | activate | km-key | 0 */ + hdcp_config |= CDNS_HDCP_ACTIVATE | (HDCP_USE_KMKEY << 4) | (HDCP_CONTENT_TYPE_0 << 3); + + DRM_DEBUG_KMS("INFO: Enabling HDCP...\n"); + ret = cdns_mhdp_hdcp_tx_config(mhdp, hdcp_config); + if (ret < 0) + DRM_DEBUG_KMS("cdns_mhdp_hdcp_tx_config failed\n"); + + /* Wait until HDCP_TX_STATUS EVENT appears */ + DRM_DEBUG_KMS("INFO: wait4event -> HDCPTX_STATUS_EVENT\n"); + retEvents = wait4event(mhdp, &mhdp->hdcp.events, HDCPTX_STATUS_EVENT, HDCP_EVENT_TO_DEF); + + /* Set TX STATUS REQUEST */ + DRM_DEBUG_KMS("INFO: Getting port status\n"); + hdcp_port_status = cdns_hdcp_get_status(mhdp); + if (cdns_hdcp_handle_status(hdcp_port_status) != 0) + return -1; + + return 0; +} + +static int cdns_hdcp_auth_check(struct cdns_mhdp_device *mhdp) +{ + u16 hdcp_port_status; + int ret; + + DRM_DEBUG_KMS("INFO: wait4event -> HDCPTX_STATUS_EVENT\n"); + mhdp->hdcp.events = wait4event(mhdp, &mhdp->hdcp.events, HDCPTX_STATUS_EVENT, HDCP_EVENT_TO_DEF+HDCP_EVENT_TO_DEF); + if (mhdp->hdcp.events == 0) + return -1; + + DRM_DEBUG_KMS("HDCP: HDCPTX_STATUS_EVENT\n"); + hdcp_port_status = cdns_hdcp_get_status(mhdp); + ret = cdns_hdcp_handle_status(hdcp_port_status); + if (ret != 0) { + if (ret == HDCP_TRAN_ERR_REAUTH_REQ) { + DRM_ERROR("HDCP_TRAN_ERR_REAUTH_REQ-->one more try!\n"); + return 1; + } else + return -1; + } + + if (hdcp_port_status & HDCP_PORT_STS_AUTH) { + DRM_INFO("Authentication completed successfully!\n"); + /* Dump hdmi and phy register */ + mhdp->hdcp.state = HDCP_STATE_AUTHENTICATED; + mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_ENABLED; + schedule_work(&mhdp->hdcp.prop_work); + return 0; + } + + DRM_WARN("Authentication failed\n"); + mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED; + return -1; +} + +inline void cdns_hdcp_swap_id(u8 *in, u8 *out) +{ + int i; + + for (i = 0; i < HDCP_RECEIVER_ID_SIZE_BYTES; i++) + out[HDCP_RECEIVER_ID_SIZE_BYTES - (i + 1)] = in[i]; +} + +inline void cdns_hdcp_swap_list(u8 *list_in, u8 *list_out, int num_ids) +{ + int i; + + for (i = 0; i < num_ids; i++) + cdns_hdcp_swap_id(&list_in[i * HDCP_RECEIVER_ID_SIZE_BYTES], + &list_out[i * HDCP_RECEIVER_ID_SIZE_BYTES]); +} + +static int cdns_hdcp_check_receviers(struct cdns_mhdp_device *mhdp) +{ + u8 ret_events; + u8 hdcp_num_rec, i; + u8 hdcp_rec_id[HDCP_MAX_RECEIVERS][HDCP_RECEIVER_ID_SIZE_BYTES]; + u8 hdcp_rec_id_temp[HDCP_MAX_RECEIVERS][HDCP_RECEIVER_ID_SIZE_BYTES]; + u16 hdcp_port_status = 0; + int ret; + + DRM_INFO("INFO: Waiting for Receiver ID valid event\n"); + ret_events = 0; + do { + u8 events = 0; + u8 hdcp_last_error = 0; + events = check_event(ret_events, + HDCPTX_IS_RECEIVER_ID_VALID_EVENT); + DRM_DEBUG_KMS("INFO: Waiting HDCPTX_IS_RECEIVER_ID_VALID_EVENT\n"); + ret_events = wait4event(mhdp, &mhdp->hdcp.events, + HDCPTX_IS_RECEIVER_ID_VALID_EVENT, + (mhdp->hdcp.sink_is_repeater ? + HDCP_EVENT_TO_RPT : HDCP_EVENT_TO_DEF)); + if (ret_events == 0) { + /* time out occurred, return error */ + DRM_ERROR("HDCP error did not get receiver IDs\n"); + return -1; + } + if (check_event(ret_events, HDCPTX_STATUS_EVENT) != 0) { + /* There was a status update, could be due to HPD + going down or some other error, check if an error + was set, if so exit. + */ + hdcp_port_status = cdns_hdcp_get_status(mhdp); + hdcp_last_error = GET_HDCP_PORT_STS_LAST_ERR(hdcp_port_status); + if (cdns_hdcp_handle_status(hdcp_port_status)) { + DRM_ERROR("HDCP error no: %u\n", hdcp_last_error); + return -1; + } else { + /* No error logged, keep going. + * If this somehow happened at same time, then need to + * put the HDCPTX_STATUS_EVENT back into the global + * events pool and checked later. */ + mhdp->hdcp.events |= HDCPTX_STATUS_EVENT; + + /* Special condition when connected to HDCP 1.4 repeater + * with no downstream devices attached, then will not + * get receiver ID list but instead will reach + * authenticated state. */ + if ((mhdp->hdcp.hdcp_version == HDCP_TX_1) && (mhdp->hdcp.sink_is_repeater == 1) && + ((hdcp_port_status & HDCP_PORT_STS_AUTH) == HDCP_PORT_STS_AUTH)) { + DRM_INFO("Connected to HDCP 1.4 repeater with no downstream devices!\n"); + return 0; + } + + msleep(20); + } + } + } while (check_event(ret_events, + HDCPTX_IS_RECEIVER_ID_VALID_EVENT) == 0); + + DRM_INFO("INFO: Requesting Receivers ID's\n"); + + hdcp_num_rec = 0; + memset(&hdcp_rec_id, 0, sizeof(hdcp_rec_id)); + + ret = cdns_mhdp_hdcp_tx_is_receiver_id_valid(mhdp, (u8 *)hdcp_rec_id, &hdcp_num_rec); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed to hdcp tx receiver ID.\n"); + return -1; + } + + if (hdcp_num_rec == 0) { + DRM_DEBUG_KMS("WARN: Failed to get receiver list\n"); + /* Unknown problem, return error */ + return -1; + } + + DRM_INFO("INFO: Number of Receivers: %d\n", hdcp_num_rec); + + for (i = 0; i < hdcp_num_rec; ++i) { + DRM_INFO("\tReceiver ID%2d: %.2X%.2X%.2X%.2X%.2X\n", + i, + hdcp_rec_id[i][0], + hdcp_rec_id[i][1], + hdcp_rec_id[i][2], + hdcp_rec_id[i][3], + hdcp_rec_id[i][4] + ); + } + + /* swap ids byte order */ + cdns_hdcp_swap_list(&hdcp_rec_id[0][0], + &hdcp_rec_id_temp[0][0], hdcp_num_rec); + + /* Check Receiver ID's against revocation list in SRM */ + if (drm_hdcp_check_ksvs_revoked(mhdp->drm_dev, (u8 *)hdcp_rec_id_temp, hdcp_num_rec)) { + mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED; + DRM_ERROR("INFO: Receiver check fails\n"); + return -1; + } + + ret = cdns_mhdp_hdcp_tx_respond_receiver_id_valid(mhdp, 1); + DRM_INFO("INFO: Responding with Receiver ID's OK!, ret=%d\n", ret); + return ret; +} + +#ifdef STORE_PAIRING +static int cdns_hdcp_get_stored_pairing(struct cdns_mhdp_device *mhdp) +{ + int ret = 0; + unsigned long timeout = jiffies + msecs_to_jiffies(IMX_FW_TIMEOUT_MS); + unsigned long sleep = 1000; + const struct firmware *fw; + + DRM_DEBUG_KMS("%s()\n", __func__); + + while (time_before(jiffies, timeout)) { + ret = request_firmware(&fw, cdns_hdcp_PAIRING_FIRMWARE, mhdp->dev); + if (ret == -ENOENT) { + msleep(sleep); + sleep *= 2; + continue; + } else if (ret) { + DRM_DEV_INFO(mhdp->dev, "HDCP pairing data not found\n"); + goto out; + } + + mhdp->hdcp.num_paired = fw->size / + sizeof(struct hdcp_trans_pairing_data); + if (mhdp->hdcp.num_paired > MAX_STORED_KM) { + /* todo: handle dropping */ + mhdp->hdcp.num_paired = MAX_STORED_KM; + DRM_DEV_INFO(mhdp->dev, + "too many paired receivers - dropping older entries\n"); + } + memcpy(&mhdp->hdcp.pairing[0], fw->data, + sizeof(struct hdcp_trans_pairing_data) * mhdp->hdcp.num_paired); + release_firmware(fw); + goto out; + } + + DRM_DEV_ERROR(mhdp->dev, "Timed out trying to load firmware\n"); + ret = -ETIMEDOUT; + out: + return ret; +} +#endif + +static int cdns_hdcp_find_km_store(struct cdns_mhdp_device *mhdp, + u8 receiver[HDCP_PAIRING_R_ID]) +{ + int i; + + DRM_DEBUG_KMS("%s()\n", __func__); + for (i = 0; i < mhdp->hdcp.num_paired; i++) { + if (memcmp(receiver, mhdp->hdcp.pairing[i].receiver_id, + HDCP_PAIRING_R_ID) == 0) { + DRM_INFO("HDCP: found receiver id: 0x%x%x%x%x%x\n", + receiver[0], receiver[1], receiver[2], receiver[3], receiver[4]); + return i; + } + } + DRM_INFO("HDCP: receiver id: 0x%x%x%x%x%x not stored\n", + receiver[0], receiver[1], receiver[2], receiver[3], receiver[4]); + return -1; +} + +static int cdns_hdcp_store_km(struct cdns_mhdp_device *mhdp, + struct hdcp_trans_pairing_data *pairing, + int stored_km_index) +{ + int i, temp_index; + struct hdcp_trans_pairing_data temp_pairing; + + DRM_DEBUG_KMS("%s()\n", __func__); + + if (stored_km_index < 0) { + /* drop one entry if array is full */ + if (mhdp->hdcp.num_paired == MAX_STORED_KM) + mhdp->hdcp.num_paired--; + + temp_index = mhdp->hdcp.num_paired; + mhdp->hdcp.num_paired++; + if (!pairing) { + DRM_ERROR("NULL HDCP pairing data!\n"); + return -1; + } else + /* save the new stored km */ + temp_pairing = *pairing; + } else { + /* save the current stored km */ + temp_index = stored_km_index; + temp_pairing = mhdp->hdcp.pairing[stored_km_index]; + } + + /* move entries one slot to the end */ + for (i = temp_index; i > 0; i--) + mhdp->hdcp.pairing[i] = mhdp->hdcp.pairing[i - 1]; + + /* save the current/new entry at the beginning */ + mhdp->hdcp.pairing[0] = temp_pairing; + + return 0; +} + +static inline int cdns_hdcp_auth_22(struct cdns_mhdp_device *mhdp) +{ + int km_idx = -1; + u8 retEvents; + u16 hdcp_port_status; + u8 resp[HDCP_STATUS_SIZE]; + struct hdcp_trans_pairing_data pairing; + int ret; + + DRM_DEBUG_KMS("HDCP: Start 2.2 Authentication\n"); + mhdp->hdcp.sink_is_repeater = 0; + + /* Wait until HDCP2_TX_IS_KM_STORED EVENT appears */ + retEvents = 0; + DRM_DEBUG_KMS("INFO: Wait until HDCP2_TX_IS_KM_STORED EVENT appears\n"); + while (check_event(retEvents, HDCPTX_IS_KM_STORED_EVENT) == 0) { + DRM_DEBUG_KMS("INFO: Waiting FOR _IS_KM_STORED EVENT\n"); + retEvents = wait4event(mhdp, &mhdp->hdcp.events, + HDCPTX_IS_KM_STORED_EVENT, HDCP_EVENT_TO_DEF); + if (retEvents == 0) + /* time out occurred, return error */ + return -1; + if (check_event(retEvents, HDCPTX_STATUS_EVENT) != 0) { + /* There was a status update, could be due to HPD + going down or some other error, check if an error + was set, if so exit. + */ + hdcp_port_status = cdns_hdcp_get_status(mhdp); + if (cdns_hdcp_handle_status(hdcp_port_status) != 0) + return -1; + } + } + + DRM_DEBUG_KMS("HDCP: HDCPTX_IS_KM_STORED_EVENT\n"); + + /* Set HDCP2 TX KM STORED REQUEST */ + ret = cdns_mhdp_hdcp2_tx_is_km_stored_req(mhdp, resp, HDCP_STATUS_SIZE); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed to hdcp2 tx km stored.\n"); + return -1; + } + + DRM_DEBUG_KMS("HDCP: CDN_API_HDCP2_TX_IS_KM_STORED_REQ_blocking\n"); + DRM_DEBUG_KMS("HDCP: Receiver ID: 0x%x%x%x%x%x\n", + resp[0], resp[1], resp[2], resp[3], resp[4]); + + km_idx = cdns_hdcp_find_km_store(mhdp, resp); + + /* Check if KM is stored */ + if (km_idx >= 0) { + DRM_DEBUG_KMS("INFO: KM is stored\n"); + /* Set HDCP2 TX RESPOND KM with stored KM */ + ret = cdns_mhdp_hdcp2_tx_respond_km(mhdp, (u8 *)&mhdp->hdcp.pairing[km_idx], + sizeof(struct hdcp_trans_pairing_data)); + + DRM_DEBUG_KMS("HDCP: CDN_API_HDCP2_TX_RESPOND_KM_blocking, ret=%d\n", ret); + } else { /* KM is not stored */ + /* Set HDCP2 TX RESPOND KM with empty data */ + ret = cdns_mhdp_hdcp2_tx_respond_km(mhdp, NULL, 0); + DRM_DEBUG_KMS("INFO: KM is not stored ret=%d\n", ret); + } + + if (cdns_hdcp_check_receviers(mhdp)) + return -1; + + /* Check if KM is not stored */ + if (km_idx < 0) { + int loop_cnt = 0; + + /* Wait until HDCP2_TX_STORE_KM EVENT appears */ + retEvents = 0; + DRM_DEBUG_KMS("INFO: wait4event -> HDCPTX_STORE_KM_EVENT\n"); + while (check_event(retEvents, HDCPTX_STORE_KM_EVENT) == 0) { + retEvents = wait4event(mhdp, &mhdp->hdcp.events, + HDCPTX_STORE_KM_EVENT, HDCP_EVENT_TO_DEF); + if (check_event(retEvents, HDCPTX_STATUS_EVENT) + != 0) { + hdcp_port_status = cdns_hdcp_get_status(mhdp); + if (cdns_hdcp_handle_status(hdcp_port_status) + != 0) + return -1; + } + if (loop_cnt > 2) { + DRM_ERROR("Did not get event HDCPTX_STORE_KM_EVENT in time\n"); + return -1; + } else + loop_cnt++; + } + DRM_DEBUG_KMS("HDCP: HDCPTX_STORE_KM_EVENT\n"); + + /* Set HDCP2_TX_STORE_KM REQUEST */ + ret = cdns_mhdp_hdcp2_tx_store_km(mhdp, (u8 *)&pairing, sizeof(struct hdcp_trans_pairing_data)); + DRM_DEBUG_KMS("HDCP: CDN_API_HDCP2_TX_STORE_KM_REQ_blocking ret=%d\n", ret); + cdns_hdcp_store_km(mhdp, &pairing, km_idx); + } else + cdns_hdcp_store_km(mhdp, NULL, km_idx); + + /* Check if device was a repeater */ + hdcp_port_status = cdns_hdcp_get_status(mhdp); + + /* Exit if there was any errors logged at this point... */ + if (GET_HDCP_PORT_STS_LAST_ERR(hdcp_port_status) > 0) { + cdns_hdcp_handle_status(hdcp_port_status); + return -1; + } + + if (hdcp_port_status & HDCP_PORT_STS_REPEATER) + mhdp->hdcp.sink_is_repeater = 1; + + /* If sink was a repeater, we will be getting additional IDs to validate... + * Note that this one may take some time since spec allows up to 3s... */ + if (mhdp->hdcp.sink_is_repeater) + if (cdns_hdcp_check_receviers(mhdp)) + return -1; + + /* Slight delay to allow firmware to finish setting up authenticated state */ + msleep(300); + + DRM_INFO("Finished cdns_hdcp_auth_22\n"); + return 0; +} + +static inline int cdns_hdcp_auth_14(struct cdns_mhdp_device *mhdp) +{ + u16 hdcp_port_status; + int ret = 0; + + DRM_DEBUG_KMS("HDCP: Starting 1.4 Authentication\n"); + mhdp->hdcp.sink_is_repeater = 0; + + ret = cdns_hdcp_check_receviers(mhdp); + if (ret) + return -1; + + /* Check if device was a repeater */ + hdcp_port_status = cdns_hdcp_get_status(mhdp); + + /* Exit if there was any errors logged at this point... */ + if (GET_HDCP_PORT_STS_LAST_ERR(hdcp_port_status) > 0) { + cdns_hdcp_handle_status(hdcp_port_status); + return -1; + } + + if (hdcp_port_status & HDCP_PORT_STS_REPEATER) { + DRM_INFO("Connected to a repeater\n"); + mhdp->hdcp.sink_is_repeater = 1; + } else + DRM_INFO("Connected to a normal sink\n"); + + /* If sink was a repeater, we will be getting additional IDs to validate... + * Note that this one may take some time since spec allows up to 3s... */ + if (mhdp->hdcp.sink_is_repeater) + ret = cdns_hdcp_check_receviers(mhdp); + + /* Slight delay to allow firmware to finish setting up authenticated state */ + msleep(300); + + return ret; +} + +static int cdns_hdcp_auth(struct cdns_mhdp_device *mhdp, u8 hdcp_config) +{ + int ret = 0; + + DRM_DEBUG_KMS("HDCP: Start Authentication\n"); + + if (mhdp->hdcp.reauth_in_progress == 0) { + ret = cdns_hdcp_set_config(mhdp, hdcp_config); + if (ret) { + DRM_ERROR("cdns_hdcp_set_config failed\n"); + return -1; + } + } + + mhdp->hdcp.reauth_in_progress = 0; + mhdp->hdcp.sink_is_repeater = 0; + mhdp->hdcp.hdcp_version = hdcp_config; + + do { + if (mhdp->hdcp.cancel == 1) { + DRM_ERROR("mhdp->hdcp.cancel is TRUE\n"); + return -ECANCELED; + } + + if (hdcp_config == HDCP_TX_1) + ret = cdns_hdcp_auth_14(mhdp); + else + ret = cdns_hdcp_auth_22(mhdp); + if (ret) { + u16 hdcp_port_status; + DRM_ERROR("cdns_hdcp_auth_%s failed\n", + (hdcp_config == HDCP_TX_1) ? "14" : "22"); + hdcp_port_status = cdns_hdcp_get_status(mhdp); + cdns_hdcp_handle_status(hdcp_port_status); + return -1; + } + + ret = cdns_hdcp_auth_check(mhdp); + } while (ret == 1); + + return ret; +} + +static int _cdns_hdcp_disable(struct cdns_mhdp_device *mhdp) +{ + int ret = 0; + u8 hdcp_cfg = (HDCP_USE_KMKEY << 4); + + DRM_DEBUG_KMS("[%s:%d] HDCP is being disabled...\n", + mhdp->connector.base.name, mhdp->connector.base.base.id); + DRM_DEBUG_KMS("INFO: Disabling HDCP...\n"); + + ret = cdns_mhdp_hdcp_tx_config(mhdp, hdcp_cfg); + if (ret < 0) + DRM_DEBUG_KMS("cdns_mhdp_hdcp_tx_config failed\n"); + + DRM_DEBUG_KMS("HDCP is disabled\n"); + + mhdp->hdcp.events = 0; + + return ret; +} + +static int _cdns_hdcp_enable(struct cdns_mhdp_device *mhdp) +{ + int i, ret = 0, tries = 9, tries14 = 50; + u8 hpd_sts; + + hpd_sts = cdns_mhdp_read_hpd(mhdp); + if (hpd_sts == 0) { + dev_info(mhdp->dev, "%s HDP detected low, set state to DISABLING\n", __func__); + mhdp->hdcp.state = HDCP_STATE_DISABLING; + return -1; + } + + DRM_DEBUG_KMS("[%s:%d] HDCP is being enabled...\n", + mhdp->connector.base.name, mhdp->connector.base.base.id); + + mhdp->hdcp.events = 0; + + /* Incase of authentication failures, HDCP spec expects reauth. */ + /* TBD should this actually try 2.2 n times then 1.4? */ + for (i = 0; i < tries; i++) { + if (mhdp->hdcp.config & HDCP_CONFIG_2_2) { + ret = cdns_hdcp_auth(mhdp, HDCP_TX_2); + if (ret == 0) + return 0; + else if (ret == -ECANCELED) + return ret; + _cdns_hdcp_disable(mhdp); + } + } + + for (i = 0; i < tries14; i++) { + if (mhdp->hdcp.config & HDCP_CONFIG_1_4) { + ret = cdns_hdcp_auth(mhdp, HDCP_TX_1); + if (ret == 0) + return 0; + else if (ret == -ECANCELED) + return ret; + _cdns_hdcp_disable(mhdp); + } + DRM_DEBUG_KMS("HDCP Auth failure (%d)\n", ret); + } + + DRM_ERROR("HDCP authentication failed (%d tries/%d)\n", tries, ret); + return ret; +} + +static void cdns_hdcp_check_work(struct work_struct *work) +{ + struct cdns_mhdp_hdcp *hdcp = container_of(work, + struct cdns_mhdp_hdcp, check_work.work); + struct cdns_mhdp_device *mhdp = container_of(hdcp, + struct cdns_mhdp_device, hdcp); + + /* todo: maybe we don't need to always schedule */ + cdns_hdcp_check_link(mhdp); + schedule_delayed_work(&hdcp->check_work, 50); +} + +static void cdns_hdcp_prop_work(struct work_struct *work) +{ + struct cdns_mhdp_hdcp *hdcp = container_of(work, + struct cdns_mhdp_hdcp, prop_work); + struct cdns_mhdp_device *mhdp = container_of(hdcp, + struct cdns_mhdp_device, hdcp); + + struct drm_device *dev = mhdp->drm_dev; + + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + mutex_lock(&mhdp->hdcp.mutex); + + /* + * This worker is only used to flip between ENABLED/DESIRED. Either of + * those to UNDESIRED is handled by core. If hdcp_value == UNDESIRED, + * we're running just after hdcp has been disabled, so just exit + */ + if (mhdp->hdcp.value != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) { + drm_hdcp_update_content_protection(&mhdp->connector.base, + mhdp->hdcp.value); + } + + mutex_unlock(&mhdp->hdcp.mutex); + drm_modeset_unlock(&dev->mode_config.connection_mutex); +} + +static void show_hdcp_supported(struct cdns_mhdp_device *mhdp) +{ + if ((mhdp->hdcp.config & (HDCP_CONFIG_1_4 | HDCP_CONFIG_2_2)) == + (HDCP_CONFIG_1_4 | HDCP_CONFIG_2_2)) + DRM_INFO("Both HDCP 1.4 and 2.2 are enabled\n"); + else if (mhdp->hdcp.config & HDCP_CONFIG_1_4) + DRM_INFO("Only HDCP 1.4 is enabled\n"); + else if (mhdp->hdcp.config & HDCP_CONFIG_2_2) + DRM_INFO("Only HDCP 2.2 is enabled\n"); + else + DRM_INFO("HDCP is disabled\n"); +} + +static ssize_t HDCPTX_do_reauth_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static struct device_attribute HDCPTX_do_reauth = __ATTR_WO(HDCPTX_do_reauth); + +static ssize_t HDCPTX_do_reauth_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret; + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + + ret = cdns_mhdp_hdcp_tx_reauth(mhdp, 1); + if (ret < 0) { + dev_err(dev, "%s cdns_mhdp_hdcp_tx_reauth failed\n", __func__); + return -1; + } + + return count; +} + +static ssize_t HDCPTX_Version_show(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t HDCPTX_Version_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static struct device_attribute HDCPTX_Version = __ATTR_RW(HDCPTX_Version); + +static ssize_t HDCPTX_Version_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + int value, ret; + + ret = kstrtoint(buf, 10, &value); + if (ret != 0) + return -EINVAL; + + if (value == 2) + mhdp->hdcp.config = 2; + else if (value == 1) + mhdp->hdcp.config = 1; + else if (value == 3) + mhdp->hdcp.config = 3; + else + mhdp->hdcp.config = 0; + + return count; +} + +ssize_t HDCPTX_Version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", mhdp->hdcp.config); +} + +static ssize_t HDCPTX_Status_show(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t HDCPTX_Status_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static struct device_attribute HDCPTX_Status = __ATTR_RW(HDCPTX_Status); + +ssize_t HDCPTX_Status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + + switch (mhdp->hdcp.state) { + case HDCP_STATE_NO_AKSV: + return sprintf(buf, "%d :HDCP_STATE_NO_AKSV\n", mhdp->hdcp.state); + case HDCP_STATE_INACTIVE: + return sprintf(buf, "%d :HDCP_STATE_INACTIVE\n", mhdp->hdcp.state); + case HDCP_STATE_ENABLING: + return sprintf(buf, "%d :HDCP_STATE_ENABLING\n", mhdp->hdcp.state); + case HDCP_STATE_AUTHENTICATING: + return sprintf(buf, "%d :HDCP_STATE_AUTHENTICATING\n", mhdp->hdcp.state); + case HDCP_STATE_AUTHENTICATED: + return sprintf(buf, "%d :HDCP_STATE_AUTHENTICATED\n", mhdp->hdcp.state); + case HDCP_STATE_DISABLING: + return sprintf(buf, "%d :HDCP_STATE_DISABLING\n", mhdp->hdcp.state); + case HDCP_STATE_AUTH_FAILED: + return sprintf(buf, "%d :HDCP_STATE_AUTH_FAILED\n", mhdp->hdcp.state); + default: + return sprintf(buf, "%d :HDCP_STATE don't exist\n", mhdp->hdcp.state); + } +} + +ssize_t HDCPTX_Status_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + int value, ret; + + if (count == 2) { + ret = kstrtoint(buf, 10, &value); + if (ret != 0) + return -EINVAL; + + if ((value >= HDCP_STATE_NO_AKSV) && (value <= HDCP_STATE_AUTH_FAILED)) { + mhdp->hdcp.state = value; + return count; + } + dev_err(dev, "%s &hdp->state invalid\n", __func__); + return -1; + } + + dev_info(dev, "%s &hdp->state desired %s count=%d\n ", __func__, buf, (int)count); + + if (strncmp(buf, "HDCP_STATE_NO_AKSV", count - 1) == 0) + mhdp->hdcp.state = HDCP_STATE_NO_AKSV; + else if (strncmp(buf, "HDCP_STATE_INACTIVE", count - 1) == 0) + mhdp->hdcp.state = HDCP_STATE_INACTIVE; + else if (strncmp(buf, "HDCP_STATE_ENABLING", count - 1) == 0) + mhdp->hdcp.state = HDCP_STATE_ENABLING; + else if (strncmp(buf, "HDCP_STATE_AUTHENTICATING", count - 1) == 0) + mhdp->hdcp.state = HDCP_STATE_AUTHENTICATING; + else if (strncmp(buf, "HDCP_STATE_AUTHENTICATED", count - 1) == 0) + mhdp->hdcp.state = HDCP_STATE_AUTHENTICATED; + else if (strncmp(buf, "HDCP_STATE_DISABLING", count - 1) == 0) + mhdp->hdcp.state = HDCP_STATE_DISABLING; + else if (strncmp(buf, "HDCP_STATE_AUTH_FAILED", count - 1) == 0) + mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED; + else + dev_err(dev, "%s &hdp->state invalid\n", __func__); + return -1; +} + +void cnds_hdcp_create_device_files(struct cdns_mhdp_device *mhdp) +{ + + if (device_create_file(mhdp->dev, &HDCPTX_do_reauth)) { + DRM_ERROR("Unable to create HDCPTX_do_reauth sysfs\n"); + device_remove_file(mhdp->dev, &HDCPTX_do_reauth); + } + + if (device_create_file(mhdp->dev, &HDCPTX_Version)) { + DRM_ERROR("Unable to create HDCPTX_Version sysfs\n"); + device_remove_file(mhdp->dev, &HDCPTX_Version); + } + + if (device_create_file(mhdp->dev, &HDCPTX_Status)) { + DRM_ERROR(KERN_ERR "Unable to create HDCPTX_Status sysfs\n"); + device_remove_file(mhdp->dev, &HDCPTX_Status); + } +} +EXPORT_SYMBOL(cnds_hdcp_create_device_files); + +#ifdef DEBUG +void cdns_hdcp_show_pairing(struct cdns_mhdp_device *mhdp, struct hdcp_trans_pairing_data *p) +{ + char s[80]; + int i, k; + + DRM_INFO("Reveiver ID: %.2X%.2X%.2X%.2X%.2X\n", + p->receiver_id[0], + p->receiver_id[1], + p->receiver_id[2], + p->receiver_id[3], + p->receiver_id[4]); + for (k = 0, i = 0; k < 16; k++) + i += snprintf(&s[i], sizeof(s), "%02x", p->m[k]); + + DRM_INFO("\tm: %s\n", s); + + for (k = 0, i = 0; k < 16; k++) + i += snprintf(&s[i], sizeof(s), "%02x", p->km[k]); + + DRM_INFO("\tkm: %s\n", s); + + for (k = 0, i = 0; k < 16; k++) + i += snprintf(&s[i], sizeof(s), "%02x", p->ekh[k]); + + DRM_INFO("\tekh: %s\n", s); +} +#endif + +static int cdns_hdcp_dump_pairing(struct seq_file *s, void *data) +{ + struct cdns_mhdp_device *mhdp = data; +#ifdef DEBUG + int i; + for (i = 0; i < mhdp->hdcp.num_paired; i++) + cdns_hdcp_show_pairing(mhdp, &mhdp->hdcp.pairing[i]); +#endif + return seq_write(s, &mhdp->hdcp.pairing[0], + mhdp->hdcp.num_paired * sizeof(struct hdcp_trans_pairing_data)); +} + +static int cdns_hdcp_pairing_show(struct seq_file *s, void *data) +{ + return cdns_hdcp_dump_pairing(s, s->private); +} + +static int cdns_hdcp_dump_pairing_open(struct inode *inode, struct file *file) +{ + return single_open(file, cdns_hdcp_pairing_show, inode->i_private); +} + +static const struct file_operations cdns_hdcp_dump_fops = { + .open = cdns_hdcp_dump_pairing_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void cdns_hdcp_debugfs_init(struct cdns_mhdp_device *mhdp) +{ + struct dentry *d, *root; + + root = debugfs_create_dir("imx-hdcp", NULL); + if (IS_ERR(root) || !root) + goto err; + + d = debugfs_create_file("dump_pairing", 0444, root, mhdp, + &cdns_hdcp_dump_fops); + if (!d) + goto err; + return; + +err: + dev_err(mhdp->dev, "Unable to create debugfs entries\n"); +} + +int cdns_hdcp_init(struct cdns_mhdp_device *mhdp, struct device_node *of_node) +{ + const char *compat; + u32 temp; + int ret; + + ret = of_property_read_string(of_node, "compatible", &compat); + if (ret) { + DRM_ERROR("Failed to compatible dts string\n"); + return ret; + } + + if (!(strstr(compat, "hdmi") || strstr(compat, "dp"))) + return -EPERM; + + ret = of_property_read_u32(of_node, "hdcp-config", &temp); + if (ret) { + /* using highest level by default */ + mhdp->hdcp.config = HDCP_CONFIG_2_2; + DRM_INFO("Failed to get HDCP config - using HDCP 2.2 only\n"); + } else { + mhdp->hdcp.config = temp; + show_hdcp_supported(mhdp); + } + + cdns_hdcp_debugfs_init(mhdp); + +#ifdef USE_DEBUG_KEYS /* reserve for hdcp test key */ + { + u8 hdcp_cfg; + hdcp_cfg = HDCP_TX_2 | (HDCP_USE_KMKEY << 4) | (HDCP_CONTENT_TYPE_0 << 3); + imx_hdmi_load_test_keys(mhdp, &hdcp_cfg); + } +#endif + + mhdp->hdcp.state = HDCP_STATE_INACTIVE; + + mutex_init(&mhdp->hdcp.mutex); + INIT_DELAYED_WORK(&mhdp->hdcp.check_work, cdns_hdcp_check_work); + INIT_WORK(&mhdp->hdcp.prop_work, cdns_hdcp_prop_work); + + return 0; +} +EXPORT_SYMBOL_GPL(cdns_hdcp_init); + +int cdns_hdcp_enable(struct cdns_mhdp_device *mhdp) +{ + int ret = 0; + + mhdp->hdcp.reauth_in_progress = 0; + +#ifdef STORE_PAIRING + cdns_hdcp_get_stored_pairing(mhdp); +#endif + msleep(500); + + mutex_lock(&mhdp->hdcp.mutex); + + mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED; + mhdp->hdcp.state = HDCP_STATE_ENABLING; + mhdp->hdcp.cancel = 0; + + schedule_work(&mhdp->hdcp.prop_work); + schedule_delayed_work(&mhdp->hdcp.check_work, 50); + + mutex_unlock(&mhdp->hdcp.mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(cdns_hdcp_enable); + +int cdns_hdcp_disable(struct cdns_mhdp_device *mhdp) +{ + int ret = 0; + + cancel_delayed_work_sync(&mhdp->hdcp.check_work); + + mutex_lock(&mhdp->hdcp.mutex); + if (mhdp->hdcp.value != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) { + mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + mhdp->hdcp.state = HDCP_STATE_DISABLING; + mhdp->hdcp.cancel = 1; + schedule_work(&mhdp->hdcp.prop_work); + } + + mutex_unlock(&mhdp->hdcp.mutex); + + /* Make sure HDCP_STATE_DISABLING state is handled */ + cdns_hdcp_check_link(mhdp); + + return ret; +} +EXPORT_SYMBOL_GPL(cdns_hdcp_disable); + +void cdns_hdcp_atomic_check(struct drm_connector *connector, + struct drm_connector_state *old_state, + struct drm_connector_state *new_state) +{ + u64 old_cp = old_state->content_protection; + u64 new_cp = new_state->content_protection; + struct drm_crtc_state *crtc_state; + + if (!new_state->crtc) { + /* + * If the connector is being disabled with CP enabled, mark it + * desired so it's re-enabled when the connector is brought back + */ + if (old_cp == DRM_MODE_CONTENT_PROTECTION_ENABLED) + new_state->content_protection = + DRM_MODE_CONTENT_PROTECTION_DESIRED; + return; + } + + /* + * Nothing to do if the state didn't change, or HDCP was activated since + * the last commit + */ + if (old_cp == new_cp || + (old_cp == DRM_MODE_CONTENT_PROTECTION_DESIRED && + new_cp == DRM_MODE_CONTENT_PROTECTION_ENABLED)) + return; + + crtc_state = drm_atomic_get_new_crtc_state(new_state->state, new_state->crtc); + crtc_state->mode_changed = true; +} +EXPORT_SYMBOL_GPL(cdns_hdcp_atomic_check); + +static int cdns_hdcp_check_link(struct cdns_mhdp_device *mhdp) +{ + u16 hdcp_port_status = 0; + u8 hdcp_last_error = 0; + u8 hpd_sts; + int ret = 0; + + mhdp->hdcp.reauth_in_progress = 0; + mutex_lock(&mhdp->lock); + + if (mhdp->hdcp.state == HDCP_STATE_INACTIVE) + goto out; + + if (mhdp->hdcp.state == HDCP_STATE_DISABLING) { + _cdns_hdcp_disable(mhdp); + mhdp->hdcp.state = HDCP_STATE_INACTIVE; + goto out; + } + + if ((mhdp->hdcp.state == HDCP_STATE_AUTHENTICATED) || + (mhdp->hdcp.state == HDCP_STATE_AUTHENTICATING) || + (mhdp->hdcp.state == HDCP_STATE_REAUTHENTICATING) || + (mhdp->hdcp.state == HDCP_STATE_ENABLING)) { + + /* In active states, check the HPD signal. Because of the IRQ + * debounce delay, the state might not reflect the disconnection. + * The FW could already have detected the HDP down and reported error */ + hpd_sts = cdns_mhdp_read_hpd(mhdp); + if (hpd_sts == 0) { + mhdp->hdcp.state = HDCP_STATE_DISABLING; + goto out; + } + } + +/* TODO items: + Need to make sure that any requests from the firmware are actually + processed so want to remove this first jump to 'out', i.e. process + reauthentication requests, cleanup errors and repeater receiver id + checks. +*/ + if (mhdp->hdcp.state == HDCP_STATE_AUTHENTICATED) { + /* get port status */ + hdcp_port_status = cdns_hdcp_get_status(mhdp); + hdcp_last_error = GET_HDCP_PORT_STS_LAST_ERR(hdcp_port_status); + if (hdcp_last_error == HDCP_TRAN_ERR_REAUTH_REQ) { + DRM_INFO("Sink requesting re-authentication\n"); + mhdp->hdcp.state = HDCP_STATE_REAUTHENTICATING; + } else if (hdcp_last_error) { + DRM_ERROR("HDCP error no: %u\n", hdcp_last_error); + + if (mhdp->hdcp.value == DRM_MODE_CONTENT_PROTECTION_UNDESIRED) + goto out; + if (hdcp_port_status & HDCP_PORT_STS_AUTH) { + if (mhdp->hdcp.value != + DRM_MODE_CONTENT_PROTECTION_UNDESIRED) { + mhdp->hdcp.value = + DRM_MODE_CONTENT_PROTECTION_ENABLED; + schedule_work(&mhdp->hdcp.prop_work); + goto out; + } + } + + mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED; + + } else if (mhdp->hdcp.sink_is_repeater) { + u8 new_events; + /* Check events... and process if HDCPTX_IS_RECEIVER_ID_VALID_EVENT. */ + new_events = cdns_mhdp_get_event(mhdp); + mhdp->hdcp.events |= new_events; + if (check_event(mhdp->hdcp.events, HDCPTX_IS_RECEIVER_ID_VALID_EVENT)) { + DRM_INFO("Sink repeater updating receiver ID list...\n"); + if (cdns_hdcp_check_receviers(mhdp)) + mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED; + } + } + } + + if (mhdp->hdcp.state == HDCP_STATE_REAUTHENTICATING) { + /* For now just deal with HDCP2.2 */ + if (mhdp->hdcp.hdcp_version == HDCP_TX_2) + mhdp->hdcp.reauth_in_progress = 1; + else + mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED; + } + + if (mhdp->hdcp.state == HDCP_STATE_ENABLING) { + mhdp->hdcp.state = HDCP_STATE_AUTHENTICATING; + ret = _cdns_hdcp_enable(mhdp); + if (ret == -ECANCELED) + goto out; + else if (ret) { + DRM_ERROR("Failed to enable hdcp (%d)\n", ret); + mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED; + schedule_work(&mhdp->hdcp.prop_work); + goto out; + } + } + + if ((mhdp->hdcp.state == HDCP_STATE_AUTH_FAILED) || + (mhdp->hdcp.state == HDCP_STATE_REAUTHENTICATING)) { + + print_port_status(hdcp_port_status); + if (mhdp->hdcp.state == HDCP_STATE_AUTH_FAILED) { + DRM_DEBUG_KMS("[%s:%d] HDCP link failed, retrying authentication 0x%2x\n", + mhdp->connector.base.name, mhdp->connector.base.base.id, hdcp_port_status); + ret = _cdns_hdcp_disable(mhdp); + if (ret) { + DRM_ERROR("Failed to disable hdcp (%d)\n", ret); + mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED; + schedule_work(&mhdp->hdcp.prop_work); + goto out; + } + } else + DRM_DEBUG_KMS("[%s:%d] HDCP attempt reauthentication 0x%2x\n", + mhdp->connector.base.name, mhdp->connector.base.base.id, hdcp_port_status); + + ret = _cdns_hdcp_enable(mhdp); + if (ret == -ECANCELED) + goto out; + else if (ret) { + DRM_ERROR("Failed to enable hdcp (%d)\n", ret); + mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED; + schedule_work(&mhdp->hdcp.prop_work); + goto out; + } + } + +out: + mutex_unlock(&mhdp->lock); + + return ret; +} diff --git a/drivers/gpu/drm/bridge/cadence/cdns-hdcp-common.h b/drivers/gpu/drm/bridge/cadence/cdns-hdcp-common.h new file mode 100644 index 000000000000..9de8759252d2 --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-hdcp-common.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2021 NXP Semiconductor, Inc. + * + * 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. + * + */ + +#ifndef CDNS_HDCP_COMMON_H +#define CDNS_HDCP_COMMON_H + +int cdns_hdcp_init(struct cdns_mhdp_device *mhdp, struct device_node *of_node); +int cdns_hdcp_enable(struct cdns_mhdp_device *mhdp); +int cdns_hdcp_disable(struct cdns_mhdp_device *mhdp); +void cdns_hdcp_atomic_check(struct drm_connector *connector, + struct drm_connector_state *old_state, + struct drm_connector_state *new_state); +void cnds_hdcp_create_device_files(struct cdns_mhdp_device *mhdp); + +#endif /* CDNS_HDCP_COMMON_H */ diff --git a/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c new file mode 100644 index 000000000000..86620e17cf63 --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c @@ -0,0 +1,827 @@ +/* + * Cadence High-Definition Multimedia Interface (HDMI) driver + * + * Copyright 2019-2021 NXP + * + * 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. + * + */ +#include <drm/bridge/cdns-mhdp.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_encoder_slave.h> +#include <drm/drm_hdcp.h> +#include <drm/drm_of.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_print.h> +#include <drm/drm_scdc_helper.h> +#include <drm/drm_vblank.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/hdmi.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/mfd/syscon.h> +#include <linux/mutex.h> +#include <linux/of_device.h> + +#include "cdns-mhdp-hdcp.h" +#include "cdns-hdcp-common.h" + +static void hdmi_sink_config(struct cdns_mhdp_device *mhdp) +{ + struct drm_scdc *scdc = &mhdp->connector.base.display_info.hdmi.scdc; + u8 buff = 0; + + /* return if hdmi work in DVI mode */ + if (mhdp->hdmi.hdmi_type == MODE_DVI) + return; + + /* check sink support SCDC or not */ + if (scdc->supported != true) { + DRM_INFO("Sink Not Support SCDC\n"); + return; + } + + if (mhdp->hdmi.char_rate > 340000) { + /* + * TMDS Character Rate above 340MHz should working in HDMI2.0 + * Enable scrambling and TMDS_Bit_Clock_Ratio + */ + buff = SCDC_TMDS_BIT_CLOCK_RATIO_BY_40 | SCDC_SCRAMBLING_ENABLE; + mhdp->hdmi.hdmi_type = MODE_HDMI_2_0; + } else if (scdc->scrambling.low_rates) { + /* + * Enable scrambling and HDMI2.0 when scrambling capability of sink + * be indicated in the HF-VSDB LTE_340Mcsc_scramble bit + */ + buff = SCDC_SCRAMBLING_ENABLE; + mhdp->hdmi.hdmi_type = MODE_HDMI_2_0; + } + + /* TMDS config */ + cdns_hdmi_scdc_write(mhdp, 0x20, buff); +} + +static void hdmi_lanes_config(struct cdns_mhdp_device *mhdp) +{ + /* Line swaping */ + cdns_mhdp_reg_write(mhdp, LANES_CONFIG, 0x00400000 | mhdp->lane_mapping); +} + +static int hdmi_avi_info_set(struct cdns_mhdp_device *mhdp, + struct drm_display_mode *mode) +{ + struct hdmi_avi_infoframe frame; + int format = mhdp->video_info.color_fmt; + struct drm_connector_state *conn_state = mhdp->connector.base.state; + struct drm_display_mode *adj_mode; + enum hdmi_quantization_range qr; + u8 buf[32]; + int ret; + + /* Initialise info frame from DRM mode */ + drm_hdmi_avi_infoframe_from_display_mode(&frame, &mhdp->connector.base, + mode); + + switch (format) { + case YCBCR_4_4_4: + frame.colorspace = HDMI_COLORSPACE_YUV444; + break; + case YCBCR_4_2_2: + frame.colorspace = HDMI_COLORSPACE_YUV422; + break; + case YCBCR_4_2_0: + frame.colorspace = HDMI_COLORSPACE_YUV420; + break; + default: + frame.colorspace = HDMI_COLORSPACE_RGB; + break; + } + + drm_hdmi_avi_infoframe_colorspace(&frame, conn_state); + + adj_mode = &mhdp->bridge.base.encoder->crtc->state->adjusted_mode; + + qr = drm_default_rgb_quant_range(adj_mode); + + drm_hdmi_avi_infoframe_quant_range(&frame, &mhdp->connector.base, + adj_mode, qr); + + ret = hdmi_avi_infoframe_check(&frame); + if (WARN_ON(ret)) + return false; + + ret = hdmi_avi_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1); + if (ret < 0) { + DRM_ERROR("failed to pack AVI infoframe: %d\n", ret); + return -1; + } + + buf[0] = 0; + cdns_mhdp_infoframe_set(mhdp, 0, sizeof(buf), buf, HDMI_INFOFRAME_TYPE_AVI); + return 0; +} + +static void hdmi_vendor_info_set(struct cdns_mhdp_device *mhdp, + struct drm_display_mode *mode) +{ + struct hdmi_vendor_infoframe frame; + u8 buf[32]; + int ret; + + /* Initialise vendor frame from DRM mode */ + ret = drm_hdmi_vendor_infoframe_from_display_mode(&frame, &mhdp->connector.base, mode); + if (ret < 0) { + DRM_INFO("No vendor infoframe\n"); + return; + } + + ret = hdmi_vendor_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1); + if (ret < 0) { + DRM_WARN("Unable to pack vendor infoframe: %d\n", ret); + return; + } + + buf[0] = 0; + cdns_mhdp_infoframe_set(mhdp, 3, sizeof(buf), buf, HDMI_INFOFRAME_TYPE_VENDOR); +} + +static void hdmi_drm_info_set(struct cdns_mhdp_device *mhdp) +{ + struct drm_connector_state *conn_state; + struct hdmi_drm_infoframe frame; + u8 buf[32]; + int ret; + + conn_state = mhdp->connector.base.state; + + if (!conn_state->hdr_output_metadata) + return; + + ret = drm_hdmi_infoframe_set_hdr_metadata(&frame, conn_state); + if (ret < 0) { + DRM_DEBUG_KMS("couldn't set HDR metadata in infoframe\n"); + return; + } + + ret = hdmi_drm_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1); + if (ret < 0) { + DRM_DEBUG_KMS("couldn't pack HDR infoframe\n"); + return; + } + + buf[0] = 0; + cdns_mhdp_infoframe_set(mhdp, 3, sizeof(buf), + buf, HDMI_INFOFRAME_TYPE_DRM); +} + +void cdns_hdmi_mode_set(struct cdns_mhdp_device *mhdp) +{ + struct drm_display_mode *mode = &mhdp->mode; + int ret; + + /* video mode valid check */ + if (mode->clock == 0 || mode->hdisplay == 0 || mode->vdisplay == 0) + return; + + hdmi_lanes_config(mhdp); + + cdns_mhdp_plat_call(mhdp, pclk_rate); + + /* delay for HDMI FW stable after pixel clock relock */ + msleep(20); + + cdns_mhdp_plat_call(mhdp, phy_set); + + hdmi_sink_config(mhdp); + + ret = cdns_hdmi_ctrl_init(mhdp, mhdp->hdmi.hdmi_type, mhdp->hdmi.char_rate); + if (ret < 0) { + DRM_ERROR("%s, ret = %d\n", __func__, ret); + return; + } + + /* Config GCP */ + if (mhdp->video_info.color_depth == 8) + cdns_hdmi_disable_gcp(mhdp); + else + cdns_hdmi_enable_gcp(mhdp); + + ret = hdmi_avi_info_set(mhdp, mode); + if (ret < 0) { + DRM_ERROR("%s ret = %d\n", __func__, ret); + return; + } + + /* vendor info frame is enable only when HDMI1.4 4K mode */ + hdmi_vendor_info_set(mhdp, mode); + + hdmi_drm_info_set(mhdp); + + ret = cdns_hdmi_mode_config(mhdp, mode, &mhdp->video_info); + if (ret < 0) { + DRM_ERROR("CDN_API_HDMITX_SetVic_blocking ret = %d\n", ret); + return; + } +} + +static void handle_plugged_change(struct cdns_mhdp_device *mhdp, bool plugged) +{ + if (mhdp->plugged_cb && mhdp->codec_dev) + mhdp->plugged_cb(mhdp->codec_dev, plugged); +} + +int cdns_hdmi_set_plugged_cb(struct cdns_mhdp_device *mhdp, + hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + bool plugged; + + mutex_lock(&mhdp->lock); + mhdp->plugged_cb = fn; + mhdp->codec_dev = codec_dev; + plugged = mhdp->last_connector_result == connector_status_connected; + handle_plugged_change(mhdp, plugged); + mutex_unlock(&mhdp->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(cdns_hdmi_set_plugged_cb); + +static enum drm_connector_status +cdns_hdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct cdns_mhdp_device *mhdp = + container_of(connector, struct cdns_mhdp_device, connector.base); + enum drm_connector_status result; + + u8 hpd = 0xf; + + hpd = cdns_mhdp_read_hpd(mhdp); + + if (hpd == 1) + /* Cable Connected */ + result = connector_status_connected; + else if (hpd == 0) + /* Cable Disconnedted */ + result = connector_status_disconnected; + else { + /* Cable status unknown */ + DRM_INFO("Unknow cable status, hdp=%u\n", hpd); + result = connector_status_unknown; + } + + mutex_lock(&mhdp->lock); + if (result != mhdp->last_connector_result) { + handle_plugged_change(mhdp, + result == connector_status_connected); + mhdp->last_connector_result = result; + } + mutex_unlock(&mhdp->lock); + + return result; +} + +static int cdns_hdmi_connector_get_modes(struct drm_connector *connector) +{ + struct cdns_mhdp_device *mhdp = + container_of(connector, struct cdns_mhdp_device, connector.base); + int num_modes = 0; + struct edid *edid; + + edid = drm_do_get_edid(&mhdp->connector.base, + cdns_hdmi_get_edid_block, mhdp); + if (edid) { + dev_info(mhdp->dev, "%x,%x,%x,%x,%x,%x,%x,%x\n", + edid->header[0], edid->header[1], + edid->header[2], edid->header[3], + edid->header[4], edid->header[5], + edid->header[6], edid->header[7]); + drm_connector_update_edid_property(connector, edid); + num_modes = drm_add_edid_modes(connector, edid); + mhdp->hdmi.hdmi_type = drm_detect_hdmi_monitor(edid) ? + MODE_HDMI_1_4 : MODE_DVI; + kfree(edid); + } + + if (num_modes == 0) + DRM_ERROR("Invalid edid\n"); + return num_modes; +} + +static bool blob_equal(const struct drm_property_blob *a, + const struct drm_property_blob *b) +{ + if (a && b) + return a->length == b->length && + !memcmp(a->data, b->data, a->length); + + return !a == !b; +} + +static void cdns_hdmi_bridge_disable(struct drm_bridge *bridge) +{ + struct cdns_mhdp_device *mhdp = bridge->driver_private; + + cdns_hdcp_disable(mhdp); +} + +static void cdns_hdmi_bridge_enable(struct drm_bridge *bridge) +{ + struct cdns_mhdp_device *mhdp = bridge->driver_private; + struct drm_connector_state *conn_state = mhdp->connector.base.state; + + if (conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_DESIRED) + cdns_hdcp_enable(mhdp); +} + +static int cdns_hdmi_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct drm_connector_state *new_con_state = + drm_atomic_get_new_connector_state(state, connector); + struct drm_connector_state *old_con_state = + drm_atomic_get_old_connector_state(state, connector); + struct drm_crtc *crtc = new_con_state->crtc; + struct drm_crtc_state *new_crtc_state; + struct cdns_mhdp_device *mhdp = + container_of(connector, struct cdns_mhdp_device, connector.base); + + cdns_hdcp_atomic_check(connector, old_con_state, new_con_state); + if (!new_con_state->crtc) + return 0; + + new_crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(new_crtc_state)) + return PTR_ERR(new_crtc_state); + + if (!blob_equal(new_con_state->hdr_output_metadata, + old_con_state->hdr_output_metadata) || + new_con_state->colorspace != old_con_state->colorspace) { + + new_crtc_state->mode_changed = + !new_con_state->hdr_output_metadata || + !old_con_state->hdr_output_metadata || + new_con_state->colorspace != old_con_state->colorspace; + } + /* save new connector state */ + memcpy(&mhdp->connector.new_state, new_con_state, sizeof(struct drm_connector_state)); + + /* + * These properties are handled by fastset, and might not end up in a + * modeset. + */ + if (new_con_state->picture_aspect_ratio != + old_con_state->picture_aspect_ratio || + new_con_state->content_type != old_con_state->content_type || + new_con_state->scaling_mode != old_con_state->scaling_mode) + new_crtc_state->mode_changed = true; + return 0; +} + +static const struct drm_connector_funcs cdns_hdmi_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = cdns_hdmi_connector_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_connector_helper_funcs cdns_hdmi_connector_helper_funcs = { + .get_modes = cdns_hdmi_connector_get_modes, + .atomic_check = cdns_hdmi_connector_atomic_check, +}; + +static int cdns_hdmi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct cdns_mhdp_device *mhdp = bridge->driver_private; + struct drm_mode_config *config = &bridge->dev->mode_config; + struct drm_encoder *encoder = bridge->encoder; + struct drm_connector *connector = &mhdp->connector.base; + int ret; + + connector->interlace_allowed = 1; + connector->polled = DRM_CONNECTOR_POLL_HPD; + if (!strncmp("imx8mq-hdmi", mhdp->plat_data->plat_name, 11)) + connector->ycbcr_420_allowed = true; + + drm_connector_helper_add(connector, &cdns_hdmi_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, &cdns_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret < 0) { + DRM_ERROR("Failed to initialize connector\n"); + return ret; + } + + if (!strncmp("imx8mq-hdmi", mhdp->plat_data->plat_name, 11)) { + drm_object_attach_property(&connector->base, + config->hdr_output_metadata_property, + 0); + + if (!drm_mode_create_hdmi_colorspace_property(connector)) + drm_object_attach_property(&connector->base, + connector->colorspace_property, + 0); + } + + drm_connector_attach_encoder(connector, encoder); + + drm_connector_attach_content_protection_property(connector, true); + return 0; +} + +static enum drm_mode_status +cdns_hdmi_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct cdns_mhdp_device *mhdp = bridge->driver_private; + enum drm_mode_status mode_status = MODE_OK; + u32 vic; + int ret; + + /* We don't support double-clocked and Interlaced modes */ + if (mode->flags & DRM_MODE_FLAG_DBLCLK || + mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_BAD; + + /* MAX support pixel clock rate 594MHz */ + if (mode->clock > 594000) + return MODE_CLOCK_HIGH; + + /* 5120 x 2160 is the maximum supported resolution */ + if (mode->hdisplay > 5120 || mode->vdisplay > 2160) + return MODE_BAD_HVALUE; + + /* imx8mq-hdmi does not support non CEA modes */ + if (!strncmp("imx8mq-hdmi", mhdp->plat_data->plat_name, 11)) { + vic = drm_match_cea_mode(mode); + if (vic == 0) + return MODE_BAD; + } + + mhdp->valid_mode = mode; + ret = cdns_mhdp_plat_call(mhdp, phy_video_valid); + if (ret == false) + return MODE_CLOCK_RANGE; + + return mode_status; +} + +static void cdns_hdmi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *orig_mode, + const struct drm_display_mode *mode) +{ + struct cdns_mhdp_device *mhdp = bridge->driver_private; + struct video_info *video = &mhdp->video_info; + + video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC); + video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC); + + DRM_INFO("Mode: %dx%dp%d\n", mode->hdisplay, mode->vdisplay, mode->clock); + memcpy(&mhdp->mode, mode, sizeof(struct drm_display_mode)); + + mutex_lock(&mhdp->lock); + cdns_hdmi_mode_set(mhdp); + mutex_unlock(&mhdp->lock); +} + +bool cdns_hdmi_bridge_mode_fixup(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct cdns_mhdp_device *mhdp = bridge->driver_private; + struct drm_connector_state *new_state = &mhdp->connector.new_state; + struct drm_display_info *di = &mhdp->connector.base.display_info; + struct video_info *video = &mhdp->video_info; + int vic = drm_match_cea_mode(mode); + + video->color_depth = 8; + video->color_fmt = PXL_RGB; + + /* for all other platforms, other than imx8mq */ + if (strncmp("imx8mq-hdmi", mhdp->plat_data->plat_name, 11)) { + if (di->bpc == 10 || di->bpc == 6) + video->color_depth = di->bpc; + + return true; + } + + /* H20 Section 7.2.2, Colorimetry BT2020 for pixel encoding 10bpc or more */ + if (new_state->colorspace == DRM_MODE_COLORIMETRY_BT2020_RGB) { + if (drm_mode_is_420_only(di, mode)) + return false; + + /* BT2020_RGB for RGB 10bit or more */ + /* 10b RGB is not supported for following VICs */ + if (vic == 97 || vic == 96 || vic == 95 || vic == 93 || vic == 94) + return false; + + video->color_depth = 10; + } else if (new_state->colorspace == DRM_MODE_COLORIMETRY_BT2020_CYCC || + new_state->colorspace == DRM_MODE_COLORIMETRY_BT2020_YCC) { + /* BT2020_YCC/CYCC for YUV 10bit or more */ + if (drm_mode_is_420_only(di, mode) || + drm_mode_is_420_also(di, mode)) + video->color_fmt = YCBCR_4_2_0; + else + video->color_fmt = YCBCR_4_2_2; + + if (di->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36) + video->color_depth = 12; + else if (di->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30) + video->color_depth = 10; + } else if (new_state->colorspace == DRM_MODE_COLORIMETRY_SMPTE_170M_YCC || + new_state->colorspace == DRM_MODE_COLORIMETRY_BT709_YCC || + new_state->colorspace == DRM_MODE_COLORIMETRY_XVYCC_601 || + new_state->colorspace == DRM_MODE_COLORIMETRY_XVYCC_709 || + new_state->colorspace == DRM_MODE_COLORIMETRY_SYCC_601) { + /* Colorimetry for HD and SD YUV */ + if (drm_mode_is_420_only(di, mode) || drm_mode_is_420_also(di, mode)) + video->color_fmt = YCBCR_4_2_0; + else + video->color_fmt = YCBCR_4_4_4; + } else if (new_state->colorspace == DRM_MODE_COLORIMETRY_DEFAULT) { + /* set default color fmt for YUV420 only mode */ + if (drm_mode_is_420_only(di, mode)) + video->color_fmt = YCBCR_4_2_0; + } + + return true; +} + +static const struct drm_bridge_funcs cdns_hdmi_bridge_funcs = { + .attach = cdns_hdmi_bridge_attach, + .enable = cdns_hdmi_bridge_enable, + .disable = cdns_hdmi_bridge_disable, + .mode_set = cdns_hdmi_bridge_mode_set, + .mode_valid = cdns_hdmi_bridge_mode_valid, + .mode_fixup = cdns_hdmi_bridge_mode_fixup, +}; + +static void hotplug_work_func(struct work_struct *work) +{ + struct cdns_mhdp_device *mhdp = container_of(work, + struct cdns_mhdp_device, hotplug_work.work); + struct drm_connector *connector = &mhdp->connector.base; + + drm_helper_hpd_irq_event(connector->dev); + + if (connector->status == connector_status_connected) { + DRM_INFO("HDMI Cable Plug In\n"); + + /* Recovery HDCP state */ + if (connector->state->content_protection != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) + mhdp->hdcp.state = HDCP_STATE_ENABLING; + + mhdp->force_mode_set = true; + enable_irq(mhdp->irq[IRQ_OUT]); + } else if (connector->status == connector_status_disconnected) { + /* Cable Disconnedted */ + DRM_INFO("HDMI Cable Plug Out\n"); + + /* Disable HDCP when cable plugout, + * set content_protection to DESIRED, recovery HDCP state after cable plugin + */ + if (connector->state->content_protection != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) { + connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + mhdp->hdcp.state = HDCP_STATE_DISABLING; + } + + /* force mode set for cable replugin to recovery HDMI2.0 video modes */ + mhdp->force_mode_set = true; + enable_irq(mhdp->irq[IRQ_IN]); + } +} + +static irqreturn_t cdns_hdmi_irq_thread(int irq, void *data) +{ + struct cdns_mhdp_device *mhdp = data; + + disable_irq_nosync(irq); + + mod_delayed_work(system_wq, &mhdp->hotplug_work, + msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); + + return IRQ_HANDLED; +} + +static void cdns_hdmi_parse_dt(struct cdns_mhdp_device *mhdp) +{ + struct device_node *of_node = mhdp->dev->of_node; + int ret; + + ret = of_property_read_u32(of_node, "lane-mapping", &mhdp->lane_mapping); + if (ret) { + mhdp->lane_mapping = 0xc6; + dev_warn(mhdp->dev, "Failed to get lane_mapping - using default 0xc6\n"); + } + dev_info(mhdp->dev, "lane-mapping 0x%02x\n", mhdp->lane_mapping); +} + +#ifdef CONFIG_DRM_CDNS_HDMI_CEC +static void cdns_mhdp_cec_init(struct cdns_mhdp_device *mhdp) +{ + struct cdns_mhdp_cec *cec = &mhdp->hdmi.cec; + + cec->dev = mhdp->dev; + cec->iolock = &mhdp->iolock; + cec->regs_base = mhdp->regs_base; + cec->regs_sec = mhdp->regs_sec; + cec->bus_type = mhdp->bus_type; +} +#endif + +static int __cdns_hdmi_probe(struct platform_device *pdev, + struct cdns_mhdp_device *mhdp) +{ + struct device *dev = &pdev->dev; + struct platform_device_info pdevinfo; + struct resource *iores = NULL; + int ret; + + mutex_init(&mhdp->lock); + mutex_init(&mhdp->api_lock); + mutex_init(&mhdp->iolock); + + INIT_DELAYED_WORK(&mhdp->hotplug_work, hotplug_work_func); + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mhdp->regs_base = devm_ioremap(dev, iores->start, resource_size(iores)); + if (IS_ERR(mhdp->regs_base)) { + dev_err(dev, "No regs_base memory\n"); + return -ENOMEM; + } + + /* sec register base */ + iores = platform_get_resource(pdev, IORESOURCE_MEM, 1); + mhdp->regs_sec = devm_ioremap(dev, iores->start, resource_size(iores)); + if (IS_ERR(mhdp->regs_sec)) { + dev_err(dev, "No regs_sec memory\n"); + return -ENOMEM; + } + + mhdp->irq[IRQ_IN] = platform_get_irq_byname(pdev, "plug_in"); + if (mhdp->irq[IRQ_IN] < 0) { + dev_info(dev, "No plug_in irq number\n"); + return -EPROBE_DEFER; + } + + mhdp->irq[IRQ_OUT] = platform_get_irq_byname(pdev, "plug_out"); + if (mhdp->irq[IRQ_OUT] < 0) { + dev_info(dev, "No plug_out irq number\n"); + return -EPROBE_DEFER; + } + + cdns_mhdp_plat_call(mhdp, power_on); + + /* Initialize FW */ + cdns_mhdp_plat_call(mhdp, firmware_init); + + /* HDMI FW alive check */ + ret = cdns_mhdp_check_alive(mhdp); + if (ret == false) { + dev_err(dev, "NO HDMI FW running\n"); + return -ENXIO; + } + + /* Enable Hotplug Detect thread */ + irq_set_status_flags(mhdp->irq[IRQ_IN], IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_IN], + NULL, cdns_hdmi_irq_thread, + IRQF_ONESHOT, dev_name(dev), + mhdp); + if (ret < 0) { + dev_err(dev, "can't claim irq %d\n", + mhdp->irq[IRQ_IN]); + return -EINVAL; + } + + irq_set_status_flags(mhdp->irq[IRQ_OUT], IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_OUT], + NULL, cdns_hdmi_irq_thread, + IRQF_ONESHOT, dev_name(dev), + mhdp); + if (ret < 0) { + dev_err(dev, "can't claim irq %d\n", + mhdp->irq[IRQ_OUT]); + return -EINVAL; + } + + cdns_hdmi_parse_dt(mhdp); + + ret = cdns_hdcp_init(mhdp, pdev->dev.of_node); + if (ret < 0) + DRM_WARN("Failed to initialize HDCP\n"); + + cnds_hdcp_create_device_files(mhdp); + + if (cdns_mhdp_read_hpd(mhdp)) + enable_irq(mhdp->irq[IRQ_OUT]); + else + enable_irq(mhdp->irq[IRQ_IN]); + + mhdp->bridge.base.driver_private = mhdp; + mhdp->bridge.base.funcs = &cdns_hdmi_bridge_funcs; +#ifdef CONFIG_OF + mhdp->bridge.base.of_node = dev->of_node; +#endif + mhdp->last_connector_result = connector_status_disconnected; + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.parent = dev; + pdevinfo.id = PLATFORM_DEVID_AUTO; + + dev_set_drvdata(dev, mhdp); + + /* register audio driver */ + cdns_mhdp_register_audio_driver(dev); + + /* register cec driver */ +#ifdef CONFIG_DRM_CDNS_HDMI_CEC + cdns_mhdp_cec_init(mhdp); + cdns_mhdp_register_cec_driver(&mhdp->hdmi.cec); +#endif + + return 0; +} + +static void __cdns_hdmi_remove(struct cdns_mhdp_device *mhdp) +{ + /* unregister cec driver */ +#ifdef CONFIG_DRM_CDNS_HDMI_CEC + cdns_mhdp_unregister_cec_driver(&mhdp->hdmi.cec); +#endif + cdns_mhdp_unregister_audio_driver(mhdp->dev); +} + +/* ----------------------------------------------------------------------------- + * Probe/remove API, used from platforms based on the DRM bridge API. + */ +int cdns_hdmi_probe(struct platform_device *pdev, + struct cdns_mhdp_device *mhdp) +{ + int ret; + + ret = __cdns_hdmi_probe(pdev, mhdp); + if (ret < 0) + return ret; + + drm_bridge_add(&mhdp->bridge.base); + + return 0; +} +EXPORT_SYMBOL_GPL(cdns_hdmi_probe); + +void cdns_hdmi_remove(struct platform_device *pdev) +{ + struct cdns_mhdp_device *mhdp = platform_get_drvdata(pdev); + + drm_bridge_remove(&mhdp->bridge.base); + + __cdns_hdmi_remove(mhdp); +} +EXPORT_SYMBOL_GPL(cdns_hdmi_remove); + +/* ----------------------------------------------------------------------------- + * Bind/unbind API, used from platforms based on the component framework. + */ +int cdns_hdmi_bind(struct platform_device *pdev, struct drm_encoder *encoder, + struct cdns_mhdp_device *mhdp) +{ + int ret; + + ret = __cdns_hdmi_probe(pdev, mhdp); + if (ret) + return ret; + + ret = drm_bridge_attach(encoder, &mhdp->bridge.base, NULL, 0); + if (ret) { + cdns_hdmi_remove(pdev); + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cdns_hdmi_bind); + +void cdns_hdmi_unbind(struct device *dev) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + + __cdns_hdmi_remove(mhdp); +} +EXPORT_SYMBOL_GPL(cdns_hdmi_unbind); + +MODULE_AUTHOR("Sandor Yu <sandor.yu@nxp.com>"); +MODULE_DESCRIPTION("Cadence HDMI transmitter driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cdn-hdmi"); diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-audio.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-audio.c new file mode 100644 index 000000000000..85f526175439 --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-audio.c @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author: Chris Zhong <zyw@rock-chips.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 <linux/clk.h> +#include <linux/reset.h> +#include <drm/bridge/cdns-mhdp.h> +#include <sound/hdmi-codec.h> +#include <drm/drm_of.h> +#include <drm/drm_vblank.h> +#include <drm/drm_print.h> + +#define CDNS_DP_SPDIF_CLK 200000000 + +static u32 TMDS_rate_table[7] = { + 25200, 27000, 54000, 74250, 148500, 297000, 594000, +}; + +static u32 N_table_32k[7] = { +/* 25200/27000/54000/74250/148500/297000/594000 */ + 4096, 4096, 4096, 4096, 4096, 3072, 3072, +}; + +static u32 N_table_44k[7] = { + 6272, 6272, 6272, 6272, 6272, 4704, 9408, +}; + +static u32 N_table_48k[7] = { + 6144, 6144, 6144, 6144, 6144, 5120, 6144, +}; + +static int select_N_index(u32 pclk) +{ + int num = sizeof(TMDS_rate_table)/sizeof(int); + int i = 0; + + for (i = 0; i < num ; i++) + if (pclk == TMDS_rate_table[i]) + break; + + if (i == num) { + DRM_WARN("pclkc %d is not supported!\n", pclk); + return num-1; + } + + return i; +} + +static void hdmi_audio_avi_set(struct cdns_mhdp_device *mhdp, + u32 channels) +{ + struct hdmi_audio_infoframe frame; + u8 buf[32]; + int ret; + + hdmi_audio_infoframe_init(&frame); + + frame.channels = channels; + frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; + + if (channels == 2) + frame.channel_allocation = 0; + else if (channels == 4) + frame.channel_allocation = 0x3; + else if (channels == 6) + frame.channel_allocation = 0xB; + else if (channels == 8) + frame.channel_allocation = 0x13; + + ret = hdmi_audio_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1); + if (ret < 0) { + DRM_ERROR("failed to pack audio infoframe: %d\n", ret); + return; + } + + buf[0] = 0; + + cdns_mhdp_infoframe_set(mhdp, 1, sizeof(buf), buf, HDMI_INFOFRAME_TYPE_AUDIO); +} + +int cdns_mhdp_audio_stop(struct cdns_mhdp_device *mhdp, + struct audio_info *audio) +{ + int ret; + + if (audio->connector_type == DRM_MODE_CONNECTOR_DisplayPort) { + ret = cdns_mhdp_reg_write(mhdp, AUDIO_PACK_CONTROL, 0); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "audio stop failed: %d\n", ret); + return ret; + } + } + + cdns_mhdp_bus_write(0, mhdp, SPDIF_CTRL_ADDR); + + /* clearn the audio config and reset */ + cdns_mhdp_bus_write(0, mhdp, AUDIO_SRC_CNTL); + cdns_mhdp_bus_write(0, mhdp, AUDIO_SRC_CNFG); + cdns_mhdp_bus_write(AUDIO_SW_RST, mhdp, AUDIO_SRC_CNTL); + cdns_mhdp_bus_write(0, mhdp, AUDIO_SRC_CNTL); + + /* reset smpl2pckt component */ + cdns_mhdp_bus_write(0, mhdp, SMPL2PKT_CNTL); + cdns_mhdp_bus_write(AUDIO_SW_RST, mhdp, SMPL2PKT_CNTL); + cdns_mhdp_bus_write(0, mhdp, SMPL2PKT_CNTL); + + /* reset FIFO */ + cdns_mhdp_bus_write(AUDIO_SW_RST, mhdp, FIFO_CNTL); + cdns_mhdp_bus_write(0, mhdp, FIFO_CNTL); + + if (audio->format == AFMT_SPDIF_INT) + clk_disable_unprepare(mhdp->spdif_clk); + + return 0; +} +EXPORT_SYMBOL(cdns_mhdp_audio_stop); + +int cdns_mhdp_audio_mute(struct cdns_mhdp_device *mhdp, bool enable) +{ + struct audio_info *audio = &mhdp->audio_info; + int ret = true; + + if (audio->connector_type == DRM_MODE_CONNECTOR_DisplayPort) { + ret = cdns_mhdp_reg_write_bit(mhdp, DP_VB_ID, 4, 1, enable); + if (ret) + DRM_DEV_ERROR(mhdp->dev, "audio mute failed: %d\n", ret); + } + + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_audio_mute); + +static void cdns_mhdp_audio_config_i2s(struct cdns_mhdp_device *mhdp, + struct audio_info *audio) +{ + int sub_pckt_num = 1, i2s_port_en_val = 0xf, i; + int idx = select_N_index(mhdp->mode.clock); + int numofchannels = audio->channels; + u32 val, ncts; + u32 disable_port3 = 0; + u32 audio_type = 0x2; /* L-PCM */ + u32 transmission_type = 0; /* not required for L-PCM */ + + if (numofchannels == 2) { + if (mhdp->dp.num_lanes == 1) + sub_pckt_num = 2; + else + sub_pckt_num = 4; + + i2s_port_en_val = 1; + } else if (numofchannels == 4) { + i2s_port_en_val = 3; + } else if (numofchannels == 6) { + numofchannels = 8; + disable_port3 = 1; + } else if ((numofchannels == 8) && (audio->non_pcm)) { + audio_type = 0x9; /* HBR packet type */ + transmission_type = 0x9; /* HBR packet type */ + } + + cdns_mhdp_bus_write(0x0, mhdp, SPDIF_CTRL_ADDR); + + val = SYNC_WR_TO_CH_ZERO; + val |= disable_port3 << 4; + cdns_mhdp_bus_write(val, mhdp, FIFO_CNTL); + + val = MAX_NUM_CH(numofchannels); + val |= NUM_OF_I2S_PORTS(numofchannels); + val |= audio_type << 7; + val |= CFG_SUB_PCKT_NUM(sub_pckt_num); + cdns_mhdp_bus_write(val, mhdp, SMPL2PKT_CNFG); + + if (audio->sample_width == 16) + val = 0; + else if (audio->sample_width == 24) + val = 1 << 9; + else + val = 2 << 9; + + val |= AUDIO_CH_NUM(numofchannels); + val |= I2S_DEC_PORT_EN(i2s_port_en_val); + val |= TRANS_SMPL_WIDTH_32; + val |= transmission_type << 13; + cdns_mhdp_bus_write(val, mhdp, AUDIO_SRC_CNFG); + + for (i = 0; i < (numofchannels + 1) / 2; i++) { + if (audio->sample_width == 16) + val = (0x02 << 8) | (0x02 << 20); + else if (audio->sample_width == 24) + val = (0x0b << 8) | (0x0b << 20); + + val |= ((2 * i) << 4) | ((2 * i + 1) << 16); + cdns_mhdp_bus_write(val, mhdp, STTS_BIT_CH(i)); + } + + switch (audio->sample_rate) { + case 32000: + val = SAMPLING_FREQ(3) | + ORIGINAL_SAMP_FREQ(0xc); + ncts = N_table_32k[idx]; + break; + case 44100: + val = SAMPLING_FREQ(0) | + ORIGINAL_SAMP_FREQ(0xf); + ncts = N_table_44k[idx]; + break; + case 48000: + val = SAMPLING_FREQ(2) | + ORIGINAL_SAMP_FREQ(0xd); + ncts = N_table_48k[idx]; + break; + case 88200: + val = SAMPLING_FREQ(8) | + ORIGINAL_SAMP_FREQ(0x7); + ncts = N_table_44k[idx] * 2; + break; + case 96000: + val = SAMPLING_FREQ(0xa) | + ORIGINAL_SAMP_FREQ(5); + ncts = N_table_48k[idx] * 2; + break; + case 176400: + val = SAMPLING_FREQ(0xc) | + ORIGINAL_SAMP_FREQ(3); + ncts = N_table_44k[idx] * 4; + break; + case 192000: + default: + val = SAMPLING_FREQ(0xe) | + ORIGINAL_SAMP_FREQ(1); + ncts = N_table_48k[idx] * 4; + break; + } + val |= 4; + cdns_mhdp_bus_write(val, mhdp, COM_CH_STTS_BITS); + + if (audio->connector_type == DRM_MODE_CONNECTOR_HDMIA) + cdns_mhdp_reg_write(mhdp, CM_I2S_CTRL, ncts | 0x4000000); + + cdns_mhdp_bus_write(SMPL2PKT_EN, mhdp, SMPL2PKT_CNTL); + cdns_mhdp_bus_write(I2S_DEC_START, mhdp, AUDIO_SRC_CNTL); +} + +static void cdns_mhdp_audio_config_spdif(struct cdns_mhdp_device *mhdp) +{ + u32 val; + + cdns_mhdp_bus_write(SYNC_WR_TO_CH_ZERO, mhdp, FIFO_CNTL); + + val = MAX_NUM_CH(2) | AUDIO_TYPE_LPCM | CFG_SUB_PCKT_NUM(4); + cdns_mhdp_bus_write(val, mhdp, SMPL2PKT_CNFG); + cdns_mhdp_bus_write(SMPL2PKT_EN, mhdp, SMPL2PKT_CNTL); + + val = SPDIF_ENABLE | SPDIF_AVG_SEL | SPDIF_JITTER_BYPASS; + cdns_mhdp_bus_write(val, mhdp, SPDIF_CTRL_ADDR); + + clk_prepare_enable(mhdp->spdif_clk); + clk_set_rate(mhdp->spdif_clk, CDNS_DP_SPDIF_CLK); +} + +int cdns_mhdp_audio_config(struct cdns_mhdp_device *mhdp, + struct audio_info *audio) +{ + int ret; + + /* reset the spdif clk before config */ + if (audio->format == AFMT_SPDIF_INT) { + reset_control_assert(mhdp->spdif_rst); + reset_control_deassert(mhdp->spdif_rst); + } + + if (audio->connector_type == DRM_MODE_CONNECTOR_DisplayPort) { + ret = cdns_mhdp_reg_write(mhdp, CM_LANE_CTRL, LANE_REF_CYC); + if (ret) + goto err_audio_config; + + ret = cdns_mhdp_reg_write(mhdp, CM_CTRL, 0); + if (ret) + goto err_audio_config; + } else { + /* HDMI Mode */ + ret = cdns_mhdp_reg_write(mhdp, CM_CTRL, 8); + if (ret) + goto err_audio_config; + } + + if (audio->format == AFMT_I2S) + cdns_mhdp_audio_config_i2s(mhdp, audio); + else if (audio->format == AFMT_SPDIF_INT) + cdns_mhdp_audio_config_spdif(mhdp); + + if (audio->connector_type == DRM_MODE_CONNECTOR_DisplayPort) + ret = cdns_mhdp_reg_write(mhdp, AUDIO_PACK_CONTROL, AUDIO_PACK_EN); + + if (audio->connector_type == DRM_MODE_CONNECTOR_HDMIA) + hdmi_audio_avi_set(mhdp, audio->channels); + +err_audio_config: + if (ret) + DRM_DEV_ERROR(mhdp->dev, "audio config failed: %d\n", ret); + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_audio_config); + +static int audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + struct audio_info audio = { + .sample_width = params->sample_width, + .sample_rate = params->sample_rate, + .channels = params->channels, + .connector_type = mhdp->connector.base.connector_type, + }; + int ret; + + switch (daifmt->fmt) { + case HDMI_I2S: + audio.format = AFMT_I2S; + break; + case HDMI_SPDIF: + audio.format = AFMT_SPDIF_EXT; + break; + default: + DRM_DEV_ERROR(dev, "Invalid format %d\n", daifmt->fmt); + ret = -EINVAL; + goto out; + } + + audio.non_pcm = params->iec.status[0] & IEC958_AES0_NONAUDIO; + + ret = cdns_mhdp_audio_config(mhdp, &audio); + if (!ret) + mhdp->audio_info = audio; + +out: + return ret; +} + +static void audio_shutdown(struct device *dev, void *data) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + int ret; + + ret = cdns_mhdp_audio_stop(mhdp, &mhdp->audio_info); + if (!ret) + mhdp->audio_info.format = AFMT_UNUSED; +} + +static int audio_mute_stream(struct device *dev, void *data, + bool enable, int direction) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + int ret; + + ret = cdns_mhdp_audio_mute(mhdp, enable); + + return ret; +} + +static int audio_get_eld(struct device *dev, void *data, + u8 *buf, size_t len) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + + memcpy(buf, mhdp->connector.base.eld, + min(sizeof(mhdp->connector.base.eld), len)); + + return 0; +} + +static int audio_hook_plugged_cb(struct device *dev, void *data, + hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + + return cdns_hdmi_set_plugged_cb(mhdp, fn, codec_dev); +} + +static const struct hdmi_codec_ops audio_codec_ops = { + .hw_params = audio_hw_params, + .audio_shutdown = audio_shutdown, + .mute_stream = audio_mute_stream, + .get_eld = audio_get_eld, + .hook_plugged_cb = audio_hook_plugged_cb, + .no_capture_mute = 1, +}; + +int cdns_mhdp_register_audio_driver(struct device *dev) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + struct hdmi_codec_pdata codec_data = { + .i2s = 1, + .spdif = 1, + .ops = &audio_codec_ops, + .max_i2s_channels = 8, + }; + + mhdp->audio_pdev = platform_device_register_data( + dev, HDMI_CODEC_DRV_NAME, 1, + &codec_data, sizeof(codec_data)); + + return PTR_ERR_OR_ZERO(mhdp->audio_pdev); +} + +void cdns_mhdp_unregister_audio_driver(struct device *dev) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev); + + platform_device_unregister(mhdp->audio_pdev); +} diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-cec.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-cec.c new file mode 100644 index 000000000000..18ae36cd3668 --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-cec.c @@ -0,0 +1,365 @@ +/* + * Copyright 2019-2020 NXP + * + * 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 <linux/module.h> +#include <linux/workqueue.h> +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <drm/bridge/cdns-mhdp.h> + +#define CEC_NAME "cdns-mhdp-cec" + +#define REG_ADDR_OFF 4 + +/* regsiter define */ +#define TX_MSG_HEADER 0x33800 +#define TX_MSG_LENGTH 0x33840 +#define TX_MSG_CMD 0x33844 +#define RX_MSG_CMD 0x33850 +#define RX_CLEAR_BUF 0x33854 +#define LOGICAL_ADDRESS_LA0 0x33858 + +#define CLK_DIV_MSB 0x3386c +#define CLK_DIV_LSB 0x33870 +#define RX_MSG_DATA1 0x33900 +#define RX_MSG_LENGTH 0x33940 +#define RX_MSG_STATUS 0x33944 +#define NUM_OF_MSG_RX_BUF 0x33948 +#define TX_MSG_STATUS 0x3394c +#define DB_L_TIMER 0x33980 + +/** + * CEC Transceiver operation. + */ +enum { + CEC_TX_STOP, + CEC_TX_TRANSMIT, + CEC_TX_ABORT, + CEC_TX_ABORT_AND_TRANSMIT +}; + +/** + * CEC Transceiver status. + */ +enum { + CEC_STS_IDLE, + CEC_STS_BUSY, + CEC_STS_SUCCESS, + CEC_STS_ERROR +}; + +/** + * CEC Receiver operation. + */ +enum { + CEC_RX_STOP, + CEC_RX_READ, + CEC_RX_DISABLE, + CEC_RX_ABORT_AND_CLR_FIFO +}; +/** + * Maximum number of Messages in the RX Buffers. + */ +#define CEC_MAX_RX_MSGS 2 + +static u32 mhdp_cec_read(struct cdns_mhdp_cec *cec, u32 offset) +{ + u32 val; + + mutex_lock(cec->iolock); + + if (cec->bus_type == BUS_TYPE_LOW4K_HDMI_RX) { + /* Remap address to low 4K HDMI RX */ + writel(offset >> 12, cec->regs_sec + 4); + val = readl((offset & 0xfff) + cec->regs_base); + } else if (cec->bus_type == BUS_TYPE_LOW4K_APB) { + /* Remap address to low 4K memory */ + writel(offset >> 12, cec->regs_sec + 8); + val = readl((offset & 0xfff) + cec->regs_base); + } else + val = readl(cec->regs_base + offset); + + mutex_unlock(cec->iolock); + + return val; +} + +static void mhdp_cec_write(struct cdns_mhdp_cec *cec, u32 offset, u32 val) +{ + mutex_lock(cec->iolock); + + if (cec->bus_type == BUS_TYPE_LOW4K_HDMI_RX) { + /* Remap address to low 4K SAPB bus */ + writel(offset >> 12, cec->regs_sec + 4); + writel(val, (offset & 0xfff) + cec->regs_base); + } else if (cec->bus_type == BUS_TYPE_LOW4K_APB) { + /* Remap address to low 4K memory */ + writel(offset >> 12, cec->regs_sec + 8); + writel(val, (offset & 0xfff) + cec->regs_base); + } else if (cec->bus_type == BUS_TYPE_NORMAL_SAPB) + writel(val, cec->regs_sec + offset); + else + writel(val, cec->regs_base + offset); + + mutex_unlock(cec->iolock); +} + +static u32 mhdp_get_fw_clk(struct cdns_mhdp_cec *cec) +{ + return mhdp_cec_read(cec, SW_CLK_H); +} + +static void mhdp_cec_clear_rx_buffer(struct cdns_mhdp_cec *cec) +{ + mhdp_cec_write(cec, RX_CLEAR_BUF, 1); + mhdp_cec_write(cec, RX_CLEAR_BUF, 0); +} + +static void mhdp_cec_set_divider(struct cdns_mhdp_cec *cec) +{ + u32 clk_div; + + /* Set clock divider */ + clk_div = mhdp_get_fw_clk(cec) * 10; + + mhdp_cec_write(cec, CLK_DIV_MSB, + (clk_div >> 8) & 0xFF); + mhdp_cec_write(cec, CLK_DIV_LSB, clk_div & 0xFF); +} + +static u32 mhdp_cec_read_message(struct cdns_mhdp_cec *cec) +{ + struct cec_msg *msg = &cec->msg; + int len; + int i; + + mhdp_cec_write(cec, RX_MSG_CMD, CEC_RX_READ); + + len = mhdp_cec_read(cec, RX_MSG_LENGTH); + msg->len = len + 1; + dev_dbg(cec->dev, "RX MSG len =%d\n", len); + + /* Read RX MSG bytes */ + for (i = 0; i < msg->len; ++i) { + msg->msg[i] = (u8) mhdp_cec_read(cec, RX_MSG_DATA1 + (i * REG_ADDR_OFF)); + dev_dbg(cec->dev, "RX MSG[%d]=0x%x\n", i, msg->msg[i]); + } + + mhdp_cec_write(cec, RX_MSG_CMD, CEC_RX_STOP); + + return true; +} + +static u32 mhdp_cec_write_message(struct cdns_mhdp_cec *cec, struct cec_msg *msg) +{ + u8 i; + + mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_STOP); + + if (msg->len > CEC_MAX_MSG_SIZE) { + dev_err(cec->dev, "Invalid MSG size!\n"); + return -EINVAL; + } + + for (i = 0; i < msg->len; ++i) + printk("msg[%d]=0x%x\n",i, msg->msg[i]); + + /* Write Message to register */ + for (i = 0; i < msg->len; ++i) { + mhdp_cec_write(cec, TX_MSG_HEADER + (i * REG_ADDR_OFF), + msg->msg[i]); + } + /* Write Message Length (payload + opcode) */ + mhdp_cec_write(cec, TX_MSG_LENGTH, msg->len - 1); + + mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_TRANSMIT); + + return true; +} + +static int mhdp_cec_set_logical_addr(struct cdns_mhdp_cec *cec, u32 la) +{ + u8 la_reg; + u8 i; + + if (la == CEC_LOG_ADDR_INVALID) { + /* invalid all LA address */ + for (i = 0; i < CEC_MAX_LOG_ADDRS; i++) + mhdp_cec_write(cec, LOGICAL_ADDRESS_LA0 + (i * REG_ADDR_OFF), 0); + return 0; + } + + /* In fact cdns mhdp cec could support max 5 La address */ + for (i = 0; i < CEC_MAX_LOG_ADDRS; i++) { + la_reg = mhdp_cec_read(cec, LOGICAL_ADDRESS_LA0 + (i * REG_ADDR_OFF)); + /* Check LA already used */ + if (la_reg & 0x10) + continue; + + if ((la_reg & 0xF) == la) { + dev_warn(cec->dev, "Warning. LA already in use.\n"); + return 0; + } + + la = (la & 0xF) | (1 << 4); + + mhdp_cec_write(cec, LOGICAL_ADDRESS_LA0 + (i * REG_ADDR_OFF), la); + return 0; + } + + dev_warn(cec->dev, "All LA in use\n"); + + return -ENXIO; +} + +static int mhdp_cec_poll_worker(void *_cec) +{ + struct cdns_mhdp_cec *cec = (struct cdns_mhdp_cec *)_cec; + int num_rx_msgs, i; + int sts; + + set_freezable(); + + for (;;) { + if (kthread_freezable_should_stop(NULL)) + break; + + /* Check TX State */ + sts = mhdp_cec_read(cec, TX_MSG_STATUS); + switch (sts) { + case CEC_STS_SUCCESS: + cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0, + 0); + mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_STOP); + break; + case CEC_STS_ERROR: + mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_STOP); + cec_transmit_done(cec->adap, + CEC_TX_STATUS_MAX_RETRIES | + CEC_TX_STATUS_NACK, 0, 1, 0, 0); + break; + case CEC_STS_BUSY: + default: + break; + } + + /* Check RX State */ + sts = mhdp_cec_read(cec, RX_MSG_STATUS); + num_rx_msgs = mhdp_cec_read(cec, NUM_OF_MSG_RX_BUF); + switch (sts) { + case CEC_STS_SUCCESS: + if (num_rx_msgs == 0xf) + num_rx_msgs = CEC_MAX_RX_MSGS; + + if (num_rx_msgs > CEC_MAX_RX_MSGS) { + dev_err(cec->dev, "Error rx msg num %d\n", + num_rx_msgs); + mhdp_cec_clear_rx_buffer(cec); + break; + } + + /* Rx FIFO Depth 2 RX MSG */ + for (i = 0; i < num_rx_msgs; i++) { + mhdp_cec_read_message(cec); + cec->msg.rx_status = CEC_RX_STATUS_OK; + cec_received_msg(cec->adap, &cec->msg); + } + break; + default: + break; + } + + if (!kthread_should_stop()) + schedule_timeout_idle(20); + } + + return 0; +} + +static int mhdp_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct cdns_mhdp_cec *cec = cec_get_drvdata(adap); + + if (enable) { + mhdp_cec_write(cec, DB_L_TIMER, 0x10); + mhdp_cec_set_divider(cec); + } else + mhdp_cec_set_divider(cec); + + return 0; +} + +static int mhdp_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct cdns_mhdp_cec *cec = cec_get_drvdata(adap); + + return mhdp_cec_set_logical_addr(cec, addr); +} + +static int mhdp_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct cdns_mhdp_cec *cec = cec_get_drvdata(adap); + + mhdp_cec_write_message(cec, msg); + + return 0; +} + +static const struct cec_adap_ops cdns_mhdp_cec_adap_ops = { + .adap_enable = mhdp_cec_adap_enable, + .adap_log_addr = mhdp_cec_adap_log_addr, + .adap_transmit = mhdp_cec_adap_transmit, +}; + +int cdns_mhdp_register_cec_driver(struct cdns_mhdp_cec *cec) +{ + int ret; + + cec->adap = cec_allocate_adapter(&cdns_mhdp_cec_adap_ops, cec, + CEC_NAME, + CEC_CAP_PHYS_ADDR | CEC_CAP_LOG_ADDRS | + CEC_CAP_TRANSMIT | CEC_CAP_PASSTHROUGH + | CEC_CAP_RC, CEC_MAX_LOG_ADDRS); + ret = PTR_ERR_OR_ZERO(cec->adap); + if (ret) + return ret; + ret = cec_register_adapter(cec->adap, cec->dev); + if (ret) { + cec_delete_adapter(cec->adap); + return ret; + } + + cec->cec_worker = kthread_create(mhdp_cec_poll_worker, cec, "cdns-mhdp-cec"); + if (IS_ERR(cec->cec_worker)) + dev_err(cec->dev, "failed create hdp cec thread\n"); + + wake_up_process(cec->cec_worker); + + dev_dbg(cec->dev, "CEC successfuly probed\n"); + return 0; +} + +int cdns_mhdp_unregister_cec_driver(struct cdns_mhdp_cec *cec) +{ + if (cec->cec_worker) { + kthread_stop(cec->cec_worker); + cec->cec_worker = NULL; + } + cec_unregister_adapter(cec->adap); + return 0; +} + +MODULE_AUTHOR("Sandor.Yu@NXP.com"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("NXP CDNS MHDP HDMI CEC driver"); diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-common.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-common.c new file mode 100644 index 000000000000..35a37f6b6d6f --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-common.c @@ -0,0 +1,853 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author: Chris Zhong <zyw@rock-chips.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/reset.h> + +#include <asm/unaligned.h> + +#include <drm/bridge/cdns-mhdp.h> +#include <drm/drm_modes.h> +#include <drm/drm_print.h> +#include <linux/regmap.h> + +#include "cdns-mhdp.h" + +#define CDNS_DP_SPDIF_CLK 200000000 +#define FW_ALIVE_TIMEOUT_US 1000000 +u32 cdns_mhdp_bus_read(struct cdns_mhdp_device *mhdp, u32 offset) +{ + u32 val; + + mutex_lock(&mhdp->iolock); + + if (mhdp->bus_type == BUS_TYPE_LOW4K_SAPB) { + /* Remap address to low 4K SAPB bus */ + writel(offset >> 12, mhdp->regs_sec + 0xc); + val = readl((offset & 0xfff) + mhdp->regs_base); + } else if (mhdp->bus_type == BUS_TYPE_LOW4K_APB) { + /* Remap address to low 4K memory */ + writel(offset >> 12, mhdp->regs_sec + 8); + val = readl((offset & 0xfff) + mhdp->regs_base); + } else if (mhdp->bus_type == BUS_TYPE_NORMAL_SAPB) + val = readl(mhdp->regs_sec + offset); + else + val = readl(mhdp->regs_base + offset); + + mutex_unlock(&mhdp->iolock); + + return val; +} +EXPORT_SYMBOL(cdns_mhdp_bus_read); + +void cdns_mhdp_bus_write(u32 val, struct cdns_mhdp_device *mhdp, u32 offset) +{ + mutex_lock(&mhdp->iolock); + + if (mhdp->bus_type == BUS_TYPE_LOW4K_SAPB) { + /* Remap address to low 4K SAPB bus */ + writel(offset >> 12, mhdp->regs_sec + 0xc); + writel(val, (offset & 0xfff) + mhdp->regs_base); + } else if (mhdp->bus_type == BUS_TYPE_LOW4K_APB) { + /* Remap address to low 4K memory */ + writel(offset >> 12, mhdp->regs_sec + 8); + writel(val, (offset & 0xfff) + mhdp->regs_base); + } else if (mhdp->bus_type == BUS_TYPE_NORMAL_SAPB) + writel(val, mhdp->regs_sec + offset); + else + writel(val, mhdp->regs_base + offset); + + mutex_unlock(&mhdp->iolock); +} +EXPORT_SYMBOL(cdns_mhdp_bus_write); + +void cdns_mhdp_set_fw_clk(struct cdns_mhdp_device *mhdp, unsigned long clk) +{ + cdns_mhdp_bus_write(clk / 1000000, mhdp, SW_CLK_H); +} +EXPORT_SYMBOL(cdns_mhdp_set_fw_clk); + +void cdns_mhdp_clock_reset(struct cdns_mhdp_device *mhdp) +{ + u32 val; + + val = DPTX_FRMR_DATA_CLK_RSTN_EN | + DPTX_FRMR_DATA_CLK_EN | + DPTX_PHY_DATA_RSTN_EN | + DPTX_PHY_DATA_CLK_EN | + DPTX_PHY_CHAR_RSTN_EN | + DPTX_PHY_CHAR_CLK_EN | + SOURCE_AUX_SYS_CLK_RSTN_EN | + SOURCE_AUX_SYS_CLK_EN | + DPTX_SYS_CLK_RSTN_EN | + DPTX_SYS_CLK_EN | + CFG_DPTX_VIF_CLK_RSTN_EN | + CFG_DPTX_VIF_CLK_EN; + cdns_mhdp_bus_write(val, mhdp, SOURCE_DPTX_CAR); + + val = SOURCE_PHY_RSTN_EN | SOURCE_PHY_CLK_EN; + cdns_mhdp_bus_write(val, mhdp, SOURCE_PHY_CAR); + + val = SOURCE_PKT_SYS_RSTN_EN | + SOURCE_PKT_SYS_CLK_EN | + SOURCE_PKT_DATA_RSTN_EN | + SOURCE_PKT_DATA_CLK_EN; + cdns_mhdp_bus_write(val, mhdp, SOURCE_PKT_CAR); + + val = SPDIF_CDR_CLK_RSTN_EN | + SPDIF_CDR_CLK_EN | + SOURCE_AIF_SYS_RSTN_EN | + SOURCE_AIF_SYS_CLK_EN | + SOURCE_AIF_CLK_RSTN_EN | + SOURCE_AIF_CLK_EN; + cdns_mhdp_bus_write(val, mhdp, SOURCE_AIF_CAR); + + val = SOURCE_CIPHER_SYSTEM_CLK_RSTN_EN | + SOURCE_CIPHER_SYS_CLK_EN | + SOURCE_CIPHER_CHAR_CLK_RSTN_EN | + SOURCE_CIPHER_CHAR_CLK_EN; + cdns_mhdp_bus_write(val, mhdp, SOURCE_CIPHER_CAR); + + val = SOURCE_CRYPTO_SYS_CLK_RSTN_EN | + SOURCE_CRYPTO_SYS_CLK_EN; + cdns_mhdp_bus_write(val, mhdp, SOURCE_CRYPTO_CAR); + + /* enable Mailbox and PIF interrupt */ + cdns_mhdp_bus_write(0, mhdp, APB_INT_MASK); +} +EXPORT_SYMBOL(cdns_mhdp_clock_reset); + +bool cdns_mhdp_check_alive(struct cdns_mhdp_device *mhdp) +{ + u32 alive, newalive; + u8 retries_left = 50; + + alive = cdns_mhdp_bus_read(mhdp, KEEP_ALIVE); + + while (retries_left--) { + msleep(1); + + newalive = cdns_mhdp_bus_read(mhdp, KEEP_ALIVE); + if (alive == newalive) + continue; + return true; + } + return false; +} +EXPORT_SYMBOL(cdns_mhdp_check_alive); + +int mhdp_mailbox_read(struct cdns_mhdp_device *mhdp) +{ + int val, ret; + + ret = mhdp_readx_poll_timeout(cdns_mhdp_bus_read, mhdp, MAILBOX_EMPTY_ADDR, + val, !val, MAILBOX_RETRY_US, + MAILBOX_TIMEOUT_US); + if (ret < 0) + return ret; + + return cdns_mhdp_bus_read(mhdp, MAILBOX0_RD_DATA) & 0xff; +} + +static int mhdp_mailbox_write(struct cdns_mhdp_device *mhdp, u8 val) +{ + int ret, full; + + ret = mhdp_readx_poll_timeout(cdns_mhdp_bus_read, mhdp, MAILBOX_FULL_ADDR, + full, !full, MAILBOX_RETRY_US, + MAILBOX_TIMEOUT_US); + if (ret < 0) + return ret; + + cdns_mhdp_bus_write(val, mhdp, MAILBOX0_WR_DATA); + + return 0; +} + +int cdns_mhdp_mailbox_validate_receive(struct cdns_mhdp_device *mhdp, + u8 module_id, u8 opcode, + u16 req_size) +{ + u32 mbox_size, i; + u8 header[4]; + int ret; + + /* read the header of the message */ + for (i = 0; i < 4; i++) { + ret = mhdp_mailbox_read(mhdp); + if (ret < 0) + return ret; + + header[i] = ret; + } + + mbox_size = get_unaligned_be16(header + 2); + + if (opcode != header[0] || module_id != header[1] || + req_size != mbox_size) { + DRM_DEV_INFO(mhdp->dev, + "Hmmm spurious mailbox data maybe, cleaning out...%d:%d:%d vs %d:%d:%d\n", + module_id, opcode, req_size, header[1], + header[0], mbox_size); + /* + * If the message in mailbox is not what we want, we need to + * clear the mailbox by reading its contents. + */ + for (i = 0; i < mbox_size; i++) + if (mhdp_mailbox_read(mhdp) < 0) + break; + + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(cdns_mhdp_mailbox_validate_receive); + +int cdns_mhdp_mailbox_read_receive(struct cdns_mhdp_device *mhdp, + u8 *buff, u16 buff_size) +{ + u32 i; + int ret; + + for (i = 0; i < buff_size; i++) { + ret = mhdp_mailbox_read(mhdp); + if (ret < 0) + return ret; + + buff[i] = ret; + } + + return 0; +} +EXPORT_SYMBOL(cdns_mhdp_mailbox_read_receive); + +int cdns_mhdp_mailbox_send(struct cdns_mhdp_device *mhdp, u8 module_id, + u8 opcode, u16 size, u8 *message) +{ + u8 header[4]; + int ret, i; + + header[0] = opcode; + header[1] = module_id; + put_unaligned_be16(size, header + 2); + + for (i = 0; i < 4; i++) { + ret = mhdp_mailbox_write(mhdp, header[i]); + if (ret) + return ret; + } + + for (i = 0; i < size; i++) { + ret = mhdp_mailbox_write(mhdp, message[i]); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL(cdns_mhdp_mailbox_send); + +int cdns_mhdp_reg_read(struct cdns_mhdp_device *mhdp, u32 addr) +{ + u8 msg[4], resp[8]; + u32 val; + int ret; + + mutex_lock(&mhdp->api_lock); + + if (addr == 0) { + ret = -EINVAL; + goto err_reg_read; + } + + put_unaligned_be32(addr, msg); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL, + GENERAL_READ_REGISTER, + sizeof(msg), msg); + if (ret) + goto err_reg_read; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_GENERAL, + GENERAL_READ_REGISTER, + sizeof(resp)); + if (ret) + goto err_reg_read; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, resp, sizeof(resp)); + if (ret) + goto err_reg_read; + + /* Returned address value should be the same as requested */ + if (memcmp(msg, resp, sizeof(msg))) { + ret = -EINVAL; + goto err_reg_read; + } + + val = get_unaligned_be32(resp + 4); + + mutex_unlock(&mhdp->api_lock); + return val; +err_reg_read: + mutex_unlock(&mhdp->api_lock); + DRM_DEV_ERROR(mhdp->dev, "Failed to read register.\n"); + +mutex_unlock(&mhdp->api_lock); + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_reg_read); + +int cdns_mhdp_reg_write(struct cdns_mhdp_device *mhdp, u32 addr, u32 val) +{ + int ret; + u8 msg[8]; + + mutex_lock(&mhdp->api_lock); + + put_unaligned_be32(addr, msg); + put_unaligned_be32(val, msg + 4); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL, + GENERAL_WRITE_REGISTER, + sizeof(msg), msg); + mutex_unlock(&mhdp->api_lock); +return ret; +} +EXPORT_SYMBOL(cdns_mhdp_reg_write); + +int cdns_mhdp_reg_write_bit(struct cdns_mhdp_device *mhdp, u16 addr, + u8 start_bit, u8 bits_no, u32 val) +{ + int ret; + u8 field[8]; + + mutex_lock(&mhdp->api_lock); + + put_unaligned_be16(addr, field); + field[2] = start_bit; + field[3] = bits_no; + put_unaligned_be32(val, field + 4); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_WRITE_FIELD, sizeof(field), field); + mutex_unlock(&mhdp->api_lock); + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_reg_write_bit); + +int cdns_mhdp_load_firmware(struct cdns_mhdp_device *mhdp, const u32 *i_mem, + u32 i_size, const u32 *d_mem, u32 d_size) +{ + u32 reg; + int i, ret; + + /* reset ucpu before load firmware*/ + cdns_mhdp_bus_write(APB_IRAM_PATH | APB_DRAM_PATH | APB_XT_RESET, + mhdp, APB_CTRL); + + for (i = 0; i < i_size; i += 4) + cdns_mhdp_bus_write(*i_mem++, mhdp, ADDR_IMEM + i); + + for (i = 0; i < d_size; i += 4) + cdns_mhdp_bus_write(*d_mem++, mhdp, ADDR_DMEM + i); + + /* un-reset ucpu */ + cdns_mhdp_bus_write(0, mhdp, APB_CTRL); + + /* check the keep alive register to make sure fw working */ + ret = mhdp_readx_poll_timeout(cdns_mhdp_bus_read, mhdp, KEEP_ALIVE, + reg, reg, 2000, FW_ALIVE_TIMEOUT_US); + if (ret < 0) { + DRM_DEV_ERROR(mhdp->dev, "failed to loaded the FW reg = %x\n", + reg); + return -EINVAL; + } + + reg = cdns_mhdp_bus_read(mhdp, VER_L) & 0xff; + mhdp->fw_version = reg; + reg = cdns_mhdp_bus_read(mhdp, VER_H) & 0xff; + mhdp->fw_version |= reg << 8; + reg = cdns_mhdp_bus_read(mhdp, VER_LIB_L_ADDR) & 0xff; + mhdp->fw_version |= reg << 16; + reg = cdns_mhdp_bus_read(mhdp, VER_LIB_H_ADDR) & 0xff; + mhdp->fw_version |= reg << 24; + + DRM_DEV_DEBUG(mhdp->dev, "firmware version: %x\n", mhdp->fw_version); + + return 0; +} +EXPORT_SYMBOL(cdns_mhdp_load_firmware); + +int cdns_mhdp_set_firmware_active(struct cdns_mhdp_device *mhdp, bool enable) +{ + u8 msg[5]; + int ret, i; + + mutex_lock(&mhdp->api_lock); + + msg[0] = GENERAL_MAIN_CONTROL; + msg[1] = MB_MODULE_ID_GENERAL; + msg[2] = 0; + msg[3] = 1; + msg[4] = enable ? FW_ACTIVE : FW_STANDBY; + + for (i = 0; i < sizeof(msg); i++) { + ret = mhdp_mailbox_write(mhdp, msg[i]); + if (ret) + goto err_set_firmware_active; + } + + /* read the firmware state */ + for (i = 0; i < sizeof(msg); i++) { + ret = mhdp_mailbox_read(mhdp); + if (ret < 0) + goto err_set_firmware_active; + + msg[i] = ret; + } + + ret = 0; + +err_set_firmware_active: + if (ret < 0) + DRM_DEV_ERROR(mhdp->dev, "set firmware active failed\n"); + mutex_unlock(&mhdp->api_lock); + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_set_firmware_active); + +int cdns_mhdp_apb_conf(struct cdns_mhdp_device *mhdp, u8 sel) +{ + u8 status; + int ret; + + mutex_lock(&mhdp->api_lock); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL, GENERAL_BUS_SETTINGS, + sizeof(sel), &sel); + if (ret) + goto err_apb_conf; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_GENERAL, + GENERAL_BUS_SETTINGS, sizeof(status)); + if (ret) + goto err_apb_conf; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, &status, sizeof(status)); + if (ret) + goto err_apb_conf; + + mutex_unlock(&mhdp->api_lock); + + return status; + +err_apb_conf: + DRM_ERROR("apb conf failed: %d\n", ret); +mutex_unlock(&mhdp->api_lock); + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_apb_conf); + +int cdns_mhdp_set_host_cap(struct cdns_mhdp_device *mhdp) +{ + u8 msg[8]; + int ret, lane; + + mutex_lock(&mhdp->api_lock); + + msg[0] = drm_dp_link_rate_to_bw_code(mhdp->dp.rate); + msg[1] = mhdp->dp.num_lanes | SCRAMBLER_EN; + if (mhdp->dp.link_training_type == DP_TX_FULL_LINK_TRAINING) { + msg[2] = (VOLTAGE_LEVEL_3 & 0x3) | (mhdp->dp.force_vswing & 0x1) << 4; + msg[3] = (PRE_EMPHASIS_LEVEL_2 & 0x3) | (mhdp->dp.force_preemphasis & 0x1) << 4; + } else { + msg[2] = 0; + msg[3] = 0; + for (lane = 0; lane < mhdp->dp.num_lanes; lane++) { + msg[2] |= (mhdp->dp.vswing[lane] & 0x3) << (2 * lane); + msg[3] |= (mhdp->dp.preemphasis[lane] & 0x3) << (2 * lane); + } + } + msg[4] = PTS1 | PTS2 | PTS3 | PTS4; + msg[5] = mhdp->dp.link_training_type; + msg[6] = mhdp->lane_mapping; + msg[7] = ENHANCED; + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_SET_HOST_CAPABILITIES, + sizeof(msg), msg); + if (ret) + goto err_set_host_cap; + +/* TODO Sandor */ +// ret = cdns_mhdp_reg_write(mhdp, DP_AUX_SWAP_INVERSION_CONTROL, +// AUX_HOST_INVERT); + +err_set_host_cap: + if (ret) + DRM_DEV_ERROR(mhdp->dev, "set host cap failed: %d\n", ret); + mutex_unlock(&mhdp->api_lock); + + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_set_host_cap); + +int cdns_mhdp_event_config(struct cdns_mhdp_device *mhdp) +{ + u8 msg[5]; + int ret; + + mutex_lock(&mhdp->api_lock); + + memset(msg, 0, sizeof(msg)); + + msg[0] = MHDP_EVENT_ENABLE_HPD | MHDP_EVENT_ENABLE_TRAINING; + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_ENABLE_EVENT, sizeof(msg), msg); + if (ret) + DRM_DEV_ERROR(mhdp->dev, "set event config failed: %d\n", ret); + + mutex_unlock(&mhdp->api_lock); + + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_event_config); + +u32 cdns_mhdp_get_event(struct cdns_mhdp_device *mhdp) +{ + return cdns_mhdp_bus_read(mhdp, SW_EVENTS0); +} +EXPORT_SYMBOL(cdns_mhdp_get_event); + +int cdns_mhdp_read_hpd(struct cdns_mhdp_device *mhdp) +{ + u8 status; + int ret; + + mutex_lock(&mhdp->api_lock); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL, GENERAL_GET_HPD_STATE, + 0, NULL); + if (ret) + goto err_get_hpd; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_GENERAL, + GENERAL_GET_HPD_STATE, sizeof(status)); + if (ret) + goto err_get_hpd; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, &status, sizeof(status)); + if (ret) + goto err_get_hpd; + + mutex_unlock(&mhdp->api_lock); + + return status; + +err_get_hpd: + DRM_ERROR("read hpd failed: %d\n", ret); + mutex_unlock(&mhdp->api_lock); + + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_read_hpd); + +int cdns_mhdp_get_edid_block(void *data, u8 *edid, + unsigned int block, size_t length) +{ + struct cdns_mhdp_device *mhdp = data; + u8 msg[2], reg[2], i; + int ret; + + mutex_lock(&mhdp->api_lock); + + for (i = 0; i < 4; i++) { + msg[0] = block / 2; + msg[1] = block % 2; + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_GET_EDID, sizeof(msg), msg); + if (ret) + continue; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, + MB_MODULE_ID_DP_TX, + DPTX_GET_EDID, + sizeof(reg) + length); + if (ret) + continue; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg)); + if (ret) + continue; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, edid, length); + if (ret) + continue; + + if (reg[0] == length && reg[1] == block / 2) + break; + } + + if (ret) + DRM_DEV_ERROR(mhdp->dev, "get block[%d] edid failed: %d\n", + block, ret); + + mutex_unlock(&mhdp->api_lock); + + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_get_edid_block); + +int cdns_mhdp_set_video_status(struct cdns_mhdp_device *mhdp, int active) +{ + u8 msg; + int ret; + + mutex_lock(&mhdp->api_lock); + + msg = !!active; + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_SET_VIDEO, sizeof(msg), &msg); + if (ret) + DRM_DEV_ERROR(mhdp->dev, "set video status failed: %d\n", ret); + + mutex_unlock(&mhdp->api_lock); + + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_set_video_status); + +static int mhdp_get_msa_misc(struct video_info *video, + struct drm_display_mode *mode) +{ + u32 msa_misc; + u8 val[2] = {0}; + + switch (video->color_fmt) { + case PXL_RGB: + case Y_ONLY: + val[0] = 0; + break; + /* set YUV default color space conversion to BT601 */ + case YCBCR_4_4_4: + val[0] = 6 + BT_601 * 8; + break; + case YCBCR_4_2_2: + val[0] = 5 + BT_601 * 8; + break; + case YCBCR_4_2_0: + val[0] = 5; + break; + } + + switch (video->color_depth) { + case 6: + val[1] = 0; + break; + case 8: + val[1] = 1; + break; + case 10: + val[1] = 2; + break; + case 12: + val[1] = 3; + break; + case 16: + val[1] = 4; + break; + } + + msa_misc = 2 * val[0] + 32 * val[1] + + ((video->color_fmt == Y_ONLY) ? (1 << 14) : 0); + + return msa_misc; +} + +int cdns_mhdp_config_video(struct cdns_mhdp_device *mhdp) +{ + struct video_info *video = &mhdp->video_info; + struct drm_display_mode *mode = &mhdp->mode; + u64 symbol; + u32 val, link_rate, rem; + u8 bit_per_pix, tu_size_reg = TU_SIZE; + int ret; + + bit_per_pix = (video->color_fmt == YCBCR_4_2_2) ? + (video->color_depth * 2) : (video->color_depth * 3); + + link_rate = mhdp->dp.rate / 1000; + + ret = cdns_mhdp_reg_write(mhdp, BND_HSYNC2VSYNC, VIF_BYPASS_INTERLACE); + if (ret) + goto err_config_video; + + ret = cdns_mhdp_reg_write(mhdp, HSYNC2VSYNC_POL_CTRL, 0); + if (ret) + goto err_config_video; + + /* + * get a best tu_size and valid symbol: + * 1. chose Lclk freq(162Mhz, 270Mhz, 540Mhz), set TU to 32 + * 2. calculate VS(valid symbol) = TU * Pclk * Bpp / (Lclk * Lanes) + * 3. if VS > *.85 or VS < *.1 or VS < 2 or TU < VS + 4, then set + * TU += 2 and repeat 2nd step. + */ + do { + tu_size_reg += 2; + symbol = (u64) tu_size_reg * mode->clock * bit_per_pix; + do_div(symbol, mhdp->dp.num_lanes * link_rate * 8); + rem = do_div(symbol, 1000); + if (tu_size_reg > 64) { + ret = -EINVAL; + DRM_DEV_ERROR(mhdp->dev, + "tu error, clk:%d, lanes:%d, rate:%d\n", + mode->clock, mhdp->dp.num_lanes, + link_rate); + goto err_config_video; + } + } while ((symbol <= 1) || (tu_size_reg - symbol < 4) || + (rem > 850) || (rem < 100)); + + val = symbol + (tu_size_reg << 8); + val |= TU_CNT_RST_EN; + ret = cdns_mhdp_reg_write(mhdp, DP_FRAMER_TU, val); + if (ret) + goto err_config_video; + + /* set the FIFO Buffer size */ + val = div_u64(mode->clock * (symbol + 1), 1000) + link_rate; + val /= (mhdp->dp.num_lanes * link_rate); + val = div_u64(8 * (symbol + 1), bit_per_pix) - val; + val += 2; + ret = cdns_mhdp_reg_write(mhdp, DP_VC_TABLE(15), val); + + switch (video->color_depth) { + case 6: + val = BCS_6; + break; + case 8: + val = BCS_8; + break; + case 10: + val = BCS_10; + break; + case 12: + val = BCS_12; + break; + case 16: + val = BCS_16; + break; + } + + val += video->color_fmt << 8; + ret = cdns_mhdp_reg_write(mhdp, DP_FRAMER_PXL_REPR, val); + if (ret) + goto err_config_video; + + val = video->h_sync_polarity ? DP_FRAMER_SP_HSP : 0; + val |= video->v_sync_polarity ? DP_FRAMER_SP_VSP : 0; + ret = cdns_mhdp_reg_write(mhdp, DP_FRAMER_SP, val); + if (ret) + goto err_config_video; + + val = (mode->hsync_start - mode->hdisplay) << 16; + val |= mode->htotal - mode->hsync_end; + ret = cdns_mhdp_reg_write(mhdp, DP_FRONT_BACK_PORCH, val); + if (ret) + goto err_config_video; + + val = mode->hdisplay * bit_per_pix / 8; + ret = cdns_mhdp_reg_write(mhdp, DP_BYTE_COUNT, val); + if (ret) + goto err_config_video; + + val = mode->htotal | ((mode->htotal - mode->hsync_start) << 16); + ret = cdns_mhdp_reg_write(mhdp, MSA_HORIZONTAL_0, val); + if (ret) + goto err_config_video; + + val = mode->hsync_end - mode->hsync_start; + val |= (mode->hdisplay << 16) | (video->h_sync_polarity << 15); + ret = cdns_mhdp_reg_write(mhdp, MSA_HORIZONTAL_1, val); + if (ret) + goto err_config_video; + + val = mode->vtotal; + val |= (mode->vtotal - mode->vsync_start) << 16; + ret = cdns_mhdp_reg_write(mhdp, MSA_VERTICAL_0, val); + if (ret) + goto err_config_video; + + val = mode->vsync_end - mode->vsync_start; + val |= (mode->vdisplay << 16) | (video->v_sync_polarity << 15); + ret = cdns_mhdp_reg_write(mhdp, MSA_VERTICAL_1, val); + if (ret) + goto err_config_video; + + val = mhdp_get_msa_misc(video, mode); + ret = cdns_mhdp_reg_write(mhdp, MSA_MISC, val); + if (ret) + goto err_config_video; + + ret = cdns_mhdp_reg_write(mhdp, STREAM_CONFIG, 1); + if (ret) + goto err_config_video; + + val = mode->hsync_end - mode->hsync_start; + val |= mode->hdisplay << 16; + ret = cdns_mhdp_reg_write(mhdp, DP_HORIZONTAL, val); + if (ret) + goto err_config_video; + + val = mode->vdisplay; + val |= (mode->vtotal - mode->vsync_start) << 16; + ret = cdns_mhdp_reg_write(mhdp, DP_VERTICAL_0, val); + if (ret) + goto err_config_video; + + val = mode->vtotal; + ret = cdns_mhdp_reg_write(mhdp, DP_VERTICAL_1, val); + if (ret) + goto err_config_video; + + ret = cdns_mhdp_reg_write_bit(mhdp, DP_VB_ID, 2, 1, 0); + +err_config_video: + if (ret) + DRM_DEV_ERROR(mhdp->dev, "config video failed: %d\n", ret); + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_config_video); + +int cdns_phy_reg_write(struct cdns_mhdp_device *mhdp, u32 addr, u32 val) +{ + return cdns_mhdp_reg_write(mhdp, ADDR_PHY_AFE + (addr << 2), val); +} +EXPORT_SYMBOL(cdns_phy_reg_write); + +u32 cdns_phy_reg_read(struct cdns_mhdp_device *mhdp, u32 addr) +{ + return cdns_mhdp_reg_read(mhdp, ADDR_PHY_AFE + (addr << 2)); +} +EXPORT_SYMBOL(cdns_phy_reg_read); diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-dp.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-dp.c new file mode 100644 index 000000000000..299e533d6b52 --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-dp.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <asm/unaligned.h> +#include <drm/bridge/cdns-mhdp.h> +#include <drm/drm_print.h> +#include <linux/io.h> + +#define LINK_TRAINING_TIMEOUT_MS 500 +#define LINK_TRAINING_RETRY_MS 20 + +int cdns_mhdp_dpcd_read(struct cdns_mhdp_device *mhdp, + u32 addr, u8 *data, u16 len) +{ + u8 msg[5], reg[5]; + int ret; + + put_unaligned_be16(len, msg); + put_unaligned_be24(addr, msg + 2); + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_READ_DPCD, sizeof(msg), msg); + if (ret) + goto err_dpcd_read; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX, + DPTX_READ_DPCD, + sizeof(reg) + len); + if (ret) + goto err_dpcd_read; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg)); + if (ret) + goto err_dpcd_read; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, data, len); + +err_dpcd_read: + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_dpcd_read); + +int cdns_mhdp_i2c_read(struct cdns_mhdp_device *mhdp, u8 addr, u8 *data, + u16 len, u8 mot, u16 *respLength) +{ + u8 msg[5], reg[3]; + int ret; + + put_unaligned_be16(len, msg); + msg[2] = addr; + msg[3] = mot; + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_I2C_READ, sizeof(msg), msg); + if (ret) + goto err_i2c_read; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX, + DPTX_I2C_READ, + sizeof(reg) + len); + if (ret) + goto err_i2c_read; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg)); + if (ret) + goto err_i2c_read; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, data, len); + *respLength = (reg[0] << 8u) + reg[1]; + +err_i2c_read: + if (ret) + DRM_DEV_ERROR(mhdp->dev, "i2c read failed: %d\n", ret); + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_i2c_read); + +int cdns_mhdp_dpcd_write(struct cdns_mhdp_device *mhdp, u32 addr, u8 value) +{ + u8 msg[6], reg[5]; + int ret; + + put_unaligned_be16(1, msg); + put_unaligned_be24(addr, msg + 2); + msg[5] = value; + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_WRITE_DPCD, sizeof(msg), msg); + if (ret) + goto err_dpcd_write; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX, + DPTX_WRITE_DPCD, sizeof(reg)); + if (ret) + goto err_dpcd_write; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg)); + if (ret) + goto err_dpcd_write; + + if (addr != get_unaligned_be24(reg + 2)) + ret = -EINVAL; + +err_dpcd_write: + if (ret) + DRM_DEV_ERROR(mhdp->dev, "dpcd write failed: %d\n", ret); + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_dpcd_write); + +int cdns_mhdp_i2c_write(struct cdns_mhdp_device *mhdp, u8 addr, u8 *value, + u8 mot, u16 len, u16 *respLength) +{ + u8 msg[4+DP_AUX_MAX_PAYLOAD_BYTES], reg[3]; + int ret; + + put_unaligned_be16(len, msg); + msg[2] = addr; + msg[3] = mot; + memcpy(&msg[4], value, len); + + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_I2C_WRITE, sizeof(msg), msg); + if (ret) + goto err_i2c_write; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX, + DPTX_I2C_WRITE, sizeof(reg)); + if (ret) + goto err_i2c_write; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg)); + if (ret) + goto err_i2c_write; + + if (addr != reg[2]) + ret = -EINVAL; + + *respLength = (reg[0]<<8u) + reg[1]; + +err_i2c_write: + if (ret) + DRM_DEV_ERROR(mhdp->dev, "i2c write failed: %d\n", ret); + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_i2c_write); + + +int cdns_mhdp_get_last_i2c_status(struct cdns_mhdp_device *mhdp, u8 *resp) +{ + u8 status[1]; + int ret; + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_GET_LAST_I2C_STATUS, 0, NULL); + if (ret) + goto err_get_i2c_status; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX, + DPTX_GET_LAST_I2C_STATUS, + sizeof(status)); + if (ret) + goto err_get_i2c_status; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, status, sizeof(status)); + if (ret) + goto err_get_i2c_status; + + *resp = status[0]; + +err_get_i2c_status: + if (ret) + DRM_DEV_ERROR(mhdp->dev, "get i2c status failed: %d\n", + ret); + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_get_last_i2c_status); + +static int cdns_mhdp_training_start(struct cdns_mhdp_device *mhdp) +{ + unsigned long timeout; + u8 msg, event[2]; + int ret; + + msg = LINK_TRAINING_RUN; + + /* start training */ + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_TRAINING_CONTROL, sizeof(msg), &msg); + if (ret) + goto err_training_start; + + timeout = jiffies + msecs_to_jiffies(LINK_TRAINING_TIMEOUT_MS); + while (time_before(jiffies, timeout)) { + msleep(LINK_TRAINING_RETRY_MS); + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_READ_EVENT, 0, NULL); + if (ret) + goto err_training_start; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, + MB_MODULE_ID_DP_TX, + DPTX_READ_EVENT, + sizeof(event)); + if (ret) + goto err_training_start; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, event, + sizeof(event)); + if (ret) + goto err_training_start; + + if (event[1] & CLK_RECOVERY_FAILED) + DRM_DEV_ERROR(mhdp->dev, "clock recovery failed\n"); + else if (event[1] & EQ_PHASE_FINISHED) + return 0; + } + + ret = -ETIMEDOUT; + +err_training_start: + DRM_DEV_ERROR(mhdp->dev, "training failed: %d\n", ret); + return ret; +} + +static int cdns_mhdp_get_training_status(struct cdns_mhdp_device *mhdp) +{ + u8 status[13]; + int ret; + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX, + DPTX_READ_LINK_STAT, 0, NULL); + if (ret) + goto err_get_training_status; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX, + DPTX_READ_LINK_STAT, + sizeof(status)); + if (ret) + goto err_get_training_status; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, status, sizeof(status)); + if (ret) + goto err_get_training_status; + + mhdp->dp.rate = drm_dp_bw_code_to_link_rate(status[0]); + mhdp->dp.num_lanes = status[1]; + +err_get_training_status: + if (ret) + DRM_DEV_ERROR(mhdp->dev, "get training status failed: %d\n", + ret); + return ret; +} + +int cdns_mhdp_train_link(struct cdns_mhdp_device *mhdp) +{ + int ret; + + ret = cdns_mhdp_training_start(mhdp); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed to start training %d\n", + ret); + return ret; + } + + ret = cdns_mhdp_get_training_status(mhdp); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed to get training stat %d\n", + ret); + return ret; + } + + DRM_DEV_DEBUG_KMS(mhdp->dev, "rate:0x%x, lanes:%d\n", mhdp->dp.rate, + mhdp->dp.num_lanes); + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_train_link); diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.c new file mode 100644 index 000000000000..d999a53ddf0a --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.c @@ -0,0 +1,350 @@ +/* + * Cadence HDCP API driver + * + * Copyright 2021 NXP + * + * 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. + */ + +#include <asm/unaligned.h> +#include <drm/bridge/cdns-mhdp.h> +#include <drm/drm_print.h> + +#include "cdns-mhdp.h" + +static u32 mhdp_hdcp_bus_read(struct cdns_mhdp_device *mhdp, u32 offset) +{ + u32 val; + + mutex_lock(&mhdp->iolock); + + if (mhdp->bus_type == BUS_TYPE_LOW4K_APB) { + /* Remap address to low 4K APB bus */ + writel(offset >> 12, mhdp->regs_sec + 8); + val = readl((offset & 0xfff) + mhdp->regs_base); + } else if (mhdp->bus_type == BUS_TYPE_NORMAL_APB) + val = readl(mhdp->regs_sec + offset); + else + val = readl(mhdp->regs_base + offset); + + mutex_unlock(&mhdp->iolock); + + return val; +} + +static void mhdp_hdcp_bus_write(u32 val, struct cdns_mhdp_device *mhdp, u32 offset) +{ + mutex_lock(&mhdp->iolock); + + if (mhdp->bus_type == BUS_TYPE_LOW4K_APB) { + /* Remap address to low 4K APB bus */ + writel(offset >> 12, mhdp->regs_sec + 8); + writel(val, (offset & 0xfff) + mhdp->regs_base); + } else if (mhdp->bus_type == BUS_TYPE_NORMAL_APB) + writel(val, mhdp->regs_sec + offset); + + mutex_unlock(&mhdp->iolock); +} + +static int mhdp_hdcp_mailbox_read(struct cdns_mhdp_device *mhdp) +{ + int val, ret; + + ret = mhdp_readx_poll_timeout(mhdp_hdcp_bus_read, mhdp, MAILBOX_EMPTY_ADDR, + val, !val, MAILBOX_RETRY_US, + MAILBOX_TIMEOUT_US); + if (ret < 0) + return ret; + + return mhdp_hdcp_bus_read(mhdp, MAILBOX0_RD_DATA) & 0xff; +} + +static int mhdp_hdcp_mailbox_write(struct cdns_mhdp_device *mhdp, u8 val) +{ + int ret, full; + + ret = mhdp_readx_poll_timeout(mhdp_hdcp_bus_read, mhdp, MAILBOX_FULL_ADDR, + full, !full, MAILBOX_RETRY_US, + MAILBOX_TIMEOUT_US); + if (ret < 0) + return ret; + + mhdp_hdcp_bus_write(val, mhdp, MAILBOX0_WR_DATA); + + return 0; +} + +static int mhdp_hdcp_mailbox_validate_receive(struct cdns_mhdp_device *mhdp, + u8 module_id, u8 opcode, u16 req_size) +{ + u32 mbox_size, i; + u8 header[4]; + int ret; + + /* read the header of the message */ + for (i = 0; i < 4; i++) { + ret = mhdp_hdcp_mailbox_read(mhdp); + if (ret < 0) + return ret; + + header[i] = ret; + } + + mbox_size = get_unaligned_be16(header + 2); + + if (opcode != header[0] || module_id != header[1] || + req_size != mbox_size) { + /* + * If the message in mailbox is not what we want, we need to + * clear the mailbox by reading its contents. + */ + for (i = 0; i < mbox_size; i++) + if (mhdp_hdcp_mailbox_read(mhdp) < 0) + break; + + return -EINVAL; + } + + return 0; +} + +static int mhdp_hdcp_mailbox_read_receive(struct cdns_mhdp_device *mhdp, + u8 *buff, u16 buff_size) +{ + u32 i; + int ret; + + for (i = 0; i < buff_size; i++) { + ret = mhdp_hdcp_mailbox_read(mhdp); + if (ret < 0) + return ret; + + buff[i] = ret; + } + + return 0; +} + +static int mhdp_hdcp_mailbox_send(struct cdns_mhdp_device *mhdp, u8 module_id, + u8 opcode, u16 size, u8 *message) +{ + u8 header[4]; + int ret, i; + + header[0] = opcode; + header[1] = module_id; + put_unaligned_be16(size, header + 2); + + for (i = 0; i < 4; i++) { + ret = mhdp_hdcp_mailbox_write(mhdp, header[i]); + if (ret) + return ret; + } + + for (i = 0; i < size; i++) { + ret = mhdp_hdcp_mailbox_write(mhdp, message[i]); + if (ret) + return ret; + } + + return 0; +} + +/* HDCP API */ +int cdns_mhdp_hdcp_tx_config(struct cdns_mhdp_device *mhdp, u8 config) +{ + int ret; + + mutex_lock(&mhdp->api_lock); + ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, + HDCP_TX_CONFIGURATION, sizeof(config), &config); + + mutex_unlock(&mhdp->api_lock); + + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_config); + +int cdns_mhdp_hdcp2_tx_respond_km(struct cdns_mhdp_device *mhdp, + u8 *msg, u16 len) +{ + int ret; + + mutex_lock(&mhdp->api_lock); + + ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, + HDCP2_TX_RESPOND_KM, len, msg); + mutex_unlock(&mhdp->api_lock); + + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_hdcp2_tx_respond_km); + +int cdns_mhdp_hdcp_tx_status_req(struct cdns_mhdp_device *mhdp, + u8 *status, u16 len) +{ + int ret; + + mutex_lock(&mhdp->api_lock); + + ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, + HDCP_TX_STATUS_CHANGE, 0, NULL); + if (ret) + goto err_tx_req; + + ret = mhdp_hdcp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDCP_TX, + HDCP_TX_STATUS_CHANGE, len); + if (ret) + goto err_tx_req; + + ret = mhdp_hdcp_mailbox_read_receive(mhdp, status, len); + if (ret) + goto err_tx_req; + +err_tx_req: + if (ret) + DRM_ERROR("hdcp tx status req failed: %d\n", ret); + mutex_unlock(&mhdp->api_lock); + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_status_req); + +int cdns_mhdp_hdcp2_tx_is_km_stored_req(struct cdns_mhdp_device *mhdp, u8 *data, u16 len) +{ + int ret; + + mutex_lock(&mhdp->api_lock); + + ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, + HDCP2_TX_IS_KM_STORED, 0, NULL); + if (ret) + goto err_is_km; + + ret = mhdp_hdcp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDCP_TX, + HDCP2_TX_IS_KM_STORED, len); + if (ret) + goto err_is_km; + + ret = mhdp_hdcp_mailbox_read_receive(mhdp, data, len); + +err_is_km: + if (ret) + DRM_ERROR("hdcp2 tx is km stored req failed: %d\n", ret); + mutex_unlock(&mhdp->api_lock); + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_hdcp2_tx_is_km_stored_req); + +int cdns_mhdp_hdcp2_tx_store_km(struct cdns_mhdp_device *mhdp, + u8 *resp, u16 len) +{ + int ret; + + mutex_lock(&mhdp->api_lock); + + ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, + HDCP2_TX_STORE_KM, 0, NULL); + if (ret) + goto err_store_km; + + ret = mhdp_hdcp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDCP_TX, + HDCP2_TX_STORE_KM, len); + if (ret) + goto err_store_km; + + ret = mhdp_hdcp_mailbox_read_receive(mhdp, resp, len); + +err_store_km: + mutex_unlock(&mhdp->api_lock); + + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_hdcp2_tx_store_km); + +int cdns_mhdp_hdcp_tx_is_receiver_id_valid(struct cdns_mhdp_device *mhdp, + u8 *rx_id, u8 *num) +{ + u32 mbox_size, i; + u8 header[4]; + u8 temp; + int ret; + + mutex_lock(&mhdp->api_lock); + + ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, + HDCP_TX_IS_RECEIVER_ID_VALID, 0, NULL); + if (ret) + goto err_rx_id; + + /* read the header of the message */ + for (i = 0; i < 4; i++) { + ret = mhdp_hdcp_mailbox_read(mhdp); + if (ret < 0) { + + mutex_unlock(&mhdp->api_lock); + return ret; + } + + header[i] = ret; + } + + mbox_size = get_unaligned_be16(header + 2); + + if (header[0] != HDCP_TX_IS_RECEIVER_ID_VALID || + header[1] != MB_MODULE_ID_HDCP_TX){ + + mutex_unlock(&mhdp->api_lock); + return -EINVAL; + } + /* First get num of receivers */ + ret = mhdp_hdcp_mailbox_read_receive(mhdp, num, 1); + if (ret) + goto err_rx_id; + + /* skip second data */ + ret = mhdp_hdcp_mailbox_read_receive(mhdp, &temp, 1); + if (ret) + goto err_rx_id; + + /* get receivers ID */ + ret = mhdp_hdcp_mailbox_read_receive(mhdp, rx_id, mbox_size - 2); + +err_rx_id: + mutex_unlock(&mhdp->api_lock); + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_is_receiver_id_valid); + +int cdns_mhdp_hdcp_tx_respond_receiver_id_valid( + struct cdns_mhdp_device *mhdp, u8 val) +{ + int ret; + + mutex_lock(&mhdp->api_lock); + + ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, + HDCP_TX_RESPOND_RECEIVER_ID_VALID, + sizeof(val), &val); + mutex_unlock(&mhdp->api_lock); + + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_respond_receiver_id_valid); + +int cdns_mhdp_hdcp_tx_reauth(struct cdns_mhdp_device *mhdp, u8 msg) +{ + int ret; + + mutex_lock(&mhdp->api_lock); + + ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, + HDCP_TX_DO_AUTH_REQ, sizeof(msg), &msg); + + mutex_unlock(&mhdp->api_lock); + + return ret; +} +EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_reauth); diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.h b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.h new file mode 100644 index 000000000000..558fc07b3ce7 --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.h @@ -0,0 +1,29 @@ +/* + * Copyright 2021 NXP + * + * 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. + * + */ + +#ifndef CDNS_HDMI_HDCP_H +#define CDNS_HDMI_HDCP_H + +int cdns_mhdp_hdcp2_tx_respond_km(struct cdns_mhdp_device *mhdp, + u8 *msg, u16 len); +int cdns_mhdp_hdcp_tx_config(struct cdns_mhdp_device *mhdp, u8 config); +int cdns_mhdp_hdcp_tx_status_req(struct cdns_mhdp_device *mhdp, + u8 *status, u16 len); +int cdns_mhdp_hdcp2_tx_is_km_stored_req(struct cdns_mhdp_device *mhdp, u8 *data, u16 len); +int cdns_mhdp_hdcp2_tx_store_km(struct cdns_mhdp_device *mhdp, + u8 *reg, u16 len); +int cdns_mhdp_hdcp_tx_is_receiver_id_valid(struct cdns_mhdp_device *mhdp, + u8 *rx_id, u8 *num); +int cdns_mhdp_hdcp_tx_respond_receiver_id_valid(struct cdns_mhdp_device *mhdp, + u8 val); +int cdns_mhdp_hdcp_tx_test_keys(struct cdns_mhdp_device *mhdp, u8 type, u8 resp); +int cdns_mhdp_hdcp_tx_reauth(struct cdns_mhdp_device *mhdp, u8 msg); + +#endif /* CDNS_HDMI_HDCP_H */ diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdmi.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdmi.c new file mode 100644 index 000000000000..47c853781d84 --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdmi.c @@ -0,0 +1,332 @@ +/* + * Copyright 2019-2021 NXP + * + * 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. + */ + +#include <drm/drm_vblank.h> +#include <drm/drm_print.h> +#include <linux/io.h> +#include <drm/bridge/cdns-mhdp.h> +#include <linux/regmap.h> + +void cdns_mhdp_infoframe_set(struct cdns_mhdp_device *mhdp, + u8 entry_id, u8 packet_len, u8 *packet, u8 packet_type) +{ + u32 *packet32, len32; + u32 val, i; + + /* invalidate entry */ + val = F_ACTIVE_IDLE_TYPE(1) | F_PKT_ALLOC_ADDRESS(entry_id); + cdns_mhdp_bus_write(val, mhdp, SOURCE_PIF_PKT_ALLOC_REG); + cdns_mhdp_bus_write(F_PKT_ALLOC_WR_EN(1), mhdp, SOURCE_PIF_PKT_ALLOC_WR_EN); + + /* flush fifo 1 */ + cdns_mhdp_bus_write(F_FIFO1_FLUSH(1), mhdp, SOURCE_PIF_FIFO1_FLUSH); + + /* write packet into memory */ + packet32 = (u32 *)packet; + len32 = packet_len / 4; + for (i = 0; i < len32; i++) + cdns_mhdp_bus_write(F_DATA_WR(packet32[i]), mhdp, SOURCE_PIF_DATA_WR); + + /* write entry id */ + cdns_mhdp_bus_write(F_WR_ADDR(entry_id), mhdp, SOURCE_PIF_WR_ADDR); + + /* write request */ + cdns_mhdp_bus_write(F_HOST_WR(1), mhdp, SOURCE_PIF_WR_REQ); + + /* update entry */ + val = F_ACTIVE_IDLE_TYPE(1) | F_TYPE_VALID(1) | + F_PACKET_TYPE(packet_type) | F_PKT_ALLOC_ADDRESS(entry_id); + cdns_mhdp_bus_write(val, mhdp, SOURCE_PIF_PKT_ALLOC_REG); + + cdns_mhdp_bus_write(F_PKT_ALLOC_WR_EN(1), mhdp, SOURCE_PIF_PKT_ALLOC_WR_EN); +} + +int cdns_hdmi_get_edid_block(void *data, u8 *edid, + u32 block, size_t length) +{ + struct cdns_mhdp_device *mhdp = data; + u8 msg[2], reg[5], i; + int ret; + + for (i = 0; i < 4; i++) { + msg[0] = block / 2; + msg[1] = block % 2; + + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_HDMI_TX, HDMI_TX_EDID, + sizeof(msg), msg); + if (ret) + continue; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDMI_TX, + HDMI_TX_EDID, sizeof(reg) + length); + if (ret) + continue; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg)); + if (ret) + continue; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, edid, length); + if (ret) + continue; + + if ((reg[3] << 8 | reg[4]) == length) + break; + } + + if (ret) + DRM_ERROR("get block[%d] edid failed: %d\n", block, ret); + return ret; +} + +int cdns_hdmi_scdc_read(struct cdns_mhdp_device *mhdp, u8 addr, u8 *data) +{ + u8 msg[4], reg[6]; + int ret; + + msg[0] = 0x54; + msg[1] = addr; + msg[2] = 0; + msg[3] = 1; + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_HDMI_TX, HDMI_TX_READ, + sizeof(msg), msg); + if (ret) + goto err_scdc_read; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDMI_TX, + HDMI_TX_READ, sizeof(reg)); + if (ret) + goto err_scdc_read; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg)); + if (ret) + goto err_scdc_read; + + *data = reg[5]; + +err_scdc_read: + if (ret) + DRM_ERROR("scdc read failed: %d\n", ret); + return ret; +} + +int cdns_hdmi_scdc_write(struct cdns_mhdp_device *mhdp, u8 addr, u8 value) +{ + u8 msg[5], reg[5]; + int ret; + + msg[0] = 0x54; + msg[1] = addr; + msg[2] = 0; + msg[3] = 1; + msg[4] = value; + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_HDMI_TX, HDMI_TX_WRITE, + sizeof(msg), msg); + if (ret) + goto err_scdc_write; + + ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDMI_TX, + HDMI_TX_WRITE, sizeof(reg)); + if (ret) + goto err_scdc_write; + + ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg)); + if (ret) + goto err_scdc_write; + + if (reg[0] != 0) + ret = -EINVAL; + +err_scdc_write: + if (ret) + DRM_ERROR("scdc write failed: %d\n", ret); + return ret; +} + +int cdns_hdmi_ctrl_init(struct cdns_mhdp_device *mhdp, + int protocol, + u32 char_rate) +{ + u32 reg0; + u32 reg1; + u32 val; + int ret; + + /* Set PHY to HDMI data */ + ret = cdns_mhdp_reg_write(mhdp, PHY_DATA_SEL, F_SOURCE_PHY_MHDP_SEL(1)); + if (ret < 0) + return ret; + + ret = cdns_mhdp_reg_write(mhdp, HDTX_HPD, + F_HPD_VALID_WIDTH(4) | F_HPD_GLITCH_WIDTH(0)); + if (ret < 0) + return ret; + + /* open CARS */ + ret = cdns_mhdp_reg_write(mhdp, SOURCE_PHY_CAR, 0xF); + if (ret < 0) + return ret; + ret = cdns_mhdp_reg_write(mhdp, SOURCE_HDTX_CAR, 0xFF); + if (ret < 0) + return ret; + ret = cdns_mhdp_reg_write(mhdp, SOURCE_PKT_CAR, 0xF); + if (ret < 0) + return ret; + ret = cdns_mhdp_reg_write(mhdp, SOURCE_AIF_CAR, 0xF); + if (ret < 0) + return ret; + ret = cdns_mhdp_reg_write(mhdp, SOURCE_CIPHER_CAR, 0xF); + if (ret < 0) + return ret; + ret = cdns_mhdp_reg_write(mhdp, SOURCE_CRYPTO_CAR, 0xF); + if (ret < 0) + return ret; + ret = cdns_mhdp_reg_write(mhdp, SOURCE_CEC_CAR, 3); + if (ret < 0) + return ret; + + reg0 = reg1 = 0x7c1f; + if (protocol == MODE_HDMI_2_0 && char_rate >= 340000) { + reg0 = 0; + reg1 = 0xFFFFF; + } + ret = cdns_mhdp_reg_write(mhdp, HDTX_CLOCK_REG_0, reg0); + if (ret < 0) + return ret; + ret = cdns_mhdp_reg_write(mhdp, HDTX_CLOCK_REG_1, reg1); + if (ret < 0) + return ret; + + /* set hdmi mode and preemble mode data enable */ + val = F_HDMI_MODE(protocol) | F_HDMI2_PREAMBLE_EN(1) | F_DATA_EN(1) | + F_HDMI2_CTRL_IL_MODE(1) | F_BCH_EN(1) | F_PIC_3D(0XF) | F_CLEAR_AVMUTE(1); + ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val); + + return ret; +} + +int cdns_hdmi_mode_config(struct cdns_mhdp_device *mhdp, + struct drm_display_mode *mode, + struct video_info *video_info) +{ + int ret; + u32 val; + u32 vsync_lines = mode->vsync_end - mode->vsync_start; + u32 eof_lines = mode->vsync_start - mode->vdisplay; + u32 sof_lines = mode->vtotal - mode->vsync_end; + u32 hblank = mode->htotal - mode->hdisplay; + u32 hactive = mode->hdisplay; + u32 vblank = mode->vtotal - mode->vdisplay; + u32 vactive = mode->vdisplay; + u32 hfront = mode->hsync_start - mode->hdisplay; + u32 hback = mode->htotal - mode->hsync_end; + u32 vfront = eof_lines; + u32 hsync = hblank - hfront - hback; + u32 vsync = vsync_lines; + u32 vback = sof_lines; + u32 v_h_polarity = ((mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : 1) + + ((mode->flags & DRM_MODE_FLAG_NVSYNC) ? 0 : 2); + + ret = cdns_mhdp_reg_write(mhdp, SCHEDULER_H_SIZE, (hactive << 16) + hblank); + if (ret < 0) + return ret; + + ret = cdns_mhdp_reg_write(mhdp, SCHEDULER_V_SIZE, (vactive << 16) + vblank); + if (ret < 0) + return ret; + + ret = cdns_mhdp_reg_write(mhdp, HDTX_SIGNAL_FRONT_WIDTH, (vfront << 16) + hfront); + if (ret < 0) + return ret; + + ret = cdns_mhdp_reg_write(mhdp, HDTX_SIGNAL_SYNC_WIDTH, (vsync << 16) + hsync); + if (ret < 0) + return ret; + + ret = cdns_mhdp_reg_write(mhdp, HDTX_SIGNAL_BACK_WIDTH, (vback << 16) + hback); + if (ret < 0) + return ret; + + ret = cdns_mhdp_reg_write(mhdp, HSYNC2VSYNC_POL_CTRL, v_h_polarity); + if (ret < 0) + return ret; + + /* Reset Data Enable */ + val = cdns_mhdp_reg_read(mhdp, HDTX_CONTROLLER); + val &= ~F_DATA_EN(1); + ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val); + if (ret < 0) + return ret; + + /* Set bpc */ + val &= ~F_VIF_DATA_WIDTH(3); + switch (video_info->color_depth) { + case 10: + val |= F_VIF_DATA_WIDTH(1); + break; + case 12: + val |= F_VIF_DATA_WIDTH(2); + break; + case 16: + val |= F_VIF_DATA_WIDTH(3); + break; + case 8: + default: + val |= F_VIF_DATA_WIDTH(0); + break; + } + + /* select color encoding */ + val &= ~F_HDMI_ENCODING(3); + switch (video_info->color_fmt) { + case YCBCR_4_4_4: + val |= F_HDMI_ENCODING(2); + break; + case YCBCR_4_2_2: + val |= F_HDMI_ENCODING(1); + break; + case YCBCR_4_2_0: + val |= F_HDMI_ENCODING(3); + break; + case PXL_RGB: + default: + val |= F_HDMI_ENCODING(0); + break; + } + + ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val); + if (ret < 0) + return ret; + + /* set data enable */ + val |= F_DATA_EN(1); + ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val); + + return ret; +} + +int cdns_hdmi_disable_gcp(struct cdns_mhdp_device *mhdp) +{ + u32 val; + + val = cdns_mhdp_reg_read(mhdp, HDTX_CONTROLLER); + val &= ~F_GCP_EN(1); + + return cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val); +} + +int cdns_hdmi_enable_gcp(struct cdns_mhdp_device *mhdp) +{ + u32 val; + + val = cdns_mhdp_reg_read(mhdp, HDTX_CONTROLLER); + val |= F_GCP_EN(1); + + return cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val); +} diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp.h b/drivers/gpu/drm/bridge/cadence/cdns-mhdp.h new file mode 100644 index 000000000000..8ad99eb8f86e --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp.h @@ -0,0 +1,232 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence MHDP DP MST bridge driver. + * + * Copyright: 2018 Cadence Design Systems, Inc. + * + * Author: Quentin Schulz <quentin.schulz@free-electrons.com> + */ + + +#ifndef CDNS_MHDP_H +#define CDNS_MHDP_H + +#include <drm/drm_dp_mst_helper.h> + +#define CDNS_APB_CFG 0x00000 +#define CDNS_APB_CTRL (CDNS_APB_CFG + 0x00) +#define CDNS_MAILBOX_FULL (CDNS_APB_CFG + 0x08) +#define CDNS_MAILBOX_EMPTY (CDNS_APB_CFG + 0x0c) +#define CDNS_MAILBOX_TX_DATA (CDNS_APB_CFG + 0x10) +#define CDNS_MAILBOX_RX_DATA (CDNS_APB_CFG + 0x14) +#define CDNS_KEEP_ALIVE (CDNS_APB_CFG + 0x18) +#define CDNS_KEEP_ALIVE_MASK GENMASK(7, 0) + +#define CDNS_MB_INT_MASK (CDNS_APB_CFG + 0x34) + +#define CDNS_SW_CLK_L (CDNS_APB_CFG + 0x3c) +#define CDNS_SW_CLK_H (CDNS_APB_CFG + 0x40) +#define CDNS_SW_EVENT0 (CDNS_APB_CFG + 0x44) +#define CDNS_DPTX_HPD BIT(0) + +#define CDNS_SW_EVENT1 (CDNS_APB_CFG + 0x48) +#define CDNS_SW_EVENT2 (CDNS_APB_CFG + 0x4c) +#define CDNS_SW_EVENT3 (CDNS_APB_CFG + 0x50) + +#define CDNS_APB_INT_MASK (CDNS_APB_CFG + 0x6C) +#define CDNS_APB_INT_MASK_MAILBOX_INT BIT(0) +#define CDNS_APB_INT_MASK_SW_EVENT_INT BIT(1) + +#define CDNS_DPTX_CAR (CDNS_APB_CFG + 0x904) +#define CDNS_VIF_CLK_EN BIT(0) +#define CDNS_VIF_CLK_RSTN BIT(1) + +#define CDNS_SOURCE_VIDEO_IF(s) (0x00b00 + (s * 0x20)) +#define CDNS_BND_HSYNC2VSYNC(s) (CDNS_SOURCE_VIDEO_IF(s) + \ + 0x00) +#define CDNS_IP_DTCT_WIN GENMASK(11, 0) +#define CDNS_IP_DET_INTERLACE_FORMAT BIT(12) +#define CDNS_IP_BYPASS_V_INTERFACE BIT(13) + +#define CDNS_HSYNC2VSYNC_POL_CTRL(s) (CDNS_SOURCE_VIDEO_IF(s) + \ + 0x10) +#define CDNS_H2V_HSYNC_POL_ACTIVE_LOW BIT(1) +#define CDNS_H2V_VSYNC_POL_ACTIVE_LOW BIT(2) + +#define CDNS_DPTX_PHY_CONFIG 0x02000 +#define CDNS_PHY_TRAINING_EN BIT(0) +#define CDNS_PHY_TRAINING_TYPE(x) (((x) & GENMASK(3, 0)) << 1) +#define CDNS_PHY_SCRAMBLER_BYPASS BIT(5) +#define CDNS_PHY_ENCODER_BYPASS BIT(6) +#define CDNS_PHY_SKEW_BYPASS BIT(7) +#define CDNS_PHY_TRAINING_AUTO BIT(8) +#define CDNS_PHY_LANE0_SKEW(x) (((x) & GENMASK(2, 0)) << 9) +#define CDNS_PHY_LANE1_SKEW(x) (((x) & GENMASK(2, 0)) << 12) +#define CDNS_PHY_LANE2_SKEW(x) (((x) & GENMASK(2, 0)) << 15) +#define CDNS_PHY_LANE3_SKEW(x) (((x) & GENMASK(2, 0)) << 18) +#define CDNS_PHY_COMMON_CONFIG (CDNS_PHY_LANE1_SKEW(1) | \ + CDNS_PHY_LANE2_SKEW(2) | \ + CDNS_PHY_LANE3_SKEW(3)) +#define CDNS_PHY_10BIT_EN BIT(21) + +#define CDNS_DPTX_FRAMER 0x02200 +#define CDNS_DP_FRAMER_GLOBAL_CONFIG (CDNS_DPTX_FRAMER + 0x00) +#define CDNS_DP_NUM_LANES(x) (x - 1) +#define CDNS_DP_MST_EN BIT(2) +#define CDNS_DP_FRAMER_EN BIT(3) +#define CDNS_DP_RATE_GOVERNOR_EN BIT(4) +#define CDNS_DP_NO_VIDEO_MODE BIT(5) +#define CDNS_DP_DISABLE_PHY_RST BIT(6) +#define CDNS_DP_WR_FAILING_EDGE_VSYNC BIT(7) + +#define CDNS_DP_SW_RESET (CDNS_DPTX_FRAMER + 0x04) +#define CDNS_DP_FRAMER_TU (CDNS_DPTX_FRAMER + 0x08) +#define CDNS_DP_FRAMER_TU_SIZE(x) (((x) & GENMASK(6, 0)) << 8) +#define CDNS_DP_FRAMER_TU_VS(x) ((x) & GENMASK(5, 0)) +#define CDNS_DP_FRAMER_TU_CNT_RST_EN BIT(15) + +#define CDNS_DPTX_STREAM(s) (0x03000 + s * 0x80) +#define CDNS_DP_MSA_HORIZONTAL_0(s) (CDNS_DPTX_STREAM(s) + 0x00) +#define CDNS_DP_MSAH0_H_TOTAL(x) (x) +#define CDNS_DP_MSAH0_HSYNC_START(x) ((x) << 16) + +#define CDNS_DP_MSA_HORIZONTAL_1(s) (CDNS_DPTX_STREAM(s) + 0x04) +#define CDNS_DP_MSAH1_HSYNC_WIDTH(x) (x) +#define CDNS_DP_MSAH1_HSYNC_POL_LOW BIT(15) +#define CDNS_DP_MSAH1_HDISP_WIDTH(x) ((x) << 16) + +#define CDNS_DP_MSA_VERTICAL_0(s) (CDNS_DPTX_STREAM(s) + 0x08) +#define CDNS_DP_MSAV0_V_TOTAL(x) (x) +#define CDNS_DP_MSAV0_VSYNC_START(x) ((x) << 16) + +#define CDNS_DP_MSA_VERTICAL_1(s) (CDNS_DPTX_STREAM(s) + 0x0c) +#define CDNS_DP_MSAV1_VSYNC_WIDTH(x) (x) +#define CDNS_DP_MSAV1_VSYNC_POL_LOW BIT(15) +#define CDNS_DP_MSAV1_VDISP_WIDTH(x) ((x) << 16) + +#define CDNS_DP_MSA_MISC(s) (CDNS_DPTX_STREAM(s) + 0x10) +#define CDNS_DP_STREAM_CONFIGs(s) (CDNS_DPTX_STREAM(s) + 0x14) +#define CDNS_DP_STREAM_CONFIG_2(s) (CDNS_DPTX_STREAM(s) + 0x2c) +#define CDNS_DP_SC2_TU_VS_DIFF(x) ((x) << 8) + +#define CDNS_DP_HORIZONTAL(s) (CDNS_DPTX_STREAM(s) + 0x30) +#define CDNS_DP_H_HSYNC_WIDTH(x) (x) +#define CDNS_DP_H_H_TOTAL(x) ((x) << 16) + +#define CDNS_DP_VERTICAL_0(s) (CDNS_DPTX_STREAM(s) + 0x34) +#define CDNS_DP_V0_VHEIGHT(x) (x) +#define CDNS_DP_V0_VSTART(x) ((x) << 16) + +#define CDNS_DP_VERTICAL_1(s) (CDNS_DPTX_STREAM(s) + 0x38) +#define CDNS_DP_V1_VTOTAL(x) (x) +#define CDNS_DP_V1_VTOTAL_EVEN BIT(16) + +#define CDNS_DP_FRAMER_PXL_REPR(s) (CDNS_DPTX_STREAM(s) + 0x4c) +#define CDNS_DP_FRAMER_6_BPC BIT(0) +#define CDNS_DP_FRAMER_8_BPC BIT(1) +#define CDNS_DP_FRAMER_10_BPC BIT(2) +#define CDNS_DP_FRAMER_12_BPC BIT(3) +#define CDNS_DP_FRAMER_16_BPC BIT(4) +#define CDNS_DP_FRAMER_PXL_FORMAT 0x8 +#define CDNS_DP_FRAMER_RGB BIT(0) +#define CDNS_DP_FRAMER_YCBCR444 BIT(1) +#define CDNS_DP_FRAMER_YCBCR422 BIT(2) +#define CDNS_DP_FRAMER_YCBCR420 BIT(3) +#define CDNS_DP_FRAMER_Y_ONLY BIT(4) + +#define CDNS_DP_FRAMER_SP(s) (CDNS_DPTX_STREAM(s) + 0x10) +#define CDNS_DP_FRAMER_VSYNC_POL_LOW BIT(0) +#define CDNS_DP_FRAMER_HSYNC_POL_LOW BIT(1) +#define CDNS_DP_FRAMER_INTERLACE BIT(2) + +#define CDNS_DP_LINE_THRESH(s) (CDNS_DPTX_STREAM(s) + 0x64) +#define CDNS_DP_ACTIVE_LINE_THRESH(x) (x) + +#define CDNS_DP_VB_ID(s) (CDNS_DPTX_STREAM(s) + 0x68) +#define CDNS_DP_VB_ID_INTERLACED BIT(2) +#define CDNS_DP_VB_ID_COMPRESSED BIT(6) + +#define CDNS_DP_FRONT_BACK_PORCH(s) (CDNS_DPTX_STREAM(s) + 0x78) +#define CDNS_DP_BACK_PORCH(x) (x) +#define CDNS_DP_FRONT_PORCH(x) ((x) << 16) + +#define CDNS_DP_BYTE_COUNT(s) (CDNS_DPTX_STREAM(s) + 0x7c) +#define CDNS_DP_BYTE_COUNT_BYTES_IN_CHUNK_SHIFT 16 + +#define CDNS_DP_MST_STREAM_CONFIG(s) (CDNS_DPTX_STREAM(s) + 0x14) +#define CDNS_DP_MST_STRM_CFG_STREAM_EN BIT(0) +#define CDNS_DP_MST_STRM_CFG_NO_VIDEO BIT(1) + +#define CDNS_DP_MST_SLOT_ALLOCATE(s) (CDNS_DPTX_STREAM(s) + 0x44) +#define CDNS_DP_S_ALLOC_START_SLOT(x) (x) +#define CDNS_DP_S_ALLOC_END_SLOT(x) ((x) << 8) + +#define CDNS_DP_RATE_GOVERNING(s) (CDNS_DPTX_STREAM(s) + 0x48) +#define CDNS_DP_RG_TARG_AV_SLOTS_Y(x) (x) +#define CDNS_DP_RG_TARG_AV_SLOTS_X(x) (x << 4) +#define CDNS_DP_RG_ENABLE BIT(10) + +#define CDNS_DP_MTPH_CONTROL 0x2264 +#define CDNS_DP_MTPH_ECF_EN BIT(0) +#define CDNS_DP_MTPH_ACT_EN BIT(1) +#define CDNS_DP_MTPH_LVP_EN BIT(2) + +#define CDNS_DP_MTPH_STATUS 0x226C +#define CDNS_DP_MTPH_ACT_STATUS BIT(0) + +#define CDNS_DPTX_GLOBAL 0x02300 +#define CDNS_DP_LANE_EN (CDNS_DPTX_GLOBAL + 0x00) +#define CDNS_DP_LANE_EN_LANES(x) GENMASK(x - 1, 0) +#define CDNS_DP_ENHNCD (CDNS_DPTX_GLOBAL + 0x04) + + +#define to_mhdp_connector(x) container_of(x, struct cdns_mhdp_connector, base) +#define to_mhdp_bridge(x) container_of(x, struct cdns_mhdp_bridge, base) +#define mgr_to_mhdp(x) container_of(x, struct cdns_mhdp_device, mst_mgr) + +#define CDNS_MHDP_MAX_STREAMS 4 + +#define MAILBOX_RETRY_US 1000 +#define MAILBOX_TIMEOUT_US 5000000 + +#define mhdp_readx_poll_timeout(op, addr, offset, val, cond, sleep_us, timeout_us) \ +({ \ + u64 __timeout_us = (timeout_us); \ + unsigned long __sleep_us = (sleep_us); \ + ktime_t __timeout = ktime_add_us(ktime_get(), __timeout_us); \ + might_sleep_if((__sleep_us) != 0); \ + for (;;) { \ + (val) = op(addr, offset); \ + if (cond) \ + break; \ + if (__timeout_us && \ + ktime_compare(ktime_get(), __timeout) > 0) { \ + (val) = op(addr, offset); \ + break; \ + } \ + if (__sleep_us) \ + usleep_range((__sleep_us >> 2) + 1, __sleep_us); \ + } \ + (cond) ? 0 : -ETIMEDOUT; \ +}) + +enum pixel_format { + PIXEL_FORMAT_RGB = 1, + PIXEL_FORMAT_YCBCR_444 = 2, + PIXEL_FORMAT_YCBCR_422 = 4, + PIXEL_FORMAT_YCBCR_420 = 8, + PIXEL_FORMAT_Y_ONLY = 16, +}; + + +int cdns_mhdp_mst_init(struct cdns_mhdp_device *mhdp); +void cdns_mhdp_mst_deinit(struct cdns_mhdp_device *mhdp); +bool cdns_mhdp_mst_probe(struct cdns_mhdp_device *mhdp); +enum pixel_format cdns_mhdp_get_pxlfmt(u32 color_formats); +u32 cdns_mhdp_get_bpp(u32 bpc, u32 color_formats); +void cdns_mhdp_configure_video(struct drm_bridge *bridge); +void cdns_mhdp_mst_enable(struct drm_bridge *bridge); +void cdns_mhdp_mst_disable(struct drm_bridge *bridge); +void cdns_mhdp_enable(struct drm_bridge *bridge); + +#endif diff --git a/drivers/gpu/drm/bridge/fsl-imx-ldb.c b/drivers/gpu/drm/bridge/fsl-imx-ldb.c new file mode 100644 index 000000000000..55ccb0128f18 --- /dev/null +++ b/drivers/gpu/drm/bridge/fsl-imx-ldb.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2012 Sascha Hauer, Pengutronix + * Copyright 2020 NXP + */ + +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <drm/bridge/fsl_imx_ldb.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> + +#define LDB_CH0_MODE_EN_TO_DI0 (1 << 0) +#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0) +#define LDB_CH0_MODE_EN_MASK (3 << 0) +#define LDB_CH1_MODE_EN_TO_DI0 (1 << 2) +#define LDB_CH1_MODE_EN_TO_DI1 (3 << 2) +#define LDB_CH1_MODE_EN_MASK (3 << 2) +#define LDB_SPLIT_MODE_EN (1 << 4) +#define LDB_DATA_WIDTH_CH0_24 (1 << 5) +#define LDB_BIT_MAP_CH0_JEIDA (1 << 6) +#define LDB_DATA_WIDTH_CH1_24 (1 << 7) +#define LDB_BIT_MAP_CH1_JEIDA (1 << 8) +#define LDB_DI0_VS_POL_ACT_LOW (1 << 9) +#define LDB_DI1_VS_POL_ACT_LOW (1 << 10) + +struct ldb_bit_mapping { + u32 bus_format; + u32 datawidth; + const char * const mapping; +}; + +static const struct ldb_bit_mapping ldb_bit_mappings[] = { + { MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, 18, "spwg" }, + { MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, 24, "spwg" }, + { MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, 24, "jeida" }, +}; + +static u32 of_get_bus_format(struct device *dev, struct device_node *np) +{ + const char *bm; + u32 datawidth = 0; + int ret, i; + + ret = of_property_read_string(np, "fsl,data-mapping", &bm); + if (ret < 0) + return ret; + + of_property_read_u32(np, "fsl,data-width", &datawidth); + + for (i = 0; i < ARRAY_SIZE(ldb_bit_mappings); i++) { + if (!strcasecmp(bm, ldb_bit_mappings[i].mapping) && + datawidth == ldb_bit_mappings[i].datawidth) + return ldb_bit_mappings[i].bus_format; + } + + dev_err(dev, "invalid data mapping: %d-bit \"%s\"\n", datawidth, bm); + + return -ENOENT; +} + +static inline struct ldb_channel *bridge_to_ldb_ch(struct drm_bridge *b) +{ + return container_of(b, struct ldb_channel, bridge); +} + +static void ldb_ch_set_bus_format(struct ldb_channel *ldb_ch, u32 bus_format) +{ + struct ldb *ldb = ldb_ch->ldb; + + switch (bus_format) { + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + if (ldb_ch->chno == 0 || ldb->dual) + ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24; + if (ldb_ch->chno == 1 || ldb->dual) + ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + if (ldb_ch->chno == 0 || ldb->dual) + ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24 | + LDB_BIT_MAP_CH0_JEIDA; + if (ldb_ch->chno == 1 || ldb->dual) + ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24 | + LDB_BIT_MAP_CH1_JEIDA; + break; + } +} + +static void ldb_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct ldb_channel *ldb_ch = bridge_to_ldb_ch(bridge); + struct ldb *ldb = ldb_ch->ldb; + + /* FIXME - assumes straight connections DI0 --> CH0, DI1 --> CH1 */ + if (ldb_ch == ldb->channel[0] || ldb->dual) { + if (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) + ldb->ldb_ctrl |= LDB_DI0_VS_POL_ACT_LOW; + else if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) + ldb->ldb_ctrl &= ~LDB_DI0_VS_POL_ACT_LOW; + } + if (ldb_ch == ldb->channel[1] || ldb->dual) { + if (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) + ldb->ldb_ctrl |= LDB_DI1_VS_POL_ACT_LOW; + else if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) + ldb->ldb_ctrl &= ~LDB_DI1_VS_POL_ACT_LOW; + } + + ldb_ch_set_bus_format(ldb_ch, ldb_ch->bus_format); +} + +static void ldb_bridge_enable(struct drm_bridge *bridge) +{ + struct ldb_channel *ldb_ch = bridge_to_ldb_ch(bridge); + struct ldb *ldb = ldb_ch->ldb; + + if (pm_runtime_enabled(ldb->dev)) + pm_runtime_get_sync(ldb->dev); + + regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl); +} + +static void ldb_bridge_disable(struct drm_bridge *bridge) +{ + struct ldb_channel *ldb_ch = bridge_to_ldb_ch(bridge); + struct ldb *ldb = ldb_ch->ldb; + + if (ldb_ch == ldb->channel[0] || ldb->dual) + ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; + if (ldb_ch == ldb->channel[1] || ldb->dual) + ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; + + regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl); + + if (pm_runtime_enabled(ldb->dev)) + pm_runtime_put(ldb->dev); +} + +static int ldb_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct ldb_channel *ldb_ch = bridge_to_ldb_ch(bridge); + struct ldb *ldb = ldb_ch->ldb; + + if (!bridge->encoder) { + dev_err(ldb->dev, "failed to find encoder object\n"); + return -ENODEV; + } + + if (!ldb_ch->next_bridge) + return 0; + + return drm_bridge_attach(bridge->encoder, + ldb_ch->next_bridge, &ldb_ch->bridge, flags); +} + +static const struct drm_bridge_funcs ldb_bridge_funcs = { + .mode_set = ldb_bridge_mode_set, + .enable = ldb_bridge_enable, + .disable = ldb_bridge_disable, + .attach = ldb_bridge_attach, +}; + +int ldb_bind(struct ldb *ldb, struct drm_encoder **encoder) +{ + struct device *dev = ldb->dev; + struct device_node *np = dev->of_node; + struct device_node *child; + int ret = 0; + int i; + + ldb->regmap = syscon_regmap_lookup_by_phandle(np, "gpr"); + if (IS_ERR(ldb->regmap)) { + dev_err(dev, "failed to get parent regmap\n"); + return PTR_ERR(ldb->regmap); + } + + if (pm_runtime_enabled(dev)) + pm_runtime_get_sync(dev); + + /* disable LDB by resetting the control register to POR default */ + regmap_write(ldb->regmap, ldb->ctrl_reg, 0); + + if (pm_runtime_enabled(dev)) + pm_runtime_put(dev); + + ldb->dual = of_property_read_bool(np, "fsl,dual-channel"); + if (ldb->dual) + ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN; + + for_each_child_of_node(np, child) { + struct ldb_channel *ldb_ch; + int bus_format; + + ret = of_property_read_u32(child, "reg", &i); + if (ret || i < 0 || i > 1) { + ret = -EINVAL; + goto free_child; + } + + if (!of_device_is_available(child)) + continue; + + if (ldb->dual && i > 0) { + dev_warn(dev, "dual-channel mode, ignoring second output\n"); + continue; + } + + ldb_ch = ldb->channel[i]; + ldb_ch->ldb = ldb; + ldb_ch->chno = i; + ldb_ch->is_valid = false; + + ret = drm_of_find_panel_or_bridge(child, + ldb->output_port, 0, + &ldb_ch->panel, + &ldb_ch->next_bridge); + if (ret && ret != -ENODEV) + goto free_child; + + bus_format = of_get_bus_format(dev, child); + if (bus_format == -EINVAL) { + /* + * If no bus format was specified in the device tree, + * we can still get it from the connected panel later. + */ + if (ldb_ch->panel && ldb_ch->panel->funcs && + ldb_ch->panel->funcs->get_modes) + bus_format = 0; + } + if (bus_format < 0) { + dev_err(dev, "could not determine data mapping: %d\n", + bus_format); + ret = bus_format; + goto free_child; + } + ldb_ch->bus_format = bus_format; + ldb_ch->child = child; + + if (ldb_ch->panel) { + ldb_ch->next_bridge = devm_drm_panel_bridge_add(dev, + ldb_ch->panel); + if (IS_ERR(ldb_ch->next_bridge)) { + ret = PTR_ERR(ldb_ch->next_bridge); + goto free_child; + } + } + + ldb_ch->bridge.driver_private = ldb_ch; + ldb_ch->bridge.funcs = &ldb_bridge_funcs; + ldb_ch->bridge.of_node = child; + + ret = drm_bridge_attach(encoder[i], &ldb_ch->bridge, NULL, 0); + if (ret) { + dev_err(dev, + "failed to attach bridge with encoder: %d\n", + ret); + goto free_child; + } + + ldb_ch->is_valid = true; + } + + return 0; + +free_child: + of_node_put(child); + return ret; +} +EXPORT_SYMBOL_GPL(ldb_bind); + +MODULE_DESCRIPTION("Freescale i.MX LVDS display bridge driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform: fsl-imx-ldb"); diff --git a/drivers/gpu/drm/bridge/it6161.c b/drivers/gpu/drm/bridge/it6161.c new file mode 100644 index 000000000000..b7bf3698db99 --- /dev/null +++ b/drivers/gpu/drm/bridge/it6161.c @@ -0,0 +1,2407 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2021 NXP + */ +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <sound/hdmi-codec.h> + +#include "it6161.h" + +#define AUX_WAIT_TIMEOUT_MS 100 +#define DEFAULT_DRV_HOLD 0 + +#define RGB_24b 0x3E +#define RGB_18b 0x1E + +#define InvMCLK TRUE +#define PDREFCLK FALSE +#define SkipStg 4 +#define MShift 8 +#define PPSFFRdStg 0x04 +#define RegAutoSync TRUE + +#define LMDbgSel 0 /* 0~7 */ +#define InvPCLK FALSE +#define PDREFCNT 0 /* when PDREFCLK=TRUE, 0:div2, 1:div4, 2:div8, 3:divg16 */ +#define EnIOIDDQ FALSE +#define EnStb2Rst FALSE +#define EnExtStdby FALSE +#define EnStandby FALSE +#define MPLaneSwap FALSE +#define MPPNSwap FALSE /* TRUE: MTK , FALSE: Solomon */ + +/* PPI */ +#define EnContCK TRUE +#define HSSetNum 1 +#define EnDeSkew TRUE +#define PPIDbgSel 12 +#define RegIgnrNull 1 +#define RegIgnrBlk 1 +#define RegEnDummyECC 0 +#define EOTPSel 0 + +/* PPS option */ +#define EnMBPM FALSE /* enable MIPI Bypass Mode */ +#if (EnMBPM == TRUE) + #define PREC_Update TRUE /* enable P-timing update */ + #define MREC_Update TRUE /* enable M-timing update */ + #define EnTBPM TRUE /* enable HDMITX Bypass Mode */ +#else + #define PREC_Update FALSE + #define MREC_Update FALSE + #define EnTBPM FALSE +#endif + +#define REGSELDEF FALSE +#define EnHReSync FALSE +#define EnVReSync FALSE +#define EnFReSync FALSE +#define EnVREnh FALSE +#define EnVREnhSel 1 /* 0:Div2, 1:Div4, 2:Div8, 3:Div16, 4:Div32 */ +#define EnMAvg TRUE + +#define PShift 3 +#define EnFFAutoRst TRUE +#define RegEnSyncErr FALSE +#define EnTxCRC TRUE +#define TxCRCnum 0x20 + +#define ENABLE_MIPI_RX_EXTERNAL_CLOCK FALSE + +#define NRTXRCLK TRUE /* true:set TRCLK by self */ +#define RCLKFreqSel TRUE /* false: 10MHz(div1); true: 20 MHz(OSSDIV2) */ +#define ForceTxCLKStb TRUE + +#define HDMI_TX_PCLK_DIV2 FALSE + +#define HDMI_TX_MODE HDMI_TX_ENABLE_DE_ONLY + +enum hdmi_tx_mode { + HDMI_TX_NONE, + HDMI_TX_BY_PASS, + HDMI_TX_ENABLE_DE_ONLY, + HDMI_TX_ENABLE_PATTERN_GENERATOR, +}; + +enum it6161_active_level { + LOW, + HIGH, +}; + +const struct RegSetEntry HDMITX_Init_Table[] = { + {0x0F, 0x40, 0x00}, + /*PLL Reset */ + {0x62, 0x08, 0x00}, /* XP_RESETB */ + {0x64, 0x04, 0x00}, /* IP_RESETB */ + {0x0F, 0x01, 0x00}, /* bank 0 ;3 */ + {0x8D, 0xFF, CEC_I2C_SLAVE_ADDR}, /* EnCEC */ + {0xA9, 0x80, (EnTBPM << 7)}, + {0xBF, 0x80, (NRTXRCLK << 7)}, + + /* Initial Value */ + {0xF8, 0xFF, 0xC3}, + {0xF8, 0xFF, 0xA5}, + {0xF4, 0x0C, 0x00}, + {0xF3, 0x02, 0x00}, + {0xF8, 0xFF, 0xFF}, + {0x5A, 0x0C, 0x0C}, + {0xD1, 0x0A, ((ForceTxCLKStb) << 3) + 0x02}, + {0x5D, 0x04, ((RCLKFreqSel) << 2)}, + {0x65, 0x03, 0x00}, + {0x71, 0xF9, 0x18}, + {0xCF, 0xFF, 0x00}, + {0xd1, 0x02, 0x00}, + {0x59, 0xD0, 0x40}, + {0xE1, 0x20, 0x00}, + {0xF5, 0x40, 0x00}, + {0x05, 0xC0, 0x40}, /* Setup INT Pin: Active Low & Open-Drain */ + {0x0C, 0xFF, 0xFF}, + {0x0D, 0xFF, 0xFF}, + {0x0E, 0x03, 0x03}, /* Clear all Interrupt */ + {0x0C, 0xFF, 0x00}, + {0x0D, 0xFF, 0x00}, + {0x0E, 0x02, 0x00}, + {0x20, 0x01, 0x00} +}; + +const struct RegSetEntry HDMITX_DefaultVideo_Table[] = { + /* Config default output format */ + {0x72, 0xff, 0x00}, + {0x70, 0xff, 0x00}, +/* GenCSC\RGB2YUV_ITU709_16_235 */ + {0x72, 0xFF, 0x02}, + {0x73, 0xFF, 0x00}, + {0x74, 0xFF, 0x80}, + {0x75, 0xFF, 0x00}, + {0x76, 0xFF, 0xB8}, + {0x77, 0xFF, 0x05}, + {0x78, 0xFF, 0xB4}, + {0x79, 0xFF, 0x01}, + {0x7A, 0xFF, 0x93}, + {0x7B, 0xFF, 0x00}, + {0x7C, 0xFF, 0x49}, + {0x7D, 0xFF, 0x3C}, + {0x7E, 0xFF, 0x18}, + {0x7F, 0xFF, 0x04}, + {0x80, 0xFF, 0x9F}, + {0x81, 0xFF, 0x3F}, + {0x82, 0xFF, 0xD9}, + {0x83, 0xFF, 0x3C}, + {0x84, 0xFF, 0x10}, + {0x85, 0xFF, 0x3F}, + {0x86, 0xFF, 0x18}, + {0x87, 0xFF, 0x04}, + {0x88, 0xF0, 0x00}, +}; + +/* Config default HDMI Mode */ +const struct RegSetEntry HDMITX_SetHDMI_Table[] = { + {0xC0, 0x01, 0x01}, + {0xC1, 0x03, 0x03}, + {0xC6, 0x03, 0x03} +}; + +/* Config default avi infoframe */ +const struct RegSetEntry HDMITX_DefaultAVIInfo_Table[] = { + {0x0F, 0x01, 0x01}, + {0x58, 0xFF, 0x10}, + {0x59, 0xFF, 0x08}, + {0x5A, 0xFF, 0x00}, + {0x5B, 0xFF, 0x00}, + {0x5C, 0xFF, 0x00}, + {0x5D, 0xFF, 0x57}, + {0x5E, 0xFF, 0x00}, + {0x5F, 0xFF, 0x00}, + {0x60, 0xFF, 0x00}, + {0x61, 0xFF, 0x00}, + {0x62, 0xFF, 0x00}, + {0x63, 0xFF, 0x00}, + {0x64, 0xFF, 0x00}, + {0x65, 0xFF, 0x00}, + {0x0F, 0x01, 0x00}, + {0xCD, 0x03, 0x03} +}; + +/* Config default audio infoframe */ +const struct RegSetEntry HDMITX_DeaultAudioInfo_Table[] = { + {0x0F, 0x01, 0x01}, + {0x68, 0xFF, 0x00}, + {0x69, 0xFF, 0x00}, + {0x6A, 0xFF, 0x00}, + {0x6B, 0xFF, 0x00}, + {0x6C, 0xFF, 0x00}, + {0x6D, 0xFF, 0x71}, + {0x0F, 0x01, 0x00}, + {0xCE, 0x03, 0x03} +}; + +const struct RegSetEntry HDMITX_Aud_CHStatus_LPCM_20bit_48Khz[] = { + {0x0F, 0x01, 0x01}, + {0x33, 0xFF, 0x00}, + {0x34, 0xFF, 0x18}, + {0x35, 0xFF, 0x00}, + {0x91, 0xFF, 0x00}, + {0x92, 0xFF, 0x00}, + {0x93, 0xFF, 0x01}, + {0x94, 0xFF, 0x00}, + {0x98, 0xFF, 0x02}, + {0x99, 0xFF, 0xDA}, + {0x0F, 0x01, 0x00} +}; + +const struct RegSetEntry HDMITX_AUD_SPDIF_2ch_24bit[] = { + {0x0F, 0x11, 0x00}, + {0x04, 0x14, 0x04}, + {0xE0, 0xFF, 0xD1}, + {0xE1, 0xFF, 0x01}, + {0xE2, 0xFF, 0xE4}, + {0xE3, 0xFF, 0x10}, + {0xE4, 0xFF, 0x00}, + {0xE5, 0xFF, 0x00}, + {0x04, 0x14, 0x00} +}; + +const struct RegSetEntry HDMITX_PwrOn_Table[] = { + /* PwrOn RCLK , IACLK ,TXCLK */ + {0x0F, 0x70, 0x00}, + /* PLL PwrOn */ + /* PwrOn DRV */ + {0x61, 0x20, 0x00}, + /* PwrOn XPLL */ + {0x62, 0x44, 0x00}, + /* PwrOn IPLL */ + {0x64, 0x40, 0x00}, + /* PLL Reset OFF */ + /* DRV_RST */ + {0x61, 0x10, 0x00}, + /* XP_RESETB */ + {0x62, 0x08, 0x08}, + /* IP_RESETB */ + {0x64, 0x04, 0x04} +}; + +struct it6161 { + struct drm_bridge bridge; + struct drm_connector connector; + struct i2c_client *i2c_mipi_rx; + struct i2c_client *i2c_hdmi_tx; + struct device_node *host_node; + struct mipi_dsi_device *dsi; + struct mutex mode_lock; + + struct regmap *regmap_mipi_rx; + struct regmap *regmap_hdmi_tx; + + u32 it6161_addr_hdmi_tx; + + struct gpio_desc *enable_gpio; + + u32 hdmi_tx_pclk; + u32 mipi_rx_rclk; + + /* video mode output to hdmi tx */ + struct drm_display_mode display_mode; + struct hdmi_avi_infoframe source_avi_infoframe; + + u8 mipi_rx_lane_count; + bool enable_drv_hold; + u8 hdmi_tx_output_color_space; + u8 hdmi_tx_input_color_space; + u8 hdmi_tx_mode; + u8 support_audio; + bool hdmi_mode; + u8 bAudioChannelEnable; + u8 bridge_enable; +}; + +struct it6161 *it6161; + +static const struct regmap_range it6161_mipi_rx_bridge_volatile_ranges[] = { + {.range_min = 0, .range_max = 0xFF}, +}; + +static const struct regmap_access_table it6161_mipi_rx_bridge_volatile_table = { + .yes_ranges = it6161_mipi_rx_bridge_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(it6161_mipi_rx_bridge_volatile_ranges), +}; + +static const struct regmap_config it6161_mipi_rx_bridge_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_table = &it6161_mipi_rx_bridge_volatile_table, + .cache_type = REGCACHE_NONE, +}; + +static const struct regmap_range it6161_hdmi_tx_bridge_volatile_ranges[] = { + {.range_min = 0, .range_max = 0xFF}, +}; + +static const struct regmap_access_table it6161_hdmi_tx_bridge_volatile_table = { + .yes_ranges = it6161_hdmi_tx_bridge_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(it6161_hdmi_tx_bridge_volatile_ranges), +}; + +static const struct regmap_config it6161_hdmi_tx_bridge_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_table = &it6161_hdmi_tx_bridge_volatile_table, + .cache_type = REGCACHE_NONE, +}; + +static int it6161_mipi_rx_read(struct it6161 *it6161, u32 reg_addr) +{ + struct device *dev = &it6161->i2c_mipi_rx->dev; + u32 value; + int err; + + err = regmap_read(it6161->regmap_mipi_rx, reg_addr, &value); + if (err < 0) { + DRM_DEV_ERROR(dev, "mipi rx read failed reg[0x%x] err: %d", reg_addr, err); + return err; + } + + return value; +} + +static int it6161_mipi_rx_write(struct it6161 *it6161, u32 addr, u32 val) +{ + struct device *dev = &it6161->i2c_mipi_rx->dev; + int err; + + err = regmap_write(it6161->regmap_mipi_rx, addr, val); + if (err < 0) { + DRM_DEV_ERROR(dev, "mipi rx write failed reg[0x%x] = 0x%x err = %d", + addr, val, err); + return err; + } + + return 0; +} + +static int it6161_mipi_rx_set_bits(struct it6161 *it6161, u32 reg, + u32 mask, u32 value) +{ + struct device *dev = &it6161->i2c_mipi_rx->dev; + int err; + + err = regmap_update_bits(it6161->regmap_mipi_rx, reg, mask, value); + if (err < 0) { + DRM_DEV_ERROR(dev, "mipi rx set reg[0x%x] = 0x%x mask = 0x%x failed err %d", + reg, value, mask, err); + return err; + } + + return 0; +} + +static int it6161_hdmi_tx_read(struct it6161 *it6161, u32 reg_addr) +{ + struct device *dev = &it6161->i2c_mipi_rx->dev; + u32 value; + int err; + + err = regmap_read(it6161->regmap_hdmi_tx, reg_addr, &value); + if (err < 0) { + DRM_DEV_ERROR(dev, "hdmi tx read failed reg[0x%x] err: %d", + reg_addr, err); + return err; + } + + return value; +} + +static int it6161_hdmi_tx_write(struct it6161 *it6161, u32 addr, u32 val) +{ + struct device *dev = &it6161->i2c_mipi_rx->dev; + int err; + + err = regmap_write(it6161->regmap_hdmi_tx, addr, val); + + if (err < 0) { + DRM_DEV_ERROR(dev, "hdmi tx write failed reg[0x%x] = 0x%x err = %d", + addr, val, err); + return err; + } + + return 0; +} + +static int it6161_hdmi_tx_set_bits(struct it6161 *it6161, u32 reg, + u32 mask, u32 value) +{ + int err; + struct device *dev = &it6161->i2c_mipi_rx->dev; + + err = regmap_update_bits(it6161->regmap_hdmi_tx, reg, mask, value); + if (err < 0) { + DRM_DEV_ERROR(dev, "hdmi tx set reg[0x%x] = 0x%x mask = 0x%x failed err %d", + reg, value, mask, err); + return err; + } + + return 0; +} + +static inline int it6161_hdmi_tx_change_bank(struct it6161 *it6161, int x) +{ + return it6161_hdmi_tx_set_bits(it6161, 0x0F, 0x03, x & 0x03); +} + +static inline struct it6161 *connector_to_it6161(struct drm_connector *c) +{ + return container_of(c, struct it6161, connector); +} + +static inline struct it6161 *bridge_to_it6161(struct drm_bridge *bridge) +{ + return container_of(bridge, struct it6161, bridge); +} + +static void mipi_rx_logic_reset(struct it6161 *it6161) +{ + it6161_mipi_rx_set_bits(it6161, 0x05, 0x08, 0x08); +} + +static void mipi_rx_logic_reset_release(struct it6161 *it6161) +{ + it6161_mipi_rx_set_bits(it6161, 0x05, 0x08, 0x00); +} + +static void it6161_mipi_rx_int_mask_disable(struct it6161 *it6161) +{ + it6161_mipi_rx_set_bits(it6161, 0x0F, 0x03, 0x00); + it6161_mipi_rx_write(it6161, 0x09, 0x00); + it6161_mipi_rx_write(it6161, 0x0A, 0x00); + it6161_mipi_rx_write(it6161, 0x0B, 0x00); +} + +static void it6161_mipi_rx_int_mask_enable(struct it6161 *it6161) +{ + it6161_hdmi_tx_set_bits(it6161, 0x0F, 0x03, 0x00); + it6161_mipi_rx_write(it6161, 0x09, 0x11); + it6161_mipi_rx_write(it6161, 0x0A, 0xc0); + it6161_mipi_rx_write(it6161, 0x0B, 0x40); +} + +static void it6161_hdmi_tx_int_mask_disable(struct it6161 *it6161) +{ + it6161_mipi_rx_set_bits(it6161, 0x0F, 0x03, 0x00); + it6161_hdmi_tx_write(it6161, REG_TX_INT_MASK1, 0xFF); + it6161_hdmi_tx_write(it6161, REG_TX_INT_MASK3, 0xFF); +} + +static void it6161_hdmi_tx_int_mask_enable(struct it6161 *it6161) +{ + it6161_hdmi_tx_set_bits(it6161, 0x0F, 0x03, 0x00); + it6161_hdmi_tx_write(it6161, REG_TX_INT_MASK1, + ~(B_TX_AUDIO_OVFLW_MASK | B_TX_DDC_FIFO_ERR_MASK | + B_TX_DDC_BUS_HANG_MASK | B_TX_HPD_MASK)); + it6161_hdmi_tx_write(it6161, REG_TX_INT_MASK3, ~B_TX_VIDSTABLE_MASK); +} + +static void it6161_hdmi_tx_write_table(struct it6161 *it6161, + const struct RegSetEntry table[], int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (table[i].mask == 0 && table[i].value == 0) + msleep(table[i].offset); + else if (table[i].mask == 0xFF) + it6161_hdmi_tx_write(it6161, table[i].offset, table[i].value); + else + it6161_hdmi_tx_set_bits(it6161, table[i].offset, table[i].mask, table[i].value); + } +} + +static inline void it6161_set_interrupts_active_level(enum it6161_active_level level) +{ + it6161_mipi_rx_set_bits(it6161, 0x0D, 0x02, level == HIGH ? 0x02 : 0x00); + it6161_hdmi_tx_set_bits(it6161, 0x05, 0xC0, level == HIGH ? 0x80 : 0x40); +} + +static void hdmi_tx_init(struct it6161 *it6161) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + + DRM_DEV_DEBUG_DRIVER(dev, "it6161 init\n"); + + it6161_hdmi_tx_write_table(it6161, HDMITX_Init_Table, + ARRAY_SIZE(HDMITX_Init_Table)); + it6161_hdmi_tx_write_table(it6161, HDMITX_PwrOn_Table, + ARRAY_SIZE(HDMITX_PwrOn_Table)); + it6161_hdmi_tx_write_table(it6161, HDMITX_DefaultVideo_Table, + ARRAY_SIZE(HDMITX_DefaultVideo_Table)); + it6161_hdmi_tx_write_table(it6161, HDMITX_SetHDMI_Table, + ARRAY_SIZE(HDMITX_SetHDMI_Table)); + it6161_hdmi_tx_write_table(it6161, HDMITX_DefaultAVIInfo_Table, + ARRAY_SIZE(HDMITX_DefaultAVIInfo_Table)); + it6161_hdmi_tx_write_table(it6161, HDMITX_DeaultAudioInfo_Table, + ARRAY_SIZE(HDMITX_DeaultAudioInfo_Table)); + it6161_hdmi_tx_write_table(it6161, HDMITX_Aud_CHStatus_LPCM_20bit_48Khz, + ARRAY_SIZE(HDMITX_Aud_CHStatus_LPCM_20bit_48Khz)); + it6161_hdmi_tx_write_table(it6161, HDMITX_AUD_SPDIF_2ch_24bit, + ARRAY_SIZE(HDMITX_AUD_SPDIF_2ch_24bit)); +} + +static bool mipi_rx_get_m_video_stable(struct it6161 *it6161) +{ + return it6161_mipi_rx_read(it6161, 0x0D) & 0x10; +} + +static bool mipi_rx_get_p_video_stable(struct it6161 *it6161) +{ + return it6161_mipi_rx_read(it6161, 0x0D) & 0x20; +} + +static void mipi_rx_afe_configuration(struct it6161 *it6161, u8 data_id) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + u8 MPLaneNum = (it6161->mipi_rx_lane_count - 1); + + DRM_DEV_DEBUG_DRIVER(dev, "afe configuration data_id: 0x%02x", data_id); + + if (data_id == RGB_18b) { + if (MPLaneNum == 3) + /* MPPCLKSel = 1; 4-lane : MCLK = 1/1 PCLK */ + it6161_mipi_rx_set_bits(it6161, 0x80, 0x1F, 0x02); + else if (MPLaneNum == 1) + /* MPPCLKSel = 6; 2-lane : MCLK = 1/1 PCLK */ + it6161_mipi_rx_set_bits(it6161, 0x80, 0x1F, 0x05); + else if (MPLaneNum == 0) + /* MPPCLKSel = 8; 1-lane : MCLK = 3/4 PCLK */ + it6161_mipi_rx_set_bits(it6161, 0x80, 0x1F, 0x08); + } else { + if (MPLaneNum == 3) + /* MPPCLKSel = 1; 4-lane : MCLK = 3/4 PCLK */ + it6161_mipi_rx_set_bits(it6161, 0x80, 0x1F, 0x02); + else if (MPLaneNum == 1) + /* MPPCLKSel = 3; 2-lane : MCLK = 3/4 PCLK */ + it6161_mipi_rx_set_bits(it6161, 0x80, 0x1F, 0x05); + else if (MPLaneNum == 0) + /* MPPCLKSel = 5; 1-lane : MCLK = 3/4 PCLK */ + it6161_mipi_rx_set_bits(it6161, 0x80, 0x1F, 0x0b); + } +} + +static void mipi_rx_configuration(struct it6161 *it6161) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + u8 mipi_lane_config = (it6161->mipi_rx_lane_count - 1); + + DRM_DEV_DEBUG_DRIVER(dev, "MIPI_LANE=%d\n", it6161->mipi_rx_lane_count); + + it6161_mipi_rx_set_bits(it6161, 0x10, 0x0F, 0x0F); + msleep(1); + it6161_mipi_rx_set_bits(it6161, 0x10, 0x0F, 0x00); + + mipi_rx_logic_reset(it6161); + msleep(1); + mipi_rx_logic_reset_release(it6161); + + it6161_mipi_rx_int_mask_disable(it6161); + + /* setup INT pin: active low */ + it6161_mipi_rx_set_bits(it6161, 0x0d, 0x02, 0x00); + + it6161_mipi_rx_set_bits(it6161, 0x0C, 0x0F, + (MPLaneSwap << 3) + (MPPNSwap << 2) + mipi_lane_config); + + it6161_mipi_rx_set_bits(it6161, 0x11, 0x3F, + (EnIOIDDQ << 5) + (EnStb2Rst << 4) + (EnExtStdby << 3) + + (EnStandby << 2) + (InvPCLK << 1) + InvMCLK); + + it6161_mipi_rx_set_bits(it6161, 0x12, 0x03, (PDREFCNT << 1) + PDREFCLK); + + it6161_mipi_rx_set_bits(it6161, 0x18, 0xf7, + (RegEnSyncErr << 7) + (SkipStg << 4) + HSSetNum); + it6161_mipi_rx_set_bits(it6161, 0x19, 0xf3, + (PPIDbgSel << 4) + (EnContCK << 1) + EnDeSkew); + it6161_mipi_rx_set_bits(it6161, 0x20, 0xf7, + (EOTPSel << 4) + (RegEnDummyECC << 2) + (RegIgnrBlk << 1) + RegIgnrNull); + it6161_mipi_rx_set_bits(it6161, 0x21, 0x07, LMDbgSel); + + it6161_mipi_rx_set_bits(it6161, 0x44, 0x3a, + (MREC_Update << 5) + (PREC_Update << 4) + (REGSELDEF << 3) + (RegAutoSync << 1)); + it6161_mipi_rx_set_bits(it6161, 0x4B, 0x1f, + (EnFReSync << 4) + (EnVREnh << 3) + EnVREnhSel); + it6161_mipi_rx_write(it6161, 0x4C, PPSFFRdStg); + it6161_mipi_rx_set_bits(it6161, 0x4D, 0x01, (PPSFFRdStg >> 8) & 0x01); + it6161_mipi_rx_set_bits(it6161, 0x4E, 0x0C, + (EnVReSync << 3) + (EnHReSync << 2)); + it6161_mipi_rx_set_bits(it6161, 0x4F, 0x03, EnFFAutoRst); + + it6161_mipi_rx_set_bits(it6161, 0x70, 0x01, EnMAvg); + it6161_mipi_rx_write(it6161, 0x72, MShift); + it6161_mipi_rx_write(it6161, 0x73, PShift); + it6161_mipi_rx_set_bits(it6161, 0x80, 0x20, ENABLE_MIPI_RX_EXTERNAL_CLOCK << 5); + + it6161_mipi_rx_write(it6161, 0x21, 0x00); + it6161_mipi_rx_set_bits(it6161, 0x84, 0x70, 0x00); + + it6161_mipi_rx_set_bits(it6161, 0xA0, 0x01, EnMBPM); + + /* enable auto detect format */ + it6161_mipi_rx_set_bits(it6161, 0x21, 0x08, 0x08); + + it6161_mipi_rx_set_bits(it6161, 0x70, 0x01, EnMAvg); + /* Video Clock Domain Reset */ + it6161_mipi_rx_set_bits(it6161, 0x05, 0x02, 0x02); + + if (EnMBPM) { + /* HRS offset */ + it6161_mipi_rx_write(it6161, 0xA1, 0x00); + /* VRS offset */ + it6161_mipi_rx_write(it6161, 0xA2, 0x00); + it6161_mipi_rx_write(it6161, 0xA3, 0x08); + it6161_mipi_rx_write(it6161, 0xA5, 0x04); + } + + if (REGSELDEF == false) { + it6161_mipi_rx_set_bits(it6161, 0x31, 0x80, 0x00); + it6161_mipi_rx_set_bits(it6161, 0x33, 0x80, 0x00); + it6161_mipi_rx_set_bits(it6161, 0x35, 0x80, 0x00); + it6161_mipi_rx_set_bits(it6161, 0x37, 0x80, 0x00); + it6161_mipi_rx_set_bits(it6161, 0x39, 0x80, 0x00); + it6161_mipi_rx_set_bits(it6161, 0x3A, 0x80, 0x00); + it6161_mipi_rx_set_bits(it6161, 0x3C, 0x80, 0x00); + it6161_mipi_rx_set_bits(it6161, 0x3E, 0x80, 0x00); + it6161_mipi_rx_set_bits(it6161, 0x41, 0x80, 0x00); + it6161_mipi_rx_set_bits(it6161, 0x43, 0x80, 0x00); + } +} + +static void mipi_rx_init(struct it6161 *it6161) +{ + mipi_rx_configuration(it6161); + /* Enable MPRX clock domain */ + it6161_mipi_rx_set_bits(it6161, 0x05, 0x03, 0x00); +} + +static void hdmi_tx_video_reset(struct it6161 *it6161) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + + DRM_DEV_DEBUG_DRIVER(dev, "reg04: 0x%02x reg05: 0x%02x reg6: 0x%02x reg07: 0x%02x reg08: 0x%02x reg0e: 0x%02x", + it6161_hdmi_tx_read(it6161, 0x04), + it6161_hdmi_tx_read(it6161, 0x05), + it6161_hdmi_tx_read(it6161, 0x06), + it6161_hdmi_tx_read(it6161, 0x07), + it6161_hdmi_tx_read(it6161, 0x08), + it6161_hdmi_tx_read(it6161, 0x0e)); + + it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST, B_HDMITX_VID_RST, B_HDMITX_VID_RST); + it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST, B_HDMITX_VID_RST, 0x00); + msleep(10); +} + +/* DDC master will set to be host */ +static void it6161_hdmi_tx_clear_ddc_fifo(struct it6161 *it6161) +{ + it6161_hdmi_tx_change_bank(it6161, 0); + it6161_hdmi_tx_write(it6161, REG_TX_DDC_MASTER_CTRL, + B_TX_MASTERDDC | B_TX_MASTERHOST); + it6161_hdmi_tx_write(it6161, REG_TX_DDC_CMD, CMD_FIFO_CLR); + it6161_hdmi_tx_set_bits(it6161, REG_TX_DDC_MASTER_CTRL, B_TX_MASTERHOST, 0x00); +} + +static void hdmi_tx_generate_blank_timing(struct it6161 *it6161) +{ + struct drm_display_mode *display_mode = &it6161->display_mode; + bool force_hdmi_tx_clock_stable = true; + bool force_hdmi_tx_video_stable = true; + bool hdmi_tx_by_pass_mode = false; + bool de_generation = false; + bool enable_de_only = true; + u8 polarity; + u16 hsync_start, hsync_end, vsync_start, vsync_end, htotal, hde_start, vtotal; + u16 vsync_start_2nd = 0, vsync_end_2nd = 0, vsync_rising_at_h_2nd; + + polarity = + ((display_mode->flags & DRM_MODE_FLAG_PHSYNC) == DRM_MODE_FLAG_PHSYNC) ? 0x02 : 0x00; + polarity |= + ((display_mode->flags & DRM_MODE_FLAG_PVSYNC) == DRM_MODE_FLAG_PVSYNC) ? 0x04 : 0x00; + + hsync_start = display_mode->hsync_start - display_mode->hdisplay - 1; + hsync_end = hsync_start + display_mode->hsync_end - display_mode->hsync_start; + vsync_rising_at_h_2nd = hsync_start + display_mode->htotal / 2; + hde_start = display_mode->htotal - display_mode->hsync_start; + + it6161_hdmi_tx_set_bits(it6161, 0xD1, 0x0C, + force_hdmi_tx_clock_stable << 3 | force_hdmi_tx_video_stable << 2); + it6161_hdmi_tx_set_bits(it6161, 0xA9, 0x80, hdmi_tx_by_pass_mode << 7); + it6161_hdmi_tx_set_bits(it6161, 0x90, 0x01, de_generation); + it6161_hdmi_tx_write(it6161, 0x91, vsync_rising_at_h_2nd >> 4); + it6161_hdmi_tx_set_bits(it6161, 0x90, 0xF0, (vsync_rising_at_h_2nd & 0x00F) << 4); + it6161_hdmi_tx_set_bits(it6161, 0x90, 0x06, polarity); + it6161_hdmi_tx_write(it6161, 0x95, (u8) hsync_start); + it6161_hdmi_tx_write(it6161, 0x96, (u8) hsync_end); + it6161_hdmi_tx_write(it6161, 0x97, (hsync_end & 0x0F00) >> 4 | hsync_start >> 8); + + vsync_start = display_mode->vsync_start - display_mode->vdisplay; + vsync_end = display_mode->vsync_end - display_mode->vdisplay; + + if ((display_mode->flags & DRM_MODE_FLAG_INTERLACE) != DRM_MODE_FLAG_INTERLACE) { + vsync_start_2nd = 0x0FFF; + vsync_end_2nd = 0x3F; + vtotal = display_mode->vtotal - 1; + it6161_hdmi_tx_set_bits(it6161, 0xA5, 0x10, 0x00); + } else { + vtotal = display_mode->vtotal * 2; + it6161_hdmi_tx_set_bits(it6161, 0xA5, 0x10, 0x10); + } + it6161_hdmi_tx_write(it6161, 0xA0, (u8) vsync_start); + it6161_hdmi_tx_write(it6161, 0xA1, (vsync_end & 0x0F) << 4 | vsync_start >> 8); + it6161_hdmi_tx_write(it6161, 0xA2, (u8) vsync_start_2nd); + it6161_hdmi_tx_write(it6161, 0xA6, (vsync_end_2nd & 0xF0) | vsync_end >> 4); + it6161_hdmi_tx_write(it6161, 0xA3, (vsync_end_2nd & 0x0F) << 4 | vsync_start_2nd >> 8); + it6161_hdmi_tx_write(it6161, 0xA4, vsync_rising_at_h_2nd); + + it6161_hdmi_tx_set_bits(it6161, 0xB1, 0x51, + (hsync_end & 0x1000) >> 6 | (hsync_start & 0x1000) >> 8 | hde_start >> 12); + it6161_hdmi_tx_set_bits(it6161, 0xA5, 0x2F, + enable_de_only << 5 | vsync_rising_at_h_2nd >> 8); + it6161_hdmi_tx_set_bits(it6161, 0xB2, 0x05, + (vsync_rising_at_h_2nd & 0x1000) >> 10 | (vsync_rising_at_h_2nd & 0x1000) >> 12); + + htotal = display_mode->htotal - 1; + it6161_hdmi_tx_set_bits(it6161, 0x90, 0xF0, (htotal & 0x0F) << 4); + it6161_hdmi_tx_write(it6161, 0x91, (htotal & 0x0FF0) >> 4); + it6161_hdmi_tx_set_bits(it6161, 0xB2, 0x01, (htotal & 0x1000) >> 12); + it6161_hdmi_tx_write(it6161, 0x98, vtotal & 0x0FF); + it6161_hdmi_tx_write(it6161, 0x99, (vtotal & 0xF00) >> 8); +} + +/* force abort DDC and reset DDC bus */ +static void it6161_hdmi_tx_abort_ddc(struct it6161 *it6161) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + u8 sw_reset, ddc_master, retry = 2; + u8 uc, timeout, i; + + DRM_DEV_DEBUG_DRIVER(dev, "ddc abort\n"); + /* save the sw reset, ddc master and cp desire setting */ + sw_reset = it6161_hdmi_tx_read(it6161, REG_TX_SW_RST); + ddc_master = it6161_hdmi_tx_read(it6161, REG_TX_DDC_MASTER_CTRL); + + it6161_hdmi_tx_write(it6161, REG_TX_SW_RST, sw_reset | B_TX_HDCP_RST_HDMITX); + it6161_hdmi_tx_write(it6161, REG_TX_DDC_MASTER_CTRL, B_TX_MASTERDDC | B_TX_MASTERHOST); + + /* do abort DDC */ + for (i = 0; i < retry; i++) { + it6161_hdmi_tx_write(it6161, REG_TX_DDC_CMD, CMD_DDC_ABORT); + it6161_hdmi_tx_write(it6161, REG_TX_DDC_CMD, CMD_GEN_SCLCLK); + + for (timeout = 0; timeout < 200; timeout++) { + uc = it6161_hdmi_tx_read(it6161, REG_TX_DDC_STATUS); + if (uc & B_TX_DDC_DONE) + break; + + if (uc & (B_TX_DDC_NOACK | B_TX_DDC_WAITBUS | B_TX_DDC_ARBILOSE)) { + DRM_DEV_ERROR(dev, "it6161_hdmi_tx_abort_ddc Fail by reg16=%02X\n", (int)uc); + break; + } + /* delay 1 ms to stable */ + msleep(1); + } + } +} + +static bool hdmi_tx_get_video_state(struct it6161 *it6161) +{ + return B_TXVIDSTABLE & it6161_hdmi_tx_read(it6161, REG_TX_SYS_STATUS); +} + +static inline bool hdmi_tx_get_sink_hpd(struct it6161 *it6161) +{ + return it6161_hdmi_tx_read(it6161, REG_TX_SYS_STATUS) & B_TX_HPDETECT; +} + +static bool it6161_ddc_op_finished(struct it6161 *it6161) +{ + int reg16 = it6161_hdmi_tx_read(it6161, REG_TX_DDC_STATUS); + + if (reg16 < 0) + return false; + + return (reg16 & B_TX_DDC_DONE) == B_TX_DDC_DONE; +} + +static int it6161_ddc_wait(struct it6161 *it6161) +{ + struct device *dev = &it6161->i2c_mipi_rx->dev; + int status; + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(AUX_WAIT_TIMEOUT_MS) + 1; + + while (!it6161_ddc_op_finished(it6161)) { + if (time_after(jiffies, timeout)) { + DRM_DEV_ERROR(dev, "Timed out waiting AUX to finish"); + return -ETIMEDOUT; + } + usleep_range(1000, 2000); + } + + status = it6161_hdmi_tx_read(it6161, REG_TX_DDC_STATUS); + if (status < 0) { + DRM_DEV_ERROR(dev, "Failed to read DDC channel: 0x%02x", status); + return status; + } + + if (status & B_TX_DDC_DONE) + return 0; + else { + DRM_DEV_ERROR(dev, "DDC error: 0x%02x", status); + return -EIO; + } +} + +static void hdmi_tx_ddc_operation(struct it6161 *it6161, u8 addr, u8 offset, u8 size, + u8 segment, u8 cmd) +{ + size = min_t(u8, size, DDC_FIFO_MAXREQ); + it6161_hdmi_tx_change_bank(it6161, 0); + it6161_hdmi_tx_write(it6161, REG_TX_DDC_MASTER_CTRL, B_TX_MASTERDDC | B_TX_MASTERHOST); + it6161_hdmi_tx_write(it6161, REG_TX_DDC_HEADER, addr); + it6161_hdmi_tx_write(it6161, REG_TX_DDC_REQOFF, offset); + it6161_hdmi_tx_write(it6161, REG_TX_DDC_REQCOUNT, size); + it6161_hdmi_tx_write(it6161, REG_TX_DDC_EDIDSEG, segment); + it6161_hdmi_tx_write(it6161, REG_TX_DDC_CMD, cmd); +} + +static int it6161_ddc_get_edid_operation(struct it6161 *it6161, u8 *buffer, + u8 segment, u8 offset, u8 size) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + int status, i; + + if (!buffer) + return -ENOMEM; + + if (it6161_hdmi_tx_read(it6161, REG_TX_INT_STAT1) & B_TX_INT_DDC_BUS_HANG) { + DRM_DEV_ERROR(dev, "Called it6161_hdmi_tx_abort_ddc()"); + it6161_hdmi_tx_abort_ddc(it6161); + } + + it6161_hdmi_tx_clear_ddc_fifo(it6161); + status = it6161_ddc_wait(it6161); + if (status < 0) + goto error; + + hdmi_tx_ddc_operation(it6161, DDC_EDID_ADDRESS, offset, size, segment, CMD_EDID_READ); + status = it6161_ddc_wait(it6161); + if (status < 0) + goto error; + + for (i = 0; i < size; i++) { + status = it6161_hdmi_tx_read(it6161, REG_TX_DDC_READFIFO); + if (status < 0) + goto error; + + buffer[i] = status; + } + + return i; + +error: + return status; +} + +static int it6161_get_edid_block(void *data, u8 *buf, u32 block_num, size_t len) +{ + struct it6161 *it6161 = data; + u8 offset, step = 8; + int ret; + + step = min_t(u8, step, DDC_FIFO_MAXREQ); + + for (offset = 0; offset < len; offset += step) { + ret = it6161_ddc_get_edid_operation(it6161, buf + offset, + block_num / 2, (block_num % 2) * EDID_LENGTH + offset, step); + if (ret < 0) + return ret; + } + return 0; +} + +static void hdmi_tx_set_capability_from_edid_parse(struct it6161 *it6161, struct edid *edid) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + struct drm_display_info *info = &it6161->connector.display_info; + + it6161->hdmi_mode = drm_detect_hdmi_monitor(edid); + it6161->support_audio = drm_detect_monitor_audio(edid); + + it6161->hdmi_tx_output_color_space = OUTPUT_COLOR_MODE; + it6161->hdmi_tx_input_color_space = INPUT_COLOR_MODE; + if (it6161->hdmi_tx_output_color_space == F_MODE_YUV444) { + if ((info->color_formats & DRM_COLOR_FORMAT_YCRCB444) != DRM_COLOR_FORMAT_YCRCB444) { + it6161->hdmi_tx_output_color_space &= ~F_MODE_CLRMOD_MASK; + it6161->hdmi_tx_output_color_space |= F_MODE_RGB444; + } + } + + if (it6161->hdmi_tx_output_color_space == F_MODE_YUV422) { + if ((info->color_formats & DRM_COLOR_FORMAT_YCRCB422) != DRM_COLOR_FORMAT_YCRCB422) { + it6161->hdmi_tx_output_color_space &= ~F_MODE_CLRMOD_MASK; + it6161->hdmi_tx_output_color_space |= F_MODE_RGB444; + } + } + DRM_DEV_DEBUG_DRIVER(dev, "%s mode, monitor %ssupport audio, outputcolormode:%d color_formats:0x%08x color_depth:%d", + it6161->hdmi_mode ? "HDMI" : "DVI", + it6161->support_audio ? "" : "not ", + it6161->hdmi_tx_output_color_space, + info->color_formats, + info->bpc); + + if ((info->color_formats & DRM_COLOR_FORMAT_RGB444) == DRM_COLOR_FORMAT_RGB444) + DRM_DEV_INFO(dev, "support RGB444 output"); + if ((info->color_formats & DRM_COLOR_FORMAT_YCRCB444) == DRM_COLOR_FORMAT_YCRCB444) + DRM_DEV_INFO(dev, "support YUV444 output"); + if ((info->color_formats & DRM_COLOR_FORMAT_YCRCB422) == DRM_COLOR_FORMAT_YCRCB422) + DRM_DEV_INFO(dev, "support YUV422 output"); +} + +static void it6161_variable_config(struct it6161 *it6161) +{ + it6161->hdmi_tx_mode = HDMI_TX_MODE; + it6161->mipi_rx_lane_count = MIPI_RX_LANE_COUNT; +} + +static struct edid *it6161_get_edid(struct it6161 *it6161) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + struct edid *edid; + + edid = drm_do_get_edid(&it6161->connector, it6161_get_edid_block, it6161); + if (!edid) { + DRM_DEV_ERROR(dev, "Failed to read EDID\n"); + return 0; + } + + hdmi_tx_set_capability_from_edid_parse(it6161, edid); + + return edid; +} + +static int it6161_get_modes(struct drm_connector *connector) +{ + struct it6161 *it6161 = connector_to_it6161(connector); + int err, num_modes = 0; + struct edid *edid; + struct device *dev = &it6161->i2c_mipi_rx->dev; + + mutex_lock(&it6161->mode_lock); + + edid = it6161_get_edid(it6161); + if (!edid) { + DRM_DEV_ERROR(dev, "Failed to read EDID\n"); + return 0; + } + + err = drm_connector_update_edid_property(connector, edid); + if (err) { + DRM_DEV_ERROR(dev, "Failed to update EDID property: %d", err); + goto unlock; + } + + num_modes = drm_add_edid_modes(connector, edid); + + kfree(edid); +unlock: + DRM_DEV_DEBUG_DRIVER(dev, "edid mode number:%d", num_modes); + mutex_unlock(&it6161->mode_lock); + + return num_modes; +} + +static const struct drm_connector_helper_funcs it6161_connector_helper_funcs = { + .get_modes = it6161_get_modes, +}; + +static enum drm_connector_status it6161_detect(struct drm_connector *connector, bool force) +{ + struct it6161 *it6161 = connector_to_it6161(connector); + struct device *dev = &it6161->i2c_hdmi_tx->dev; + enum drm_connector_status status = connector_status_disconnected; + bool hpd; + + hpd = hdmi_tx_get_sink_hpd(it6161); + if (hpd) { + it6161_variable_config(it6161); + status = connector_status_connected; + } + DRM_DEV_INFO(dev, "hpd:%s\n", hpd ? "high" : "low"); + + it6161_set_interrupts_active_level(HIGH); + it6161_mipi_rx_int_mask_enable(it6161); + it6161_hdmi_tx_int_mask_enable(it6161); + + return status; +} + +static const struct drm_connector_funcs it6161_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = it6161_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int it6161_attach_dsi(struct it6161 *it6161) +{ + struct mipi_dsi_host *host; + struct mipi_dsi_device *dsi; + int ret = 0; + const struct mipi_dsi_device_info info = {.type = "it6161", }; + struct device *dev = &it6161->i2c_hdmi_tx->dev; + + DRM_DEV_DEBUG_DRIVER(dev, "attach\n"); + host = of_find_mipi_dsi_host_by_node(it6161->host_node); + if (!host) { + DRM_DEV_ERROR(dev, "it6161 failed to find dsi host\n"); + return -EPROBE_DEFER; + } + + dsi = mipi_dsi_device_register_full(host, &info); + if (IS_ERR(dsi)) { + DRM_DEV_ERROR(dev, "it6161 failed to create dsi device\n"); + ret = PTR_ERR(dsi); + goto err_dsi_device; + } + + it6161->dsi = dsi; + + dsi->lanes = MIPI_RX_LANE_COUNT; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "it6161 failed to attach dsi to host\n"); + goto err_dsi_attach; + } + + return 0; + +err_dsi_attach: + mipi_dsi_device_unregister(dsi); +err_dsi_device: + return ret; +} + +static int it6161_connector_init(struct drm_bridge *bridge, struct it6161 *it6161) +{ + struct device *dev; + int ret; + + dev = &it6161->i2c_mipi_rx->dev; + + if (!bridge->encoder) { + DRM_DEV_ERROR(dev, "Parent encoder object not found"); + return -ENODEV; + } + + it6161->connector.polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(bridge->dev, &it6161->connector, + &it6161_connector_funcs, DRM_MODE_CONNECTOR_HDMIA); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to initialize connector: %d", ret); + return ret; + } + + drm_connector_helper_add(&it6161->connector, &it6161_connector_helper_funcs); + drm_connector_attach_encoder(&it6161->connector, bridge->encoder); + + return 0; +} + +static int it6161_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct it6161 *it6161 = bridge_to_it6161(bridge); + int ret; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + ret = it6161_connector_init(bridge, it6161); + if (ret < 0) + return ret; + } + + ret = it6161_attach_dsi(it6161); + + return ret; +} + +static void it6161_detach_dsi(struct it6161 *it6161) +{ + mipi_dsi_detach(it6161->dsi); + mipi_dsi_device_unregister(it6161->dsi); +} + +static void it6161_bridge_detach(struct drm_bridge *bridge) +{ + struct it6161 *it6161 = bridge_to_it6161(bridge); + + drm_connector_unregister(&it6161->connector); + drm_connector_cleanup(&it6161->connector); + it6161_detach_dsi(it6161); +} + +static enum drm_mode_status +it6161_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->clock > 108000) + return MODE_CLOCK_HIGH; + + /* TODO, Only 480p60 work with imx8ulp now */ + if (mode->vdisplay > 480) + return MODE_BAD_VVALUE; + + return MODE_OK; +} + +static void it6161_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct it6161 *it6161 = bridge_to_it6161(bridge); + struct device *dev = &it6161->i2c_hdmi_tx->dev; + u8 polarity; + + DRM_DEV_DEBUG_DRIVER(dev, " mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(mode)); + DRM_DEV_DEBUG_DRIVER(dev, "adj mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adjusted_mode)); + + memcpy(&it6161->display_mode, mode, sizeof(struct drm_display_mode)); + + polarity = ((adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) == DRM_MODE_FLAG_PHSYNC) ? 0x01 : 0x00; + polarity |= ((adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) == DRM_MODE_FLAG_PVSYNC) ? 0x02 : 0x00; + + it6161_mipi_rx_set_bits(it6161, 0x4E, 0x03, polarity); +} + +static void it6161_bridge_enable(struct drm_bridge *bridge) +{ + struct it6161 *it6161 = bridge_to_it6161(bridge); + struct device *dev = &it6161->i2c_hdmi_tx->dev; + + DRM_DEV_DEBUG_DRIVER(dev, "start"); + + if (it6161->bridge_enable) + return; + + mipi_rx_init(it6161); + hdmi_tx_init(it6161); + it6161_set_interrupts_active_level(HIGH); + it6161_mipi_rx_int_mask_enable(it6161); + it6161_hdmi_tx_int_mask_enable(it6161); + + it6161->bridge_enable = true; + +} + +static void it6161_bridge_disable(struct drm_bridge *bridge) +{ + struct it6161 *it6161 = bridge_to_it6161(bridge); + struct device *dev = &it6161->i2c_hdmi_tx->dev; + + DRM_DEV_DEBUG_DRIVER(dev, "start"); + + /* only keep HPD for cabe detect */ + it6161_mipi_rx_int_mask_disable(it6161); + it6161_hdmi_tx_int_mask_disable(it6161); + it6161_hdmi_tx_set_bits(it6161, 0x0F, 0x03, 0x00); + it6161_hdmi_tx_write(it6161, REG_TX_INT_MASK1, ~B_TX_HPD_MASK); + + it6161->bridge_enable = false; +} + +static enum drm_connector_status it6161_bridge_detect(struct drm_bridge *bridge) +{ + struct it6161 *it6161 = bridge_to_it6161(bridge); + enum drm_connector_status status = connector_status_disconnected; + bool hpd = hdmi_tx_get_sink_hpd(it6161); + struct device *dev = &it6161->i2c_hdmi_tx->dev; + + DRM_DEV_DEBUG_DRIVER(dev, "hpd:%s", hpd ? "high" : "low"); + + if (hpd) { + it6161_variable_config(it6161); + status = connector_status_connected; + } + + it6161_set_interrupts_active_level(HIGH); + it6161_mipi_rx_int_mask_enable(it6161); + it6161_hdmi_tx_int_mask_enable(it6161); + + return status; +} + +static struct edid *it6161_bridge_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct it6161 *it6161 = bridge_to_it6161(bridge); + + return it6161_get_edid(it6161); +} + +static const struct drm_bridge_funcs it6161_bridge_funcs = { + .attach = it6161_bridge_attach, + .detach = it6161_bridge_detach, + .mode_valid = it6161_bridge_mode_valid, + .mode_set = it6161_bridge_mode_set, + .enable = it6161_bridge_enable, + .disable = it6161_bridge_disable, + .detect = it6161_bridge_detect, + .get_edid = it6161_bridge_get_edid, +}; + +static bool it6161_check_device_ready(struct it6161 *it6161) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + u8 Vendor_ID[2], Device_ID[2]; + + Vendor_ID[0] = it6161_mipi_rx_read(it6161, 0x00); + Vendor_ID[1] = it6161_mipi_rx_read(it6161, 0x01); + Device_ID[0] = it6161_mipi_rx_read(it6161, 0x02); + Device_ID[1] = it6161_mipi_rx_read(it6161, 0x03); + if (Vendor_ID[0] == 0x54 && Vendor_ID[1] == 0x49 && + Device_ID[0] == 0x61 && Device_ID[1] == 0x61) { + DRM_DEV_INFO(dev, "Find it6161 revision: 0x%2x", + (u32) it6161_mipi_rx_read(it6161, 0x04)); + return true; + } + DRM_DEV_INFO(dev, "find it6161 Fail"); + return false; +} + +static void it6161_hdmi_tx_set_av_mute(struct it6161 *it6161, u8 bEnable) +{ + it6161_hdmi_tx_change_bank(it6161, 0); + it6161_hdmi_tx_set_bits(it6161, REG_TX_GCP, + B_TX_SETAVMUTE, bEnable ? B_TX_SETAVMUTE : 0); + it6161_hdmi_tx_write(it6161, REG_TX_PKT_GENERAL_CTRL, + B_TX_ENABLE_PKT | B_TX_REPEAT_PKT); +} + +static void hdmi_tx_setup_pclk_div2(struct it6161 *it6161) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + + if (HDMI_TX_PCLK_DIV2) { + DRM_DEV_DEBUG_DRIVER(dev, "PCLK Divided by 2 mode"); + it6161_hdmi_tx_set_bits(it6161, REG_TX_INPUT_MODE, + B_TX_PCLKDIV2, B_TX_PCLKDIV2); + } +} + +/************************************************************************* + * Function: hdmi_tx_setup_csc + * Parameter: input_mode - + * D[1:0] - Color Mode + * D[4] - Colorimetry 0: ITU_BT601 1: ITU_BT709 + * D[5] - Quantization 0: 0_255 1: 16_235 + * D[6] - Up/Dn Filter 'Required' + * 0: no up/down filter + * 1: enable up/down filter when csc need. + * D[7] - Dither Filter 'Required' + * 0: no dither enabled. + * 1: enable dither and dither free go "when required". + * output_mode - + * D[1:0] - Color mode. + * Return: N/A + * Remark: reg72~reg8D will be programmed depended the input with table + * **********************************************************************/ +static void hdmi_tx_setup_csc(struct it6161 *it6161) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + u8 ucData, csc = 0, i; + u8 filter = 0; /* filter is for Video CTRL DN_FREE_GO,EN_DITHER,and ENUDFILT */ + u8 input_mode = it6161->hdmi_tx_input_color_space; + u8 output_mode = it6161->hdmi_tx_output_color_space; + u8 *ptable = NULL; + + /* (1) YUV422 in,RGB/YUV444 output (Output is 8-bit,input is 12-bit) + * (2) YUV444/422 in,RGB output (CSC enable,and output is not YUV422) + * (3) RGB in,YUV444 output (CSC enable,and output is not YUV422) + * + * YUV444/RGB24 <-> YUV422 need set up/down filter. + */ + DRM_DEV_DEBUG_DRIVER(dev, "hdmi_tx_setup_csc(u8 input_mode = %x,u8 output_mode = %x)\n", + (int)input_mode, (int)output_mode); + + switch (input_mode & F_MODE_CLRMOD_MASK) { + /* YUV444 INPUT */ + case F_MODE_YUV444: + switch (output_mode & F_MODE_CLRMOD_MASK) { + case F_MODE_YUV444: + csc = B_HDMITX_CSC_BYPASS; + break; + case F_MODE_YUV422: + /* YUV444 to YUV422 need up/down filter for processing. */ + if (input_mode & F_VIDMODE_EN_UDFILT) + filter |= B_TX_EN_UDFILTER; + csc = B_HDMITX_CSC_BYPASS; + break; + case F_MODE_RGB444: + csc = B_HDMITX_CSC_YUV2RGB; + /* YUV444 to RGB24 need dither */ + if (input_mode & F_VIDMODE_EN_DITHER) + filter |= B_TX_EN_DITHER | B_TX_DNFREE_GO; + break; + } + break; + + /* YUV422 INPUT */ + case F_MODE_YUV422: + switch (output_mode & F_MODE_CLRMOD_MASK) { + case F_MODE_YUV444: + csc = B_HDMITX_CSC_BYPASS; + if (input_mode & F_VIDMODE_EN_UDFILT) + filter |= B_TX_EN_UDFILTER; + else if (input_mode & F_VIDMODE_EN_DITHER) + filter |= B_TX_EN_DITHER | B_TX_DNFREE_GO; + break; + case F_MODE_YUV422: + csc = B_HDMITX_CSC_BYPASS; + break; + case F_MODE_RGB444: + csc = B_HDMITX_CSC_YUV2RGB; + if (input_mode & F_VIDMODE_EN_UDFILT) + filter |= B_TX_EN_UDFILTER; + else if (input_mode & F_VIDMODE_EN_DITHER) + filter |= B_TX_EN_DITHER | B_TX_DNFREE_GO; + break; + } + break; + + /* RGB444 INPUT */ + case F_MODE_RGB444: + switch (output_mode & F_MODE_CLRMOD_MASK) { + case F_MODE_YUV444: + csc = B_HDMITX_CSC_RGB2YUV; + if (input_mode & F_VIDMODE_EN_DITHER) + filter |= B_TX_EN_DITHER | B_TX_DNFREE_GO; + break; + case F_MODE_YUV422: + if (input_mode & F_VIDMODE_EN_UDFILT) + filter |= B_TX_EN_UDFILTER; + else if (input_mode & F_VIDMODE_EN_DITHER) + filter |= B_TX_EN_DITHER | B_TX_DNFREE_GO; + csc = B_HDMITX_CSC_RGB2YUV; + break; + case F_MODE_RGB444: + csc = B_HDMITX_CSC_BYPASS; + break; + } + break; + } + + /* set the CSC metrix registers by colorimetry and quantization */ + if (csc == B_HDMITX_CSC_RGB2YUV) { + switch (input_mode & (F_VIDMODE_ITU709 | F_VIDMODE_16_235)) { + case F_VIDMODE_ITU709 | F_VIDMODE_16_235: + ptable = bCSCMtx_RGB2YUV_ITU709_16_235; + break; + case F_VIDMODE_ITU709 | F_VIDMODE_0_255: + ptable = bCSCMtx_RGB2YUV_ITU709_0_255; + break; + case F_VIDMODE_ITU601 | F_VIDMODE_16_235: + ptable = bCSCMtx_RGB2YUV_ITU601_16_235; + break; + case F_VIDMODE_ITU601 | F_VIDMODE_0_255: + default: + ptable = bCSCMtx_RGB2YUV_ITU601_0_255; + break; + } + } + + if (csc == B_HDMITX_CSC_YUV2RGB) { + switch (input_mode & (F_VIDMODE_ITU709 | F_VIDMODE_16_235)) { + case F_VIDMODE_ITU709 | F_VIDMODE_16_235: + ptable = bCSCMtx_YUV2RGB_ITU709_16_235; + break; + case F_VIDMODE_ITU709 | F_VIDMODE_0_255: + ptable = bCSCMtx_YUV2RGB_ITU709_0_255; + break; + case F_VIDMODE_ITU601 | F_VIDMODE_16_235: + ptable = bCSCMtx_YUV2RGB_ITU601_16_235; + break; + case F_VIDMODE_ITU601 | F_VIDMODE_0_255: + default: + ptable = bCSCMtx_YUV2RGB_ITU601_0_255; + break; + } + } + + if (csc == B_HDMITX_CSC_BYPASS) + it6161_hdmi_tx_set_bits(it6161, 0xF, 0x10, 0x10); + else { + if (ptable != NULL) + for (i = 0; i < SIZEOF_CSCMTX; i++) + it6161_hdmi_tx_write(it6161, REG_TX_CSC_YOFF + i, ptable[i]); + it6161_hdmi_tx_set_bits(it6161, 0xF, 0x10, 0x00); + } + + ucData = it6161_hdmi_tx_read(it6161, + REG_TX_CSC_CTRL) & ~(M_TX_CSC_SEL | + B_TX_DNFREE_GO | + B_TX_EN_DITHER | + B_TX_EN_UDFILTER); + ucData |= filter | csc; + + it6161_hdmi_tx_write(it6161, REG_TX_CSC_CTRL, ucData); +} + +static void hdmi_tx_setup_afe(struct it6161 *it6161, u8 level) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + + it6161_hdmi_tx_write(it6161, REG_TX_AFE_DRV_CTRL, B_TX_AFE_DRV_RST); + switch (level) { + case PCLK_HIGH: + it6161_hdmi_tx_set_bits(it6161, 0x62, 0x90, 0x80); + it6161_hdmi_tx_set_bits(it6161, 0x64, 0x89, 0x80); + it6161_hdmi_tx_set_bits(it6161, 0x68, 0x10, 0x00); + it6161_hdmi_tx_set_bits(it6161, 0x66, 0x80, 0x80); + break; + default: + it6161_hdmi_tx_set_bits(it6161, 0x62, 0x90, 0x10); + it6161_hdmi_tx_set_bits(it6161, 0x64, 0x89, 0x09); + it6161_hdmi_tx_set_bits(it6161, 0x68, 0x10, 0x10); + break; + } + DRM_DEV_DEBUG_DRIVER(dev, "setup afe: %s", level ? "high" : "low"); +} + +static void hdmi_tx_fire_afe(struct it6161 *it6161) +{ + it6161_hdmi_tx_change_bank(it6161, 0x00); + it6161_hdmi_tx_write(it6161, REG_TX_AFE_DRV_CTRL, 0x00); +} + +static void hdmi_tx_disable_video_output(struct it6161 *it6161) +{ + it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST, B_HDMITX_VID_RST, B_HDMITX_VID_RST); + it6161_hdmi_tx_write(it6161, REG_TX_AFE_DRV_CTRL, B_TX_AFE_DRV_RST | B_TX_AFE_DRV_PWD); + it6161_hdmi_tx_set_bits(it6161, 0x62, 0x90, 0x00); + it6161_hdmi_tx_set_bits(it6161, 0x64, 0x89, 0x00); +} + +static void hdmi_tx_enable_video_output(struct it6161 *it6161, u8 level) +{ + it6161_hdmi_tx_write(it6161, REG_TX_SW_RST, + B_HDMITX_AUD_RST | B_TX_AREF_RST | B_TX_HDCP_RST_HDMITX); + it6161_hdmi_tx_change_bank(it6161, 1); + it6161_hdmi_tx_write(it6161, REG_TX_AVIINFO_DB1, 0x00); + it6161_hdmi_tx_change_bank(it6161, 0); + + if (it6161->hdmi_mode) + it6161_hdmi_tx_set_av_mute(it6161, true); + + hdmi_tx_setup_pclk_div2(it6161); + hdmi_tx_setup_csc(it6161); + it6161_hdmi_tx_write(it6161, REG_TX_HDMI_MODE, + it6161->hdmi_mode ? B_TX_HDMI_MODE : B_TX_DVI_MODE); + hdmi_tx_setup_afe(it6161, level); + hdmi_tx_fire_afe(it6161); +} + +static void setHDMITX_ChStat(struct it6161 *it6161, u8 ucIEC60958ChStat[]) +{ + u8 uc; + + it6161_hdmi_tx_change_bank(it6161, 1); + uc = (ucIEC60958ChStat[0] << 1) & 0x7C; + it6161_hdmi_tx_write(it6161, REG_TX_AUDCHST_MODE, uc); + it6161_hdmi_tx_write(it6161, REG_TX_AUDCHST_CAT, ucIEC60958ChStat[1]); + it6161_hdmi_tx_write(it6161, REG_TX_AUDCHST_SRCNUM, ucIEC60958ChStat[2] & 0xF); + it6161_hdmi_tx_write(it6161, REG_TX_AUD0CHST_CHTNUM, (ucIEC60958ChStat[2] >> 4) & 0xF); + it6161_hdmi_tx_write(it6161, REG_TX_AUDCHST_CA_FS, ucIEC60958ChStat[3]); + it6161_hdmi_tx_write(it6161, REG_TX_AUDCHST_OFS_WL, ucIEC60958ChStat[4]); + it6161_hdmi_tx_change_bank(it6161, 0); +} + +static void setHDMITX_LPCMAudio(u8 AudioSrcNum, u8 AudSWL, u8 bAudInterface) +{ + u8 AudioEnable = 0, AudioFormat = 0, bTDMSetting; + + switch (AudSWL) { + case 16: + AudioEnable |= M_TX_AUD_16BIT; + break; + case 18: + AudioEnable |= M_TX_AUD_18BIT; + break; + case 20: + AudioEnable |= M_TX_AUD_20BIT; + break; + case 24: + default: + AudioEnable |= M_TX_AUD_24BIT; + break; + } + if (bAudInterface == SPDIF) { + AudioFormat &= ~0x40; + AudioEnable |= B_TX_AUD_SPDIF | B_TX_AUD_EN_I2S0; + } else { + AudioFormat |= 0x40; + switch (AudioSrcNum) { + case 4: + AudioEnable |= + B_TX_AUD_EN_I2S3 | B_TX_AUD_EN_I2S2 | + B_TX_AUD_EN_I2S1 | B_TX_AUD_EN_I2S0; + break; + + case 3: + AudioEnable |= + B_TX_AUD_EN_I2S2 | B_TX_AUD_EN_I2S1 | B_TX_AUD_EN_I2S0; + break; + + case 2: + AudioEnable |= B_TX_AUD_EN_I2S1 | B_TX_AUD_EN_I2S0; + break; + + case 1: + default: + AudioFormat &= ~0x40; + AudioEnable |= B_TX_AUD_EN_I2S0; + break; + + } + } + AudioFormat |= 0x01; + it6161->bAudioChannelEnable = AudioEnable; + + it6161_hdmi_tx_change_bank(it6161, 0); + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, AudioEnable & 0xF0); + + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL1, AudioFormat); + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_FIFOMAP, 0xE4); + + if (bAudInterface == SPDIF) + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL3, B_TX_CHSTSEL); + else + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL3, 0); + + it6161_hdmi_tx_write(it6161, REG_TX_AUD_SRCVALID_FLAT, 0x00); + it6161_hdmi_tx_write(it6161, REG_TX_AUD_HDAUDIO, 0x00); + + if (bAudInterface == SPDIF) { + u8 i; + + it6161_hdmi_tx_set_bits(it6161, 0x5c, (1 << 6), (1 << 6)); + for (i = 0; i < 100; i++) + if (it6161_hdmi_tx_read(it6161, REG_TX_CLK_STATUS2) & B_TX_OSF_LOCK) + break; /* stable clock. */ + } else { + bTDMSetting = it6161_hdmi_tx_read(it6161, REG_TX_AUD_HDAUDIO); + if (bAudInterface == TDM) { + bTDMSetting |= B_TX_TDM; + bTDMSetting &= 0x9F; + bTDMSetting |= (AudioSrcNum - 1) << 5; + } else + bTDMSetting &= ~B_TX_TDM; + + /* 1 channel NLPCM, no TDM mode. */ + it6161_hdmi_tx_write(it6161, REG_TX_AUD_HDAUDIO, bTDMSetting); + } +} + +static void setHDMITX_NLPCMAudio(u8 bAudInterface) +{ + u8 AudioEnable, AudioFormat; + u8 i; + + /* NLPCM must use standard I2S mode. */ + AudioFormat = 0x01; + if (bAudInterface == SPDIF) + AudioEnable = M_TX_AUD_24BIT | B_TX_AUD_SPDIF; + else + AudioEnable = M_TX_AUD_24BIT; + + it6161_hdmi_tx_change_bank(it6161, 0); + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, AudioEnable); + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL1, 0x01); + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_FIFOMAP, 0xE4); + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL3, B_TX_CHSTSEL); + it6161_hdmi_tx_write(it6161, REG_TX_AUD_SRCVALID_FLAT, 0x00); + it6161_hdmi_tx_write(it6161, REG_TX_AUD_HDAUDIO, 0x00); + + if (bAudInterface == SPDIF) { + for (i = 0; i < 100; i++) + if (it6161_hdmi_tx_read(it6161, REG_TX_CLK_STATUS2) & B_TX_OSF_LOCK) + break; + } else { + i = it6161_hdmi_tx_read(it6161, REG_TX_AUD_HDAUDIO); + i &= ~B_TX_TDM; + /* 2 channel NLPCM, no TDM mode. */ + it6161_hdmi_tx_write(it6161, REG_TX_AUD_HDAUDIO, i); + } + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, + AudioEnable | B_TX_AUD_EN_I2S0); +} + +static void setHDMITX_HBRAudio(u8 bAudInterface) +{ + it6161_hdmi_tx_change_bank(it6161, 0); + + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL1, 0x47); + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_FIFOMAP, 0xE4); + + if (bAudInterface == SPDIF) { + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, + M_TX_AUD_24BIT | B_TX_AUD_SPDIF); + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL3, B_TX_CHSTSEL); + } else { + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, M_TX_AUD_24BIT); + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL3, 0); + } + it6161_hdmi_tx_write(it6161, REG_TX_AUD_SRCVALID_FLAT, 0x08); + it6161_hdmi_tx_write(it6161, REG_TX_AUD_HDAUDIO, B_TX_HBR); + + if (bAudInterface == SPDIF) { + u8 i; + + for (i = 0; i < 100; i++) + if (it6161_hdmi_tx_read(it6161, REG_TX_CLK_STATUS2) & B_TX_OSF_LOCK) + break; + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, + M_TX_AUD_24BIT | B_TX_AUD_SPDIF | B_TX_AUD_EN_SPDIF); + } else { + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, + M_TX_AUD_24BIT | B_TX_AUD_EN_I2S3 | + B_TX_AUD_EN_I2S2 | B_TX_AUD_EN_I2S1 | + B_TX_AUD_EN_I2S0); + } + it6161_hdmi_tx_set_bits(it6161, 0x5c, 1 << 6, 0x00); + it6161->bAudioChannelEnable = it6161_hdmi_tx_read(it6161, REG_TX_AUDIO_CTRL0); +} + +static void setHDMITX_DSDAudio(void) +{ + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL1, 0x41); + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_FIFOMAP, 0xE4); + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, M_TX_AUD_24BIT); + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL3, 0); + it6161_hdmi_tx_write(it6161, REG_TX_AUD_SRCVALID_FLAT, 0x00); + it6161_hdmi_tx_write(it6161, REG_TX_AUD_HDAUDIO, B_TX_DSD); + + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, + M_TX_AUD_24BIT | B_TX_AUD_EN_I2S3 | + B_TX_AUD_EN_I2S2 | B_TX_AUD_EN_I2S1 | + B_TX_AUD_EN_I2S0); +} + +static void HDMITX_DisableAudioOutput(struct it6161 *it6161) +{ + it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST, + (B_HDMITX_AUD_RST | B_TX_AREF_RST), + (B_HDMITX_AUD_RST | B_TX_AREF_RST)); + it6161_hdmi_tx_set_bits(it6161, 0x0F, 0x10, 0x10); +} + +static void setHDMITX_NCTS(u8 Fs) +{ + u32 n, LastCTS = 0; + bool HBR_mode; + + if (B_TX_HBR & it6161_hdmi_tx_read(it6161, REG_TX_AUD_HDAUDIO)) + HBR_mode = true; + else + HBR_mode = false; + + switch (Fs) { + case AUDFS_32KHz: + n = 4096; + break; + case AUDFS_44p1KHz: + n = 6272; + break; + case AUDFS_48KHz: + n = 6144; + break; + case AUDFS_88p2KHz: + n = 12544; + break; + case AUDFS_96KHz: + n = 12288; + break; + case AUDFS_176p4KHz: + n = 25088; + break; + case AUDFS_192KHz: + n = 24576; + break; + case AUDFS_768KHz: + n = 24576; + break; + default: + n = 6144; + } + + it6161_hdmi_tx_change_bank(it6161, 1); + it6161_hdmi_tx_write(it6161, REGPktAudN0, (u8) ((n) & 0xFF)); + it6161_hdmi_tx_write(it6161, REGPktAudN1, (u8) ((n >> 8) & 0xFF)); + it6161_hdmi_tx_write(it6161, REGPktAudN2, (u8) ((n >> 16) & 0xF)); + + it6161_hdmi_tx_write(it6161, REGPktAudCTS0, (u8) ((LastCTS) & 0xFF)); + it6161_hdmi_tx_write(it6161, REGPktAudCTS1, (u8) ((LastCTS >> 8) & 0xFF)); + it6161_hdmi_tx_write(it6161, REGPktAudCTS2, (u8) ((LastCTS >> 16) & 0xF)); + it6161_hdmi_tx_change_bank(it6161, 0); + + it6161_hdmi_tx_write(it6161, 0xF8, 0xC3); + it6161_hdmi_tx_write(it6161, 0xF8, 0xA5); + /* D[1] = 0, HW auto count CTS */ + it6161_hdmi_tx_set_bits(it6161, REG_TX_PKT_SINGLE_CTRL, B_TX_SW_CTS, 0x00); + it6161_hdmi_tx_write(it6161, 0xF8, 0xFF); + + if (false == HBR_mode) { + /* LPCM */ + u8 uData; + + it6161_hdmi_tx_change_bank(it6161, 1); + Fs = AUDFS_768KHz; + it6161_hdmi_tx_write(it6161, REG_TX_AUDCHST_CA_FS, 0x00 | Fs); + /* OFS is the one's complement of FS */ + Fs = ~Fs; + uData = (0x0f & it6161_hdmi_tx_read(it6161, REG_TX_AUDCHST_OFS_WL)); + it6161_hdmi_tx_write(it6161, REG_TX_AUDCHST_OFS_WL, (Fs << 4) | uData); + it6161_hdmi_tx_change_bank(it6161, 0); + } +} + +static void HDMITX_EnableAudioOutput(struct it6161 *it6161, u8 AudioType, + u8 bAudInterface, u32 SampleFreq, u8 ChNum, u8 *pIEC60958ChStat) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + static u8 ucIEC60958ChStat[5]; + u8 Fs; + + it6161->bAudioChannelEnable = 0; + it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST, + (B_HDMITX_AUD_RST | B_TX_AREF_RST), + (B_HDMITX_AUD_RST | B_TX_AREF_RST)); + it6161_hdmi_tx_write(it6161, REG_TX_CLK_CTRL0, + B_TX_AUTO_OVER_SAMPLING_CLOCK | B_TX_EXT_256FS | 0x01); + + /* power on the ACLK */ + it6161_hdmi_tx_set_bits(it6161, 0x0F, 0x10, 0x00); + + if (bAudInterface == SPDIF) { + if (AudioType == T_AUDIO_HBR) + it6161_hdmi_tx_write(it6161, REG_TX_CLK_CTRL0, 0x81); + + it6161_hdmi_tx_set_bits(it6161, REG_TX_AUDIO_CTRL0, + B_TX_AUD_SPDIF, B_TX_AUD_SPDIF); + } else + it6161_hdmi_tx_set_bits(it6161, REG_TX_AUDIO_CTRL0, B_TX_AUD_SPDIF, 0x00); + + if (AudioType != T_AUDIO_DSD) { + /* one bit audio have no channel status. */ + switch (SampleFreq) { + case 44100L: + Fs = AUDFS_44p1KHz; + break; + case 88200L: + Fs = AUDFS_88p2KHz; + break; + case 176400L: + Fs = AUDFS_176p4KHz; + break; + case 32000L: + Fs = AUDFS_32KHz; + break; + case 48000L: + Fs = AUDFS_48KHz; + break; + case 96000L: + Fs = AUDFS_96KHz; + break; + case 192000L: + Fs = AUDFS_192KHz; + break; + case 768000L: + Fs = AUDFS_768KHz; + break; + default: + SampleFreq = 48000L; + Fs = AUDFS_48KHz; + break; + } + + setHDMITX_NCTS(Fs); + if (pIEC60958ChStat == NULL) { + ucIEC60958ChStat[0] = 0; + ucIEC60958ChStat[1] = 0; + ucIEC60958ChStat[2] = (ChNum + 1) / 2; + + if (ucIEC60958ChStat[2] < 1) + ucIEC60958ChStat[2] = 1; + else if (ucIEC60958ChStat[2] > 4) + ucIEC60958ChStat[2] = 4; + + ucIEC60958ChStat[3] = Fs; + ucIEC60958ChStat[4] = (((~Fs) << 4) & 0xF0) | CHTSTS_SWCODE; + pIEC60958ChStat = ucIEC60958ChStat; + } + } + it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST, + (B_HDMITX_AUD_RST | B_TX_AREF_RST), B_TX_AREF_RST); + + switch (AudioType) { + case T_AUDIO_HBR: + DRM_DEV_DEBUG_DRIVER(dev, "T_AUDIO_HBR\n"); + pIEC60958ChStat[0] |= 1 << 1; + pIEC60958ChStat[2] = 0; + pIEC60958ChStat[3] &= 0xF0; + pIEC60958ChStat[3] |= AUDFS_768KHz; + pIEC60958ChStat[4] |= (((~AUDFS_768KHz) << 4) & 0xF0) | 0xB; + setHDMITX_ChStat(it6161, pIEC60958ChStat); + setHDMITX_HBRAudio(bAudInterface); + break; + case T_AUDIO_DSD: + DRM_DEV_DEBUG_DRIVER(dev, "T_AUDIO_DSD\n"); + setHDMITX_DSDAudio(); + break; + case T_AUDIO_NLPCM: + DRM_DEV_DEBUG_DRIVER(dev, "T_AUDIO_NLPCM\n"); + pIEC60958ChStat[0] |= 1 << 1; + setHDMITX_ChStat(it6161, pIEC60958ChStat); + setHDMITX_NLPCMAudio(bAudInterface); + break; + case T_AUDIO_LPCM: + DRM_DEV_DEBUG_DRIVER(dev, "T_AUDIO_LPCM\n"); + pIEC60958ChStat[0] &= ~(1 << 1); + + setHDMITX_ChStat(it6161, pIEC60958ChStat); + setHDMITX_LPCMAudio((ChNum + 1) / 2, SUPPORT_AUDI_AudSWL, bAudInterface); + break; + } + it6161_hdmi_tx_set_bits(it6161, REG_TX_INT_MASK1, B_TX_AUDIO_OVFLW_MASK, 0x00); + it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, it6161->bAudioChannelEnable); + + it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST, (B_HDMITX_AUD_RST | B_TX_AREF_RST), 0); +} + +static void hdmi_audio_info_frame_set(struct it6161 *it6161, u8 channels) +{ + struct hdmi_audio_infoframe frame; + u8 buf[16]; + int ret; + + hdmi_audio_infoframe_init(&frame); + + frame.channels = channels; + frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; + + ret = hdmi_audio_infoframe_pack(&frame, buf, sizeof(buf)); + if (ret < 0) { + DRM_ERROR("failed to pack audio infoframe: %d\n", ret); + return; + } + + /* set audio Data byte */ + it6161_hdmi_tx_change_bank(it6161, 1); + it6161_hdmi_tx_write(it6161, REG_TX_PKT_AUDINFO_SUM, buf[3]); + it6161_hdmi_tx_write(it6161, REG_TX_PKT_AUDINFO_CC, buf[4]); + it6161_hdmi_tx_write(it6161, REG_TX_PKT_AUDINFO_SF, buf[5]); + it6161_hdmi_tx_write(it6161, REG_TX_PKT_AUDINFO_CA, buf[7]); + it6161_hdmi_tx_write(it6161, REG_TX_PKT_AUDINFO_DM_LSV, buf[8]); + + /* Enable Audio info frame */ + it6161_hdmi_tx_change_bank(it6161, 0); + it6161_hdmi_tx_write(it6161, REG_TX_AUD_INFOFRM_CTRL, B_TX_ENABLE_PKT | B_TX_REPEAT_PKT); +} + +static void hdmi_tx_audio_process(struct it6161 *it6161) +{ + if (it6161->support_audio) { + hdmi_audio_info_frame_set(it6161, (u8) OUTPUT_CHANNEL); + + HDMITX_EnableAudioOutput(it6161, + CNOFIG_INPUT_AUDIO_TYPE, + CONFIG_INPUT_AUDIO_INTERFACE, + INPUT_SAMPLE_FREQ, + OUTPUT_CHANNEL, + NULL); + } +} + +static inline void hdmi_tx_disable_avi_infoframe(struct it6161 *it6161) +{ + it6161_hdmi_tx_change_bank(it6161, 0); + it6161_hdmi_tx_write(it6161, REG_TX_AVI_INFOFRM_CTRL, 0x00); +} + +static inline void hdmi_tx_enable_avi_infoframe(struct it6161 *it6161) +{ + it6161_hdmi_tx_change_bank(it6161, 0); + it6161_hdmi_tx_write(it6161, REG_TX_AVI_INFOFRM_CTRL, + B_TX_ENABLE_PKT | B_TX_REPEAT_PKT); +} + +static int hdmi_tx_avi_infoframe_set(struct it6161 *it6161) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + struct hdmi_avi_infoframe *frame = &it6161->source_avi_infoframe; + struct drm_display_mode *display_mode = &it6161->display_mode; + u8 buf[32], i, *ptr; + int ret; + + DRM_DEV_DEBUG_DRIVER(dev, "avinfo set\n"); + hdmi_tx_disable_avi_infoframe(it6161); + + ret = drm_hdmi_avi_infoframe_from_display_mode( + frame, &it6161->connector, display_mode); + if (ret) { + DRM_DEV_ERROR(dev, "Failed to setup AVI infoframe: %d", ret); + return ret; + } + + if ((it6161->hdmi_tx_output_color_space & F_MODE_CLRMOD_MASK) == F_MODE_RGB444) + frame->colorspace = HDMI_COLORSPACE_RGB; + + if ((it6161->hdmi_tx_output_color_space & F_MODE_CLRMOD_MASK) == F_MODE_YUV444) + frame->colorspace = HDMI_COLORSPACE_YUV444; + + if ((it6161->hdmi_tx_output_color_space & F_MODE_CLRMOD_MASK) == F_MODE_YUV422) + frame->colorspace = HDMI_COLORSPACE_YUV422; + + ret = hdmi_avi_infoframe_pack(&it6161->source_avi_infoframe, buf, sizeof(buf)); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to pack AVI infoframe: %d", ret); + return ret; + } + + /* fill PB */ + it6161_hdmi_tx_change_bank(it6161, 1); + ptr = buf + HDMI_INFOFRAME_HEADER_SIZE; + for (i = 0; i < it6161->source_avi_infoframe.length; i++) + it6161_hdmi_tx_write(it6161, REG_TX_AVIINFO_DB1 + i, ptr[i]); + + it6161_hdmi_tx_write(it6161, REG_TX_AVIINFO_SUM, buf[3]); + + /* Enable */ + hdmi_tx_enable_avi_infoframe(it6161); + return 0; +} + +static void hdmi_tx_set_output_process(struct it6161 *it6161) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + u8 level; + u32 TMDSClock; + + DRM_DEV_DEBUG_DRIVER(dev, "hdmi tx set\n"); + + TMDSClock = it6161->hdmi_tx_pclk * 1000 * + (it6161->source_avi_infoframe.pixel_repeat + 1); + + HDMITX_DisableAudioOutput(it6161); + hdmi_tx_disable_avi_infoframe(it6161); + + if (TMDSClock > 80000000L) + level = PCLK_HIGH; + else if (TMDSClock > 20000000L) + level = PCLK_MEDIUM; + else + level = PCLK_LOW; + + hdmi_tx_enable_video_output(it6161, level); + + if (it6161->hdmi_mode) { + hdmi_tx_avi_infoframe_set(it6161); + hdmi_tx_audio_process(it6161); + } + + it6161_hdmi_tx_set_av_mute(it6161, false); +} + +static void mipi_rx_calc_rclk(struct it6161 *it6161) +{ + struct device *dev = &it6161->i2c_mipi_rx->dev; + u32 sum = 0, i, retry = 5; + int t10usint; + + for (i = 0; i < retry; i++) { + /* Enable RCLK 100ms count */ + it6161_mipi_rx_set_bits(it6161, 0x94, 0x80, 0x80); + msleep(100); + /* Disable RCLK 100ms count */ + it6161_mipi_rx_set_bits(it6161, 0x94, 0x80, 0x00); + + it6161->mipi_rx_rclk = it6161_mipi_rx_read(it6161, 0x97); + it6161->mipi_rx_rclk <<= 8; + it6161->mipi_rx_rclk += it6161_mipi_rx_read(it6161, 0x96); + it6161->mipi_rx_rclk <<= 8; + it6161->mipi_rx_rclk += it6161_mipi_rx_read(it6161, 0x95); + sum += it6161->mipi_rx_rclk; + } + + sum /= retry; + it6161->mipi_rx_rclk = sum / 108; + t10usint = it6161->mipi_rx_rclk; + DRM_DEV_DEBUG_DRIVER(dev, "mipi_rx_rclk = %d,%03d,%03d\n", + (sum * 10) / 1000000, ((sum * 10) % 1000000) / 1000, ((sum * 10) % 100)); + it6161_mipi_rx_write(it6161, 0x91, t10usint & 0xFF); +} + +static void mipi_rx_reset_p_domain(struct it6161 *it6161) +{ + /* Video Clock Domain Reset */ + it6161_mipi_rx_set_bits(it6161, 0x05, 0x04, 0x04); + /* Release Video Clock Domain Reset */ + it6161_mipi_rx_set_bits(it6161, 0x05, 0x04, 0x00); +} + +static void it6161_mipi_rx_interrupt_clear(struct it6161 *it6161, u8 reg06, u8 reg07, u8 reg08) +{ + it6161_mipi_rx_write(it6161, 0x06, reg06); + it6161_mipi_rx_write(it6161, 0x07, reg07); + it6161_mipi_rx_write(it6161, 0x08, reg08); +} + +static void it6161_mipi_rx_interrupt_reg06_process(struct it6161 *it6161, u8 reg06) +{ + struct device *dev = &it6161->i2c_mipi_rx->dev; + bool m_video_stable, p_video_stable; + u8 data_id; + + if (reg06 & 0x01) { + m_video_stable = mipi_rx_get_m_video_stable(it6161); + DRM_DEV_DEBUG_DRIVER(dev, "PPS M video stable Change Interrupt, %sstable", + m_video_stable ? "" : "un"); + + if (m_video_stable) { + data_id = it6161_mipi_rx_read(it6161, 0x28); + DRM_DEV_DEBUG_DRIVER(dev, "mipi receive video format: 0x%02x", data_id); + mipi_rx_calc_rclk(it6161); + mipi_rx_afe_configuration(it6161, data_id); + mipi_rx_reset_p_domain(it6161); + } + } else if (reg06 & 0x10) { + + p_video_stable = mipi_rx_get_p_video_stable(it6161); + DRM_DEV_DEBUG_DRIVER(dev, "PPS P video stable Change Interrupt, %sstable", p_video_stable ? "" : "un"); + if (p_video_stable) { + DRM_DEV_DEBUG_DRIVER(dev, "PVidStb Change to HIGH"); + mipi_rx_calc_rclk(it6161); + + it6161_mipi_rx_write(it6161, 0xC0, (EnTxCRC << 7) + TxCRCnum); + /* setup 1 sec timer interrupt */ + it6161_mipi_rx_set_bits(it6161, 0x0b, 0x40, 0x40); + + switch (it6161->hdmi_tx_mode) { + case HDMI_TX_BY_PASS: + it6161_hdmi_tx_set_bits(it6161, 0xA9, 0x80, 0x80); + break; + + case HDMI_TX_ENABLE_DE_ONLY: + hdmi_tx_generate_blank_timing(it6161); + break; + + default: + DRM_DEV_ERROR(dev, "use hdmi tx normal mode"); + break; + } + + hdmi_tx_video_reset(it6161); + } + } else + DRM_DEV_DEBUG_DRIVER(dev, "MIPI Rx int reg06=0x%x\n", reg06); +} + +static void it6161_mipi_rx_interrupt_reg07_process(struct it6161 *it6161, u8 reg07) +{ + struct device *dev = &it6161->i2c_mipi_rx->dev; + + if (reg07 & 0x40) { + DRM_DEV_DEBUG_DRIVER(dev, "PPS FIFO over read Interrupt !!! tx video statle:%d", + hdmi_tx_get_video_state(it6161)); + it6161_mipi_rx_set_bits(it6161, 0x07, 0x40, 0x40); + } else if (reg07 & 0x80) { + DRM_DEV_DEBUG_DRIVER(dev, "PPS FIFO over write Interrupt !!!\n"); + it6161_mipi_rx_set_bits(it6161, 0x07, 0x80, 0x80); + } else + DRM_DEV_DEBUG_DRIVER(dev, "MIPI Rx int reg07=0x%x\n", reg07); +} + +static void it6161_mipi_rx_interrupt_reg08_process(struct it6161 *it6161, u8 reg08) +{ + struct device *dev = &it6161->i2c_mipi_rx->dev; + int crc; + + if (reg08 & 0x40) { + it6161_mipi_rx_set_bits(it6161, 0x0b, 0x40, 0x00); + + if ((it6161_mipi_rx_read(it6161, 0xC1) & 0x03) == 0x03) + DRM_DEV_DEBUG_DRIVER(dev, "CRC Fail !!!\n"); + + if ((it6161_mipi_rx_read(it6161, 0xC1) & 0x05) == 0x05) { + DRM_DEV_DEBUG_DRIVER(dev, "CRC Pass !!!\n"); + crc = it6161_mipi_rx_read(it6161, 0xC2) + + (it6161_mipi_rx_read(it6161, 0xC3) << 8); + DRM_DEV_DEBUG_DRIVER(dev, "CRCR = 0x%x !!!\n", crc); + crc = it6161_mipi_rx_read(it6161, 0xC4) + + (it6161_mipi_rx_read(it6161, 0xC5) << 8); + DRM_DEV_DEBUG_DRIVER(dev, "CRCG = 0x%x !!!\n", crc); + crc = it6161_mipi_rx_read(it6161, 0xC6) + + (it6161_mipi_rx_read(it6161, 0xC7) << 8); + DRM_DEV_DEBUG_DRIVER(dev, "CRCB = 0x%x !!!\n", crc); + } + } else + DRM_DEV_DEBUG_DRIVER(dev, "MIPI Rx int reg08=0x%x\n", reg08); +} + +static void it6161_hdmi_tx_interrupt_clear(struct it6161 *it6161, u8 reg06, u8 reg08) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + u8 int_clear; + + if (reg06 & B_TX_INT_AUD_OVERFLOW) { + DRM_DEV_ERROR(dev, "B_TX_INT_AUD_OVERFLOW"); + it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST, + (B_HDMITX_AUD_RST | B_TX_AREF_RST), + (B_HDMITX_AUD_RST | B_TX_AREF_RST)); + it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST, B_HDMITX_AUD_RST | B_TX_AREF_RST, 0x00); + } else if (reg06 & B_TX_INT_DDCFIFO_ERR) { + DRM_DEV_ERROR(dev, "DDC FIFO Error"); + it6161_hdmi_tx_clear_ddc_fifo(it6161); + } else if (reg06 & B_TX_INT_DDC_BUS_HANG) { + DRM_DEV_ERROR(dev, "DDC BUS HANG"); + it6161_hdmi_tx_abort_ddc(it6161); + } + + /* clear interrupt */ + it6161_hdmi_tx_write(it6161, REG_TX_INT_CLR0, 0xFF); + it6161_hdmi_tx_write(it6161, REG_TX_INT_CLR1, 0xFF); + /* write B_TX_INTACTDONE '1' to trigger clear interrupt */ + int_clear = (it6161_hdmi_tx_read(it6161, REG_TX_SYS_STATUS)) | B_TX_CLR_AUD_CTS | B_TX_INTACTDONE; + it6161_hdmi_tx_write(it6161, REG_TX_SYS_STATUS, int_clear); +} + +static void it6161_hdmi_tx_interrupt_reg06_process(struct it6161 *it6161, u8 reg06) +{ + struct device *dev = &it6161->i2c_hdmi_tx->dev; + + if (reg06 & B_TX_INT_HPD_PLUG) { + /* + * sometimes the interrupt is triggered before init bridge.dev. + * So avoid null pointer + */ + if (it6161->bridge.dev) + drm_helper_hpd_irq_event(it6161->bridge.dev); + if (hdmi_tx_get_sink_hpd(it6161)) { + DRM_DEV_INFO(dev, "HDMI Cable Plug In\n"); + hdmi_tx_video_reset(it6161); + } else { + DRM_DEV_INFO(dev, "HDMI Cable Plug Out"); + hdmi_tx_disable_video_output(it6161); + } + } +} + +static void it6161_hdmi_tx_interrupt_reg08_process(struct it6161 *it6161, u8 reg08) +{ + if (reg08 & B_TX_INT_VIDSTABLE) { + it6161_hdmi_tx_write(it6161, REG_TX_INT_STAT3, reg08); + if (hdmi_tx_get_video_state(it6161)) { + hdmi_tx_set_output_process(it6161); + it6161_hdmi_tx_set_av_mute(it6161, FALSE); + } + } +} + +static irqreturn_t it6161_intp_threaded_handler(int unused, void *data) +{ + struct it6161 *it6161 = data; + struct device *dev = &it6161->i2c_hdmi_tx->dev; + u8 mipi_rx_reg06, mipi_rx_reg07, mipi_rx_reg08, mipi_rx_reg0d; + u8 hdmi_tx_reg06, hdmi_tx_reg08; + + mipi_rx_reg06 = it6161_mipi_rx_read(it6161, 0x06); + mipi_rx_reg07 = it6161_mipi_rx_read(it6161, 0x07); + mipi_rx_reg08 = it6161_mipi_rx_read(it6161, 0x08); + mipi_rx_reg0d = it6161_mipi_rx_read(it6161, 0x0D); + + hdmi_tx_reg06 = it6161_hdmi_tx_read(it6161, 0x06); + hdmi_tx_reg08 = it6161_hdmi_tx_read(it6161, 0x08); + + if ((mipi_rx_reg06 != 0) || (mipi_rx_reg07 != 0) || (mipi_rx_reg08 != 0)) { + DRM_DEV_DEBUG_DRIVER(dev, "[MIPI rx ++] reg06: 0x%02x reg07: 0x%02x reg08: 0x%02x reg0d: 0x%02x", + mipi_rx_reg06, mipi_rx_reg07, mipi_rx_reg08, mipi_rx_reg0d); + it6161_mipi_rx_interrupt_clear(it6161, mipi_rx_reg06, mipi_rx_reg07, mipi_rx_reg08); + } + + if ((hdmi_tx_reg06 != 0) || (hdmi_tx_reg08 != 0)) { + DRM_DEV_DEBUG_DRIVER(dev, "[HDMI tx ++] reg06: 0x%02x reg08: 0x%02x", + hdmi_tx_reg06, hdmi_tx_reg08); + it6161_hdmi_tx_interrupt_clear(it6161, hdmi_tx_reg06, hdmi_tx_reg08); + } + + it6161_mipi_rx_interrupt_reg08_process(it6161, mipi_rx_reg08); + it6161_mipi_rx_interrupt_reg06_process(it6161, mipi_rx_reg06); + it6161_mipi_rx_interrupt_reg07_process(it6161, mipi_rx_reg07); + it6161_hdmi_tx_interrupt_reg06_process(it6161, hdmi_tx_reg06); + it6161_hdmi_tx_interrupt_reg08_process(it6161, hdmi_tx_reg08); + + return IRQ_HANDLED; +} + +static ssize_t hdmi_output_color_space_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct it6161 *it6161 = dev_get_drvdata(dev); + + DRM_DEV_DEBUG_DRIVER(dev, "config color space: %s", buf); + it6161->hdmi_tx_output_color_space &= ~F_MODE_CLRMOD_MASK; + + if (strncmp(buf, "ycbcr444", strlen(buf) - 1) == 0 + || strncmp(buf, "yuv444", strlen(buf) - 1) == 0) { + it6161->hdmi_tx_output_color_space |= F_MODE_YUV444; + goto end; + } + + if (strncmp(buf, "ycbcr422", strlen(buf) - 1) == 0 + || strncmp(buf, "yuv422", strlen(buf) - 1) == 0) { + it6161->hdmi_tx_output_color_space |= F_MODE_YUV422; + goto end; + } + + if (strncmp(buf, "rgb444", strlen(buf) - 1) == 0) { + it6161->hdmi_tx_output_color_space |= F_MODE_RGB444; + goto end; + } + + DRM_DEV_DEBUG_DRIVER(dev, + "not support this color space, only support ycbcr444/yuv444, ycbcr422/yuv422, rgb444"); + return count; + +end: + DRM_DEV_INFO(dev, "nconfig color space: %s value:0x%02x", buf, + it6161->hdmi_tx_output_color_space); + return count; +} + +static ssize_t hdmi_output_color_space_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct it6161 *it6161 = dev_get_drvdata(dev); + char *str = buf, *end = buf + PAGE_SIZE; + + str += scnprintf(str, end - str, + "it6161->hdmi_tx_output_color_space:%d\n", it6161->hdmi_tx_output_color_space); + + return str - buf; +} + +static DEVICE_ATTR_RW(hdmi_output_color_space); + +static const struct attribute *it6161_attrs[] = { + &dev_attr_hdmi_output_color_space.attr, + NULL, +}; + +static int it6161_parse_dt(struct it6161 *it6161, struct device_node *np) +{ + struct device *dev = &it6161->i2c_mipi_rx->dev; + + it6161->host_node = of_graph_get_remote_node(np, 0, 0); + if (!it6161->host_node) { + DRM_DEV_ERROR(dev, "no host node"); + return -ENODEV; + } + of_node_put(it6161->host_node); + + return 0; +} + +static int it6161_i2c_probe(struct i2c_client *i2c_mipi_rx, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c_mipi_rx->dev; + int err, intp_irq; + + it6161 = devm_kzalloc(dev, sizeof(*it6161), GFP_KERNEL); + if (!it6161) + return -ENOMEM; + + it6161->i2c_mipi_rx = i2c_mipi_rx; + mutex_init(&it6161->mode_lock); + + it6161->bridge.of_node = i2c_mipi_rx->dev.of_node; + + it6161_parse_dt(it6161, dev->of_node); + it6161->regmap_mipi_rx = devm_regmap_init_i2c(i2c_mipi_rx, &it6161_mipi_rx_bridge_regmap_config); + if (IS_ERR(it6161->regmap_mipi_rx)) { + DRM_DEV_ERROR(dev, "regmap_mipi_rx i2c init failed"); + return PTR_ERR(it6161->regmap_mipi_rx); + } + + if (device_property_read_u32(dev, "it6161-addr-hdmi-tx", &it6161->it6161_addr_hdmi_tx) < 0) + it6161->it6161_addr_hdmi_tx = 0x4C; + it6161->i2c_hdmi_tx = i2c_new_dummy_device(i2c_mipi_rx->adapter, it6161->it6161_addr_hdmi_tx); + it6161->regmap_hdmi_tx = devm_regmap_init_i2c(it6161->i2c_hdmi_tx, &it6161_hdmi_tx_bridge_regmap_config); + if (IS_ERR(it6161->regmap_hdmi_tx)) { + DRM_DEV_ERROR(dev, "regmap_hdmi_tx i2c init failed"); + return PTR_ERR(it6161->regmap_hdmi_tx); + } + + if (!it6161_check_device_ready(it6161)) + return -ENODEV; + + /* The enable GPIO is optional. */ + it6161->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(it6161->enable_gpio)) + DRM_DEV_INFO(dev, "No enable GPIO"); + else + gpiod_set_value_cansleep(it6161->enable_gpio, 1); + + it6161->enable_drv_hold = DEFAULT_DRV_HOLD; + it6161_set_interrupts_active_level(HIGH); + + intp_irq = i2c_mipi_rx->irq; + + if (!intp_irq) { + DRM_DEV_ERROR(dev, "it6112 failed to get INTP IRQ"); + return -ENODEV; + } + + err = devm_request_threaded_irq(&i2c_mipi_rx->dev, intp_irq, NULL, + it6161_intp_threaded_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "it6161-intp", it6161); + if (err) { + DRM_DEV_ERROR(dev, "it6112 failed to request INTP threaded IRQ: %d", err); + return err; + } + + i2c_set_clientdata(i2c_mipi_rx, it6161); + it6161->bridge.funcs = &it6161_bridge_funcs; + it6161->bridge.of_node = i2c_mipi_rx->dev.of_node; + it6161->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD | DRM_BRIDGE_OP_MODES; + it6161->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + + drm_bridge_add(&it6161->bridge); + + err = sysfs_create_files(&i2c_mipi_rx->dev.kobj, it6161_attrs); + if (err) + return err; + + return 0; +} + +static int it6161_remove(struct i2c_client *i2c_mipi_rx) +{ + struct it6161 *it6161 = i2c_get_clientdata(i2c_mipi_rx); + + drm_connector_unregister(&it6161->connector); + drm_connector_cleanup(&it6161->connector); + drm_bridge_remove(&it6161->bridge); + return 0; +} + +static const struct i2c_device_id it6161_id[] = { + {"it6161", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, it6161_id); + +static const struct of_device_id it6161_of_match[] = { + {.compatible = "ite,it6161"}, + {} +}; + +static struct i2c_driver it6161_i2c_driver = { + .driver = { + .name = "it6161_mipirx_hdmitx", + .of_match_table = it6161_of_match, + }, + .id_table = it6161_id, + .probe = it6161_i2c_probe, + .remove = it6161_remove, +}; + +module_i2c_driver(it6161_i2c_driver); + +MODULE_AUTHOR("allen chen <allen.chen@ite.com.tw>"); +MODULE_DESCRIPTION("it6161 HDMI Transmitter driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/it6161.h b/drivers/gpu/drm/bridge/it6161.h new file mode 100644 index 000000000000..93900406bcb3 --- /dev/null +++ b/drivers/gpu/drm/bridge/it6161.h @@ -0,0 +1,322 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * IT6161 MIPI to HDMI Converter driver + * + * Copyright (C) 2021 NXP + */ +#ifndef __IT6161_H__ +#define __IT6161_H__ + +/* Video Configuration */ +#define F_MODE_RGB444 0 +#define F_MODE_YUV422 1 +#define F_MODE_YUV444 2 +#define F_MODE_CLRMOD_MASK 3 + +#define F_VIDMODE_ITU709 (1<<4) +#define F_VIDMODE_ITU601 0 +#define F_VIDMODE_16_235 (1<<5) +#define F_VIDMODE_0_255 0 + +#define F_VIDMODE_EN_UDFILT (1<<6) +#define F_VIDMODE_EN_DITHER (1<<7) + +#define INPUT_COLOR_MODE F_MODE_RGB444 +#define OUTPUT_COLOR_MODE F_MODE_RGB444 + +/* Audio Configuration */ + +/* Audio interface */ +#define I2S 0 +#define SPDIF 1 +#define TDM 2 + +/* Audio sample clock */ +#define AUDFS_44p1KHz 0 +#define AUDFS_88p2KHz 8 +#define AUDFS_176p4KHz 12 +#define AUDFS_32KHz 3 +#define AUDFS_48KHz 2 +#define AUDFS_96KHz 10 +#define AUDFS_192KHz 14 +#define AUDFS_768KHz 9 + +#define F_AUDIO_ON (1<<7) +#define F_AUDIO_HBR (1<<6) +#define F_AUDIO_DSD (1<<5) +#define F_AUDIO_NLPCM (1<<4) +#define F_AUDIO_LAYOUT_1 (1<<3) +#define F_AUDIO_LAYOUT_0 (0<<3) + +#define T_AUDIO_HBR (F_AUDIO_ON | F_AUDIO_HBR) +#define T_AUDIO_DSD (F_AUDIO_ON | F_AUDIO_DSD) +#define T_AUDIO_NLPCM (F_AUDIO_ON | F_AUDIO_NLPCM) +#define T_AUDIO_LPCM (F_AUDIO_ON) + +/* #define SUPPORT_HBR_AUDIO */ +#ifndef SUPPORT_HBR_AUDIO + #define INPUT_SAMPLE_FREQ AUDFS_48KHz + #define INPUT_SAMPLE_FREQ_HZ 48000L + #define OUTPUT_CHANNEL 2 /* 3,4,5,6,7,8 */ + #define CNOFIG_INPUT_AUDIO_TYPE T_AUDIO_LPCM + #define CONFIG_INPUT_AUDIO_INTERFACE SPDIF + #define I2S_FORMAT 0x01 /* 32bit I2S audio */ +#else /* SUPPORT_HBR_AUDIO */ + #define INPUT_SAMPLE_FREQ AUDFS_768KHz + #define INPUT_SAMPLE_FREQ_HZ 768000L + #define OUTPUT_CHANNEL 8 + #define CNOFIG_INPUT_AUDIO_TYPE T_AUDIO_HBR + #define CONFIG_INPUT_AUDIO_INTERFACE SPDIF + #define I2S_FORMAT 0x47 /* 32bit audio */ +#endif + +/* MIPI Rx Configuration */ +#define MIPI_RX_LANE_COUNT 2 /* 1~4 */ + +#define SUPPORT_AUDI_AudSWL 24 + +#if (SUPPORT_AUDI_AudSWL == 16) + #define CHTSTS_SWCODE 0x02 +#elif (SUPPORT_AUDI_AudSWL == 18) + #define CHTSTS_SWCODE 0x04 +#elif (SUPPORT_AUDI_AudSWL == 20) + #define CHTSTS_SWCODE 0x03 +#else + #define CHTSTS_SWCODE 0x0B +#endif + +#define DDC_FIFO_MAXREQ 0x20 + +/* I2C address */ +#define DDC_EDID_ADDRESS 0xA0 +#define CEC_I2C_SLAVE_ADDR 0x9C + +/* HDMI TX Register offset */ +#define REG_TX_SW_RST 0x04 +#define B_TX_AREF_RST (1<<4) +#define B_HDMITX_VID_RST (1<<3) +#define B_HDMITX_AUD_RST (1<<2) +#define B_TX_HDMI_RST (1<<1) +#define B_TX_HDCP_RST_HDMITX (1<<0) + +#define REG_TX_INT_STAT1 0x06 +#define B_TX_INT_AUD_OVERFLOW (1<<7) +#define B_TX_INT_DDCFIFO_ERR (1<<4) +#define B_TX_INT_DDC_BUS_HANG (1<<2) +#define B_TX_INT_HPD_PLUG (1<<0) + +#define REG_TX_INT_STAT3 0x08 +#define B_TX_INT_VIDSTABLE (1<<4) + +#define REG_TX_INT_MASK1 0x09 +#define B_TX_AUDIO_OVFLW_MASK (1<<7) +#define B_TX_DDC_FIFO_ERR_MASK (1<<4) +#define B_TX_DDC_BUS_HANG_MASK (1<<2) +#define B_TX_RXSEN_MASK (1<<1) +#define B_TX_HPD_MASK (1<<0) + +#define REG_TX_INT_MASK3 0x0B +#define B_TX_VIDSTABLE_MASK (1<<3) + +#define REG_TX_INT_CLR0 0x0C +#define REG_TX_INT_CLR1 0x0D +#define REG_TX_SYS_STATUS 0x0E +#define B_TX_HPDETECT (1<<6) +#define B_TX_RXSENDETECT (1<<5) +#define B_TXVIDSTABLE (1<<4) +#define B_TX_CLR_AUD_CTS (1<<1) +#define B_TX_INTACTDONE (1<<0) + +/* DDC */ +#define REG_TX_DDC_MASTER_CTRL 0x10 +#define B_TX_MASTERROM (1<<1) +#define B_TX_MASTERDDC (0<<1) +#define B_TX_MASTERHOST (1<<0) + +#define REG_TX_DDC_HEADER 0x11 +#define REG_TX_DDC_REQOFF 0x12 +#define REG_TX_DDC_REQCOUNT 0x13 +#define REG_TX_DDC_EDIDSEG 0x14 +#define REG_TX_DDC_CMD 0x15 +#define CMD_EDID_READ 3 +#define CMD_FIFO_CLR 9 +#define CMD_GEN_SCLCLK 0xA +#define CMD_DDC_ABORT 0xF + +#define REG_TX_DDC_STATUS 0x16 +#define B_TX_DDC_DONE (1<<7) +#define B_TX_DDC_NOACK (1<<5) +#define B_TX_DDC_WAITBUS (1<<4) +#define B_TX_DDC_ARBILOSE (1<<3) + +#define REG_TX_DDC_READFIFO 0x17 +#define REG_TX_CLK_CTRL0 0x58 +#define B_TX_AUTO_OVER_SAMPLING_CLOCK (1<<4) +#define O_TX_EXT_MCLK_SEL 2 +#define M_TX_EXT_MCLK_SEL (3<<O_TX_EXT_MCLK_SEL) +#define B_TX_EXT_128FS (0<<O_TX_EXT_MCLK_SEL) +#define B_TX_EXT_256FS (1<<O_TX_EXT_MCLK_SEL) +#define B_TX_EXT_512FS (2<<O_TX_EXT_MCLK_SEL) +#define B_TX_EXT_1024FS (3<<O_TX_EXT_MCLK_SEL) + +#define REG_TX_CLK_STATUS2 0x5F +#define B_TX_OSF_LOCK (1<<5) + +#define REG_TX_AFE_DRV_CTRL 0x61 +#define B_TX_AFE_DRV_PWD (1<<5) +#define B_TX_AFE_DRV_RST (1<<4) + +/* Input Data Format Register */ +#define REG_TX_INPUT_MODE 0x70 +#define B_TX_PCLKDIV2 (1<<5) + +#define REG_TX_CSC_CTRL 0x72 +#define B_HDMITX_CSC_BYPASS 0 +#define B_HDMITX_CSC_RGB2YUV 2 +#define B_HDMITX_CSC_YUV2RGB 3 +#define M_TX_CSC_SEL 3 +#define B_TX_EN_DITHER (1<<7) +#define B_TX_EN_UDFILTER (1<<6) +#define B_TX_DNFREE_GO (1<<5) +#define SIZEOF_CSCMTX 21 + +#define REG_TX_CSC_YOFF 0x73 +#define REG_TX_CSC_COFF 0x74 +#define REG_TX_CSC_RGBOFF 0x75 + +/* HDMI General Control Registers */ +#define REG_TX_HDMI_MODE 0xC0 +#define B_TX_HDMI_MODE 1 +#define B_TX_DVI_MODE 0 +#define REG_TX_GCP 0xC1 +#define B_TX_SETAVMUTE (1<<0) + +#define O_TX_COLOR_DEPTH 4 +#define M_TX_COLOR_DEPTH 7 +#define B_TX_COLOR_DEPTH_MASK (M_TX_COLOR_DEPTH << O_TX_COLOR_DEPTH) +#define REG_TX_PKT_GENERAL_CTRL 0xC6 + +/* Audio Channel Control */ +#define REG_TX_AUDIO_CTRL0 0xE0 +#define M_TX_AUD_16BIT (0<<6) +#define M_TX_AUD_18BIT (1<<6) +#define M_TX_AUD_20BIT (2<<6) +#define M_TX_AUD_24BIT (3<<6) +#define B_TX_AUD_SPDIF (1<<4) +#define B_TX_AUD_I2S (0<<4) +#define B_TX_AUD_EN_I2S3 (1<<3) +#define B_TX_AUD_EN_I2S2 (1<<2) +#define B_TX_AUD_EN_I2S1 (1<<1) +#define B_TX_AUD_EN_I2S0 (1<<0) +#define B_TX_AUD_EN_SPDIF 1 + +#define REG_TX_AUDIO_CTRL1 0xE1 +#define REG_TX_AUDIO_FIFOMAP 0xE2 +#define REG_TX_AUDIO_CTRL3 0xE3 +#define B_TX_CHSTSEL (1<<4) + +#define REG_TX_AUD_SRCVALID_FLAT 0xE4 + +#define REG_TX_AUD_HDAUDIO 0xE5 +#define B_TX_HBR (1<<3) +#define B_TX_DSD (1<<1) +#define B_TX_TDM (1<<0) + +/* HDMI TX reg Bank 1 */ +#define REGPktAudCTS0 0x30 /* 7:0 */ +#define REGPktAudCTS1 0x31 /* 15:8 */ +#define REGPktAudCTS2 0x32 /* 19:16 */ +#define REGPktAudN0 0x33 /* 7:0 */ +#define REGPktAudN1 0x34 /* 15:8 */ +#define REGPktAudN2 0x35 /* 19:16 */ +#define REG_TX_AVIINFO_DB1 0x58 +#define REG_TX_AVIINFO_SUM 0x5D +#define REG_TX_PKT_AUDINFO_CC 0x68 /* [2:0] */ +#define REG_TX_PKT_AUDINFO_SF 0x69 /* [4:2] */ +#define REG_TX_PKT_AUDINFO_CA 0x6B /* [7:0] */ +#define REG_TX_PKT_AUDINFO_DM_LSV 0x6C /* [7][6:3] */ +#define REG_TX_PKT_AUDINFO_SUM 0x6D /* [7:0] */ +#define REG_TX_AUDCHST_MODE 0x91 /* 191 REG_TX_AUD_CHSTD[2:0] 6:4 */ +#define REG_TX_AUDCHST_CAT 0x92 /* 192 REG_TX_AUD_CHSTCAT 7:0 */ +#define REG_TX_AUDCHST_SRCNUM 0x93 /* 193 REG_TX_AUD_CHSTSRC 3:0 */ +#define REG_TX_AUD0CHST_CHTNUM 0x94 /* 194 REG_TX_AUD0_CHSTCHR 7:4 */ +#define REG_TX_AUDCHST_CA_FS 0x98 /* 198 REG_TX_AUD_CHSTCA 5:4 */ +#define REG_TX_AUDCHST_OFS_WL 0x99 /* 199 REG_TX_AUD_CHSTOFS 7:4 */ + +#define REG_TX_PKT_SINGLE_CTRL 0xC5 +#define B_TX_SW_CTS (1<<1) + +#define REG_TX_AVI_INFOFRM_CTRL 0xCD +#define REG_TX_AUD_INFOFRM_CTRL 0xCE +#define B_TX_ENABLE_PKT 1 +#define B_TX_REPEAT_PKT (1<<1) + +struct RegSetEntry { + u8 offset; + u8 mask; + u8 value; +}; + +enum { + PCLK_LOW = 0, + PCLK_MEDIUM, + PCLK_HIGH +}; + +u8 bCSCMtx_RGB2YUV_ITU601_16_235[] = { + 0x00, 0x80, 0x00, + 0xB2, 0x04, 0x65, 0x02, 0xE9, 0x00, + 0x93, 0x3C, 0x18, 0x04, 0x55, 0x3F, + 0x49, 0x3D, 0x9F, 0x3E, 0x18, 0x04 +}; + +u8 bCSCMtx_RGB2YUV_ITU601_0_255[] = { + 0x10, 0x80, 0x10, + 0x09, 0x04, 0x0E, 0x02, 0xC9, 0x00, + 0x0F, 0x3D, 0x84, 0x03, 0x6D, 0x3F, + 0xAB, 0x3D, 0xD1, 0x3E, 0x84, 0x03 +}; + +u8 bCSCMtx_RGB2YUV_ITU709_16_235[] = { + 0x00, 0x80, 0x00, + 0xB8, 0x05, 0xB4, 0x01, 0x94, 0x00, + 0x4a, 0x3C, 0x17, 0x04, 0x9F, 0x3F, + 0xD9, 0x3C, 0x10, 0x3F, 0x17, 0x04 +}; + +u8 bCSCMtx_RGB2YUV_ITU709_0_255[] = { + 0x10, 0x80, 0x10, + 0xEa, 0x04, 0x77, 0x01, 0x7F, 0x00, + 0xD0, 0x3C, 0x83, 0x03, 0xAD, 0x3F, + 0x4B, 0x3D, 0x32, 0x3F, 0x83, 0x03 +}; + +u8 bCSCMtx_YUV2RGB_ITU601_16_235[] = { + 0x00, 0x00, 0x00, + 0x00, 0x08, 0x6B, 0x3A, 0x50, 0x3D, + 0x00, 0x08, 0xF5, 0x0A, 0x02, 0x00, + 0x00, 0x08, 0xFD, 0x3F, 0xDA, 0x0D +}; + +u8 bCSCMtx_YUV2RGB_ITU601_0_255[] = { + 0x04, 0x00, 0xA7, + 0x4F, 0x09, 0x81, 0x39, 0xDD, 0x3C, + 0x4F, 0x09, 0xC4, 0x0C, 0x01, 0x00, + 0x4F, 0x09, 0xFD, 0x3F, 0x1F, 0x10 +}; + +u8 bCSCMtx_YUV2RGB_ITU709_16_235[] = { + 0x00, 0x00, 0x00, + 0x00, 0x08, 0x55, 0x3C, 0x88, 0x3E, + 0x00, 0x08, 0x51, 0x0C, 0x00, 0x00, + 0x00, 0x08, 0x00, 0x00, 0x84, 0x0E +}; + +u8 bCSCMtx_YUV2RGB_ITU709_0_255[] = { + 0x04, 0x00, 0xA7, + 0x4F, 0x09, 0xBA, 0x3B, 0x4B, 0x3E, + 0x4F, 0x09, 0x57, 0x0E, 0x02, 0x00, + 0x4F, 0x09, 0xFE, 0x3F, 0xE8, 0x10 +}; + +#endif diff --git a/drivers/gpu/drm/bridge/it6263.c b/drivers/gpu/drm/bridge/it6263.c new file mode 100644 index 000000000000..a687a2754097 --- /dev/null +++ b/drivers/gpu/drm/bridge/it6263.c @@ -0,0 +1,1040 @@ +/* + * Copyright 2017-2019 NXP + * + * 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 <drm/drm_vblank.h> +#include <drm/drm_bridge.h> +#include <drm/drm_drv.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_print.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/regmap.h> + +#define REG_VENDOR_ID(n) (0x00 + (n)) /* n: 0/1 */ +#define REG_DEVICE_ID(n) (0x02 + (n)) /* n: 0/1 */ +#define LVDS_VENDER_ID_LOW 0x15 +#define LVDS_VENDER_ID_HIGH 0xCA +#define LVDS_DEVICE_ID_LOW 0x61 +#define LVDS_DEVICE_ID_HIGH 0x62 +#define HDMI_VENDER_ID_LOW 0x01 +#define HDMI_VENDER_ID_HIGH 0xCA +#define HDMI_DEVICE_ID_LOW 0x13 +#define HDMI_DEVICE_ID_HIGH 0x76 + +/* LVDS registers */ +#define LVDS_REG_SW_RST 0x05 +#define SOFT_REFCLK_DM_RST BIT(0) +#define SOFT_PCLK_DM_RST BIT(1) + +#define LVDS_REG_MODE 0x2C +#define LVDS_COLOR_DEPTH 0x3 +enum { + LVDS_COLOR_DEPTH_18, + LVDS_COLOR_DEPTH_24, + LVDS_COLOR_DEPTH_30, + LVDS_COLOR_DEPTH_36, +}; +#define LVDS_OUT_MAP BIT(4) +#define VESA BIT(4) +#define JEIDA 0 +#define DMODE BIT(7) +#define SPLIT_MODE BIT(7) +#define SINGLE_MODE 0 + +#define LVDS_REG_STABLE 0x30 +#define VIDEO_STABLE BIT(0) +#define PCLK_LOCK BIT(1) + +#define LVDS_REG_39 0x39 + +#define LVDS_REG_PLL 0x3C +#define LVDS_REG_AFE_3E 0x3E +#define LVDS_REG_AFE_3F 0x3F +#define LVDS_REG_AFE_47 0x47 +#define LVDS_REG_AFE_48 0x48 +#define LVDS_REG_AFE_4F 0x4F +#define LVDS_REG_52 0x52 +#define LVDS_REG_PCLK_CNT_HIGH 0x57 +#define LVDS_REG_PCLK_CNT_LOW 0x58 + +/* + * HDMI registers + * + * Registers are separated into three banks: + * 1) common bank: 0x00 ~ 0x2F + * 2) bank0: 0x30 ~ 0xFF + * 3) bank1: 0x130 ~ 0x1FF (HDMI packet registers) + * + * Use register HDMI_REG_BANK_CTRL @ 0x0F[1:0] to select bank0/1: + * 2b'00 - bank0 + * 2b'01 - bank1 + */ + +/******************************/ +/* HDMI register common bank */ +/******************************/ + +/* HDMI genernal registers */ +#define HDMI_REG_SW_RST 0x04 +#define SOFTREF_RST BIT(5) +#define SOFTA_RST BIT(4) +#define SOFTV_RST BIT(3) +#define AUD_RST BIT(2) +#define HDCP_RST BIT(0) +#define HDMI_RST_ALL (SOFTREF_RST | SOFTA_RST | SOFTV_RST | \ + AUD_RST | HDCP_RST) + +#define HDMI_REG_INT_CTRL 0x05 +#define INTPOL_ACTH BIT(7) +#define INTPOL_ACTL 0 +#define INTIOMODE_OPENDRAIN BIT(6) +#define INTIOMODE_PUSHPULL 0 +#define SELXTAL BIT(5) /* REFCLK <= XTALCLK */ +#define SELXTAL_QUARTER 0 /* REFCLK <= OSCCLK/4 */ +#define PDREFCNT(n) (((n) >> 2) << 2) /* REFCLK Div(n) */ +#define PDREFCLK BIT(1) +#define PDTXCLK_GATED BIT(0) +#define PDTXCLK_ACTIVE 0 + +#define HDMI_REG_INT_STAT(n) (0x05 + (n)) /* n: 1/2/3 */ +#define HDMI_REG_INT_MASK(n) (0x08 + (n)) /* n: 1/2/3 */ + +/* INT1 */ +#define INT_AUD_OVERFLOW BIT(7) +#define INT_RDDC_NOACK BIT(5) +#define INT_DDCFIFO_ERR BIT(4) +#define INT_DDC_BUS_HANG BIT(2) +#define INT_RX_SENSE BIT(1) +#define INT_HPD BIT(0) + +/* INT2 */ +#define INT_VID_UNSTABLE BIT(6) +#define INT_PKTACP BIT(5) +#define INT_PKTNULL BIT(4) +#define INT_PKTGEN BIT(3) +#define INT_KSVLIST_CHK BIT(2) +#define INT_AUTH_DONE BIT(1) +#define INT_AUTH_FAIL BIT(0) + +/* INT3 */ +#define INT_AUD_CTS BIT(6) +#define INT_VSYNC BIT(5) +#define INT_VIDSTABLE BIT(4) +#define INT_PKTMPG BIT(3) +#define INT_PKTGBD BIT(2) +#define INT_PKTAUD BIT(1) +#define INT_PKTAVI BIT(0) + +#define INT_MASK_AUD_CTS BIT(5) +#define INT_MASK_VSYNC BIT(4) +#define INT_MASK_VIDSTABLE BIT(3) +#define INT_MASK_PKTMPG BIT(2) +#define INT_MASK_PKTGBD BIT(1) +#define INT_MASK_PKTAUD BIT(0) + +#define HDMI_REG_INT_CLR(n) (0x0C + (n)) /* n: 0/1 */ + +/* CLR0 */ +#define INT_CLR_PKTACP BIT(7) +#define INT_CLR_PKTNULL BIT(6) +#define INT_CLR_PKTGEN BIT(5) +#define INT_CLR_KSVLIST_CHK BIT(4) +#define INT_CLR_AUTH_DONE BIT(3) +#define INT_CLR_AUTH_FAIL BIT(2) +#define INT_CLR_RXSENSE BIT(1) +#define INT_CLR_HPD BIT(0) + +/* CLR1 */ +#define INT_CLR_VSYNC BIT(7) +#define INT_CLR_VIDSTABLE BIT(6) +#define INT_CLR_PKTMPG BIT(5) +#define INT_CLR_PKTGBD BIT(4) +#define INT_CLR_PKTAUD BIT(3) +#define INT_CLR_PKTAVI BIT(2) +#define INT_CLR_VID_UNSTABLE BIT(0) + +#define HDMI_REG_SYS_STATUS 0x0E +#define INT_ACTIVE BIT(7) +#define HPDETECT BIT(6) +#define RXSENDETECT BIT(5) +#define TXVIDSTABLE BIT(4) +#define CTSINTSTEP 0xC +#define CLR_AUD_CTS BIT(1) +#define INTACTDONE BIT(0) + +#define HDMI_REG_BANK_CTRL 0x0F +#define BANK_SEL(n) ((n) ? 1 : 0) + +/* HDMI System DDC control registers */ +#define HDMI_REG_DDC_MASTER_CTRL 0x10 +#define MASTER_SEL_HOST BIT(0) +#define MASTER_SEL_HDCP 0 + +#define HDMI_REG_DDC_HEADER 0x11 +#define DDC_HDCP_ADDRESS 0x74 + +#define HDMI_REG_DDC_REQOFF 0x12 +#define HDMI_REG_DDC_REQCOUNT 0x13 +#define HDMI_REG_DDC_EDIDSEG 0x14 + +#define HDMI_REG_DDC_CMD 0x15 +#define DDC_CMD_SEQ_BURSTREAD 0x0 +#define DDC_CMD_LINK_CHKREAD 0x2 +#define DDC_CMD_EDID_READ 0x3 +#define DDC_CMD_FIFO_CLR 0x9 +#define DDC_CMD_GEN_SCLCLK 0xA +#define DDC_CMD_ABORT 0xF + +#define HDMI_REG_DDC_STATUS 0x16 +#define DDC_DONE BIT(7) +#define DDC_ACT BIT(6) +#define DDC_NOACK BIT(5) +#define DDC_WAITBUS BIT(4) +#define DDC_ARBILOSE BIT(3) +#define DDC_ERROR (DDC_NOACK | DDC_WAITBUS | DDC_ARBILOSE) +#define DDC_FIFOFULL BIT(2) +#define DDC_FIFOEMPTY BIT(1) + +#define HDMI_DDC_FIFO_SIZE 32 /* bytes */ +#define HDMI_REG_DDC_READFIFO 0x17 +#define HDMI_REG_ROM_STAT 0x1C +#define HDMI_REG_LVDS_PORT 0x1D /* LVDS input ctrl i2c addr */ +#define HDMI_REG_LVDS_PORT_EN 0x1E /* and to enable */ +#define LVDS_INPUT_CTRL_I2C_ADDR 0x33 + +/***********************/ +/* HDMI register bank0 */ +/***********************/ + +/* HDMI clock control registers */ +#define HDMI_REG_CLK_CTRL1 0x59 +#define EN_TXCLK_COUNT BIT(5) +#define VDO_LATCH_EDGE BIT(3) + +/* HDMI AFE registers */ +#define HDMI_REG_AFE_DRV_CTRL 0x61 +#define AFE_DRV_PWD BIT(5) +#define AFE_DRV_RST BIT(4) +#define AFE_DRV_PDRXDET BIT(2) +#define AFE_DRV_TERMON BIT(1) +#define AFE_DRV_ENCAL BIT(0) + +#define HDMI_REG_AFE_XP_CTRL 0x62 +#define AFE_XP_GAINBIT BIT(7) +#define AFE_XP_PWDPLL BIT(6) +#define AFE_XP_ENI BIT(5) +#define AFE_XP_ER0 BIT(4) +#define AFE_XP_RESETB BIT(3) +#define AFE_XP_PWDI BIT(2) +#define AFE_XP_DEI BIT(1) +#define AFE_XP_DER BIT(0) + +#define HDMI_REG_AFE_ISW_CTRL 0x63 +#define AFE_RTERM_SEL BIT(7) +#define AFE_IP_BYPASS BIT(6) +#define AFE_DRV_ISW 0x38 +#define AFE_DRV_ISWK 7 + +#define HDMI_REG_AFE_IP_CTRL 0x64 +#define AFE_IP_GAINBIT BIT(7) +#define AFE_IP_PWDPLL BIT(6) +#define AFE_IP_CKSEL 0x30 +#define AFE_IP_ER0 BIT(3) +#define AFE_IP_RESETB BIT(2) +#define AFE_IP_ENC BIT(1) +#define AFE_IP_EC1 BIT(0) + +/* HDMI input data format registers */ +#define HDMI_REG_INPUT_MODE 0x70 +#define IN_RGB 0x00 +#define IN_YUV422 0x40 +#define IN_YUV444 0x80 + +#define HDMI_REG_TXFIFO_RST 0x71 +#define ENAVMUTERST BIT(0) +#define TXFFRST BIT(1) + +/* HDMI pattern generation SYNC/DE registers */ +#define HDMI_REG_9X(n) (0x90 + (n)) /* n: 0x0 ~ 0xF */ +#define HDMI_REG_AX(n) (0xA0 + (n)) /* n: 0x0 ~ 0xF */ +#define HDMI_REG_B0 0xB0 + +/* HDMI general control registers */ +#define HDMI_REG_HDMI_MODE 0xC0 +#define TX_HDMI_MODE 1 +#define TX_DVI_MODE 0 + +#define HDMI_REG_GCP 0xC1 +#define AVMUTE BIT(0) +#define BLUE_SCR_MUTE BIT(1) +#define NODEF_PHASE BIT(2) +#define PHASE_RESYNC BIT(3) +#define HDMI_COLOR_DEPTH 0x70 +enum { + HDMI_COLOR_DEPTH_DEF = 0x0, /* default as 24bit */ + HDMI_COLOR_DEPTH_24 = 0x40, + HDMI_COLOR_DEPTH_30 = 0x50, + HDMI_COLOR_DEPTH_36 = 0x60, + HDMI_COLOR_DEPTH_48 = 0x70, +}; + +#define HDMI_REG_OESS_CYCLE 0xC3 +#define HDMI_REG_ENCRYPTION 0xC4 /* HDCP */ + +#define HDMI_REG_PKT_SINGLE_CTRL 0xC5 +#define SINGLE_PKT BIT(0) +#define BURST_PKT 0 + +#define HDMI_REG_PKT_GENERAL_CTRL 0xC6 +#define HDMI_REG_NULL_CTRL 0xC9 +#define HDMI_REG_ACP_CTRL 0xCA +#define HDMI_REG_ISRC1_CTRL 0xCB +#define HDMI_REG_ISRC2_CTRL 0xCC +#define HDMI_REG_AVI_INFOFRM_CTRL 0xCD +#define HDMI_REG_AUD_INFOFRM_CTRL 0xCE +#define HDMI_REG_SPD_INFOFRM_CTRL 0xCF +#define HDMI_REG_MPG_INFOFRM_CTRL 0xD0 +#define ENABLE_PKT BIT(0) +#define REPEAT_PKT BIT(1) + +/***********************/ +/* HDMI register bank1 */ +/***********************/ + +/* AVI packet registers */ +#define HDMI_REG_AVI_DB1 0x58 +#define AVI_DB1_COLOR_SPACE 0x60 +enum { + AVI_COLOR_SPACE_RGB = 0x00, + AVI_COLOR_SPACE_YUV422 = 0x20, + AVI_COLOR_SPACE_YUV444 = 0x40, +}; + +struct it6263 { + struct i2c_client *hdmi_i2c; + struct i2c_client *lvds_i2c; + struct regmap *hdmi_regmap; + struct regmap *lvds_regmap; + struct drm_bridge bridge; + struct drm_connector connector; + struct gpio_desc *reset_gpio; + bool is_hdmi; + bool split_mode; +}; + +struct it6263_minimode { + int hdisplay; + int vdisplay; + int vrefresh; +}; + +static const struct it6263_minimode it6263_bad_mode_db[] = { + {1600, 900, 60}, + {1280, 1024, 60}, + {1280, 720, 30}, + {1280, 720, 25}, + {1280, 720, 24}, + {1152, 864, 75}, +}; + +static inline struct it6263 *bridge_to_it6263(struct drm_bridge *bridge) +{ + return container_of(bridge, struct it6263, bridge); +} + +static inline struct it6263 *connector_to_it6263(struct drm_connector *con) +{ + return container_of(con, struct it6263, connector); +} + +static inline void lvds_update_bits(struct it6263 *it6263, unsigned int reg, + unsigned int mask, unsigned int val) +{ + regmap_update_bits(it6263->lvds_regmap, reg, mask, val); +} + +static inline void hdmi_update_bits(struct it6263 *it6263, unsigned int reg, + unsigned int mask, unsigned int val) +{ + regmap_update_bits(it6263->hdmi_regmap, reg, mask, val); +} + +static void it6263_reset(struct it6263 *it6263) +{ + if (!it6263->reset_gpio) + return; + + gpiod_set_value_cansleep(it6263->reset_gpio, 0); + + usleep_range(1000, 2000); + + gpiod_set_value_cansleep(it6263->reset_gpio, 1); + + /* + * The chip maker says the low pulse should be at least 40ms, + * so 41ms is sure to be enough. + */ + usleep_range(41000, 45000); + + gpiod_set_value_cansleep(it6263->reset_gpio, 0); + + /* somehow, addtional time to wait the high voltage to be stable */ + usleep_range(5000, 6000); +} + +static void it6263_lvds_reset(struct it6263 *it6263) +{ + /* AFE PLL reset */ + lvds_update_bits(it6263, LVDS_REG_PLL, 0x1, 0x0); + usleep_range(1000, 2000); + lvds_update_bits(it6263, LVDS_REG_PLL, 0x1, 0x1); + + /* pclk reset */ + lvds_update_bits(it6263, LVDS_REG_SW_RST, + SOFT_PCLK_DM_RST, SOFT_PCLK_DM_RST); + usleep_range(1000, 2000); + lvds_update_bits(it6263, LVDS_REG_SW_RST, SOFT_PCLK_DM_RST, 0x0); + + usleep_range(1000, 2000); +} + +static void it6263_lvds_set_interface(struct it6263 *it6263) +{ + /* color depth */ + lvds_update_bits(it6263, LVDS_REG_MODE, LVDS_COLOR_DEPTH, + LVDS_COLOR_DEPTH_24); + + /* jeida mapping */ + lvds_update_bits(it6263, LVDS_REG_MODE, LVDS_OUT_MAP, JEIDA); + + if (it6263->split_mode) { + lvds_update_bits(it6263, LVDS_REG_MODE, DMODE, SPLIT_MODE); + lvds_update_bits(it6263, LVDS_REG_52, BIT(1), BIT(1)); + } else { + lvds_update_bits(it6263, LVDS_REG_MODE, DMODE, SINGLE_MODE); + lvds_update_bits(it6263, LVDS_REG_52, BIT(1), 0); + } +} + +static void it6263_lvds_set_afe(struct it6263 *it6263) +{ + struct regmap *regmap = it6263->lvds_regmap; + + regmap_write(regmap, LVDS_REG_AFE_3E, 0xaa); + regmap_write(regmap, LVDS_REG_AFE_3F, 0x02); + regmap_write(regmap, LVDS_REG_AFE_47, 0xaa); + regmap_write(regmap, LVDS_REG_AFE_48, 0x02); + regmap_write(regmap, LVDS_REG_AFE_4F, 0x11); + + lvds_update_bits(it6263, LVDS_REG_PLL, 0x07, 0); +} + +static void it6263_lvds_config(struct it6263 *it6263) +{ + it6263_lvds_reset(it6263); + it6263_lvds_set_interface(it6263); + it6263_lvds_set_afe(it6263); +} + +static void it6263_hdmi_config(struct it6263 *it6263) +{ + regmap_write(it6263->hdmi_regmap, HDMI_REG_INPUT_MODE, IN_RGB); + + hdmi_update_bits(it6263, HDMI_REG_GCP, HDMI_COLOR_DEPTH, + HDMI_COLOR_DEPTH_24); +} + +static bool it6263_hpd_is_connected(struct it6263 *it6263) +{ + unsigned int status; + + regmap_read(it6263->hdmi_regmap, HDMI_REG_SYS_STATUS, &status); + + return !!(status & HPDETECT); +} + +static enum drm_connector_status +it6263_connector_detect(struct drm_connector *connector, bool force) +{ + struct it6263 *it6263 = connector_to_it6263(connector); + int i; + + if (force) { + /* + * FIXME: We read status tens of times to workaround + * cable detection failure issue at boot time on some + * platforms. + * Spin on this for up to one second. + */ + for (i = 0; i < 100; i++) { + if (it6263_hpd_is_connected(it6263)) + return connector_status_connected; + usleep_range(5000, 10000); + } + } else { + if (it6263_hpd_is_connected(it6263)) + return connector_status_connected; + } + + return connector_status_disconnected; +} + +static const struct drm_connector_funcs it6263_connector_funcs = { + .detect = it6263_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int +it6263_read_edid(void *data, u8 *buf, unsigned int block, size_t len) +{ + struct it6263 *it6263 = data; + struct regmap *regmap = it6263->hdmi_regmap; + unsigned long timeout; + unsigned int status, count, val; + unsigned int segment = block >> 1; + unsigned int start = (block % 2) * EDID_LENGTH; + + regmap_write(regmap, HDMI_REG_DDC_MASTER_CTRL, MASTER_SEL_HOST); + regmap_write(regmap, HDMI_REG_DDC_HEADER, DDC_ADDR << 1); + regmap_write(regmap, HDMI_REG_DDC_EDIDSEG, segment); + + while (len) { + /* clear DDC FIFO */ + regmap_write(regmap, HDMI_REG_DDC_CMD, DDC_CMD_FIFO_CLR); + + timeout = jiffies + msecs_to_jiffies(10); + do { + regmap_read(regmap, HDMI_REG_DDC_STATUS, &status); + } while (!(status & DDC_DONE) && time_before(jiffies, timeout)); + + if (!(status & DDC_DONE)) { + dev_err(&it6263->hdmi_i2c->dev, + "failed to clear DDC FIFO\n"); + return -ETIMEDOUT; + } + + count = len > HDMI_DDC_FIFO_SIZE ? HDMI_DDC_FIFO_SIZE : len; + + /* fire the read command */ + regmap_write(regmap, HDMI_REG_DDC_REQOFF, start); + regmap_write(regmap, HDMI_REG_DDC_REQCOUNT, count); + regmap_write(regmap, HDMI_REG_DDC_CMD, DDC_CMD_EDID_READ); + + start += count; + len -= count; + + /* wait for reading done */ + timeout = jiffies + msecs_to_jiffies(250); + do { + regmap_read(regmap, HDMI_REG_DDC_STATUS, &status); + if (status & DDC_ERROR) { + dev_err(&it6263->hdmi_i2c->dev, "DDC error\n"); + return -EIO; + } + } while (!(status & DDC_DONE) && time_before(jiffies, timeout)); + + if (!(status & DDC_DONE)) { + dev_err(&it6263->hdmi_i2c->dev, + "failed to read EDID\n"); + return -ETIMEDOUT; + } + + /* cache to buffer */ + for (; count > 0; count--) { + regmap_read(regmap, HDMI_REG_DDC_READFIFO, &val); + *(buf++) = val; + } + } + + return 0; +} + +static int it6263_get_modes(struct drm_connector *connector) +{ + struct it6263 *it6263 = connector_to_it6263(connector); + u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + struct edid *edid; + int num = 0; + int ret; + + edid = drm_do_get_edid(connector, it6263_read_edid, it6263); + drm_connector_update_edid_property(connector, edid); + if (edid) { + num = drm_add_edid_modes(connector, edid); + it6263->is_hdmi = drm_detect_hdmi_monitor(edid); + kfree(edid); + } + + ret = drm_display_info_set_bus_formats(&connector->display_info, + &bus_format, 1); + if (ret) + dev_dbg(&it6263->hdmi_i2c->dev, + "failed to set the supported bus format %d\n", ret); + + return num; +} + +static enum drm_mode_status it6263_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + const struct it6263_minimode *m; + int i, vrefresh = drm_mode_vrefresh(mode); + + if (mode->clock > 150000) + return MODE_CLOCK_HIGH; + + for (i = 0; i < ARRAY_SIZE(it6263_bad_mode_db); i++) { + m = &it6263_bad_mode_db[i]; + if ((mode->hdisplay == m->hdisplay) && + (mode->vdisplay == m->vdisplay) && + (vrefresh == m->vrefresh)) + return MODE_BAD; + } + + return MODE_OK; +} + +static const struct drm_connector_helper_funcs it6263_connector_helper_funcs = { + .get_modes = it6263_get_modes, + .mode_valid = it6263_mode_valid, +}; + +static void it6263_bridge_disable(struct drm_bridge *bridge) +{ + struct it6263 *it6263 = bridge_to_it6263(bridge); + struct regmap *regmap = it6263->hdmi_regmap; + + /* AV mute */ + hdmi_update_bits(it6263, HDMI_REG_GCP, AVMUTE, AVMUTE); + + if (it6263->is_hdmi) + regmap_write(regmap, HDMI_REG_PKT_GENERAL_CTRL, 0); + + hdmi_update_bits(it6263, HDMI_REG_SW_RST, SOFTV_RST, SOFTV_RST); + regmap_write(regmap, HDMI_REG_AFE_DRV_CTRL, AFE_DRV_RST | AFE_DRV_PWD); +} + +static void it6263_bridge_enable(struct drm_bridge *bridge) +{ + struct it6263 *it6263 = bridge_to_it6263(bridge); + struct regmap *regmap = it6263->hdmi_regmap; + unsigned long timeout; + unsigned int status; + bool is_stable = false; + int i; + + regmap_write(it6263->hdmi_regmap, HDMI_REG_BANK_CTRL, BANK_SEL(1)); + /* set the color space to RGB in the AVI packet */ + hdmi_update_bits(it6263, HDMI_REG_AVI_DB1, AVI_DB1_COLOR_SPACE, + AVI_COLOR_SPACE_RGB); + regmap_write(it6263->hdmi_regmap, HDMI_REG_BANK_CTRL, BANK_SEL(0)); + + /* software video reset */ + hdmi_update_bits(it6263, HDMI_REG_SW_RST, SOFTV_RST, SOFTV_RST); + usleep_range(1000, 2000); + hdmi_update_bits(it6263, HDMI_REG_SW_RST, SOFTV_RST, 0); + + /* reconfigure LVDS and retry several times in case video is instable */ + for (i = 0; i < 3; i++) { + timeout = jiffies + msecs_to_jiffies(500); + do { + regmap_read(regmap, HDMI_REG_SYS_STATUS, &status); + } while (!(status & TXVIDSTABLE) && + time_before(jiffies, timeout)); + + if (status & TXVIDSTABLE) { + is_stable = true; + break; + } + + it6263_lvds_config(it6263); + + dev_dbg(&it6263->hdmi_i2c->dev, + "retry to lock input video %d\n", i); + } + + if (!is_stable) + dev_warn(&it6263->hdmi_i2c->dev, + "failed to wait for video stable\n"); + + regmap_write(regmap, HDMI_REG_AFE_DRV_CTRL, 0); + + /* AV unmute */ + hdmi_update_bits(it6263, HDMI_REG_GCP, AVMUTE, 0); + + if (it6263->is_hdmi) + regmap_write(regmap, HDMI_REG_PKT_GENERAL_CTRL, + ENABLE_PKT | REPEAT_PKT); +} + +static void it6263_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adj) +{ + struct it6263 *it6263 = bridge_to_it6263(bridge); + struct regmap *regmap = it6263->hdmi_regmap; + bool pclk_high = adj->clock > 80000 ? true : false; + + regmap_write(regmap, HDMI_REG_HDMI_MODE, + it6263->is_hdmi ? TX_HDMI_MODE : TX_DVI_MODE); + + dev_dbg(&it6263->hdmi_i2c->dev, "%s mode\n", + it6263->is_hdmi ? "HDMI" : "DVI"); + + /* setup AFE */ + regmap_write(regmap, HDMI_REG_AFE_DRV_CTRL, AFE_DRV_RST); + if (pclk_high) + regmap_write(regmap, HDMI_REG_AFE_XP_CTRL, + AFE_XP_GAINBIT | AFE_XP_RESETB); + else + regmap_write(regmap, HDMI_REG_AFE_XP_CTRL, + AFE_XP_ER0 | AFE_XP_RESETB); + regmap_write(regmap, HDMI_REG_AFE_ISW_CTRL, 0x10); + if (pclk_high) + regmap_write(regmap, HDMI_REG_AFE_IP_CTRL, + AFE_IP_GAINBIT | AFE_IP_RESETB); + else + regmap_write(regmap, HDMI_REG_AFE_IP_CTRL, + AFE_IP_ER0 | AFE_IP_RESETB); +} + +static int it6263_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct it6263 *it6263 = bridge_to_it6263(bridge); + struct drm_device *drm = bridge->dev; + int ret; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { + DRM_ERROR("Fix bridge driver to make connector optional!"); + return -EINVAL; + } + + if (!drm_core_check_feature(drm, DRIVER_ATOMIC)) { + dev_err(&it6263->hdmi_i2c->dev, + "it6263 driver only copes with atomic updates\n"); + return -ENOTSUPP; + } + + it6263->connector.polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + ret = drm_connector_init(drm, &it6263->connector, + &it6263_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) { + dev_err(&it6263->hdmi_i2c->dev, + "Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_helper_add(&it6263->connector, + &it6263_connector_helper_funcs); + drm_connector_attach_encoder(&it6263->connector, bridge->encoder); + + return ret; +} + +static const struct drm_bridge_funcs it6263_bridge_funcs = { + .attach = it6263_bridge_attach, + .mode_set = it6263_bridge_mode_set, + .disable = it6263_bridge_disable, + .enable = it6263_bridge_enable, +}; + +static int it6263_check_chipid(struct it6263 *it6263) +{ + struct device *dev = &it6263->hdmi_i2c->dev; + u8 vendor_id[2], device_id[2]; + int ret; + + ret = regmap_bulk_read(it6263->hdmi_regmap, REG_VENDOR_ID(0), + &vendor_id, 2); + if (ret) { + dev_err(dev, "regmap_bulk_read failed %d\n", ret); + return ret; + } + + if (vendor_id[0] != HDMI_VENDER_ID_LOW || + vendor_id[1] != HDMI_VENDER_ID_HIGH) { + dev_err(dev, + "Invalid hdmi vendor id %02x %02x(expect 0x01 0xca)\n", + vendor_id[0], vendor_id[1]); + return -EINVAL; + } + + ret = regmap_bulk_read(it6263->hdmi_regmap, REG_DEVICE_ID(0), + &device_id, 2); + if (ret) { + dev_err(dev, "regmap_bulk_read failed %d\n", ret); + return ret; + } + + if (device_id[0] != HDMI_DEVICE_ID_LOW || + device_id[1] != HDMI_DEVICE_ID_HIGH) { + dev_err(dev, + "Invalid hdmi device id %02x %02x(expect 0x13 0x76)\n", + device_id[0], device_id[1]); + return -EINVAL; + } + + ret = regmap_bulk_read(it6263->lvds_regmap, REG_VENDOR_ID(0), + &vendor_id, 2); + if (ret) { + dev_err(dev, "regmap_bulk_read failed %d\n", ret); + return ret; + } + + if (vendor_id[0] != LVDS_VENDER_ID_LOW || + vendor_id[1] != LVDS_VENDER_ID_HIGH) { + dev_err(dev, + "Invalid lvds vendor id %02x %02x(expect 0x15 0xca)\n", + vendor_id[0], vendor_id[1]); + return -EINVAL; + } + + ret = regmap_bulk_read(it6263->lvds_regmap, REG_DEVICE_ID(0), + &device_id, 2); + if (ret) { + dev_err(dev, "regmap_bulk_read failed %d\n", ret); + return ret; + } + + if (device_id[0] != LVDS_DEVICE_ID_LOW || + device_id[1] != LVDS_DEVICE_ID_HIGH) { + dev_err(dev, + "Invalid lvds device id %02x %02x(expect 0x61 0x62)\n", + device_id[0], device_id[1]); + return -EINVAL; + } + + return ret; +} + +static const struct regmap_range it6263_hdmi_volatile_ranges[] = { + { .range_min = 0, .range_max = 0x1ff }, +}; + +static const struct regmap_access_table it6263_hdmi_volatile_table = { + .yes_ranges = it6263_hdmi_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(it6263_hdmi_volatile_ranges), +}; + +static const struct regmap_config it6263_hdmi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_table = &it6263_hdmi_volatile_table, + .cache_type = REGCACHE_NONE, +}; + +static const struct regmap_range it6263_lvds_volatile_ranges[] = { + { .range_min = 0, .range_max = 0xff }, +}; + +static const struct regmap_access_table it6263_lvds_volatile_table = { + .yes_ranges = it6263_lvds_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(it6263_lvds_volatile_ranges), +}; + +static const struct regmap_config it6263_lvds_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_table = &it6263_lvds_volatile_table, + .cache_type = REGCACHE_NONE, +}; + +static int it6263_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device_node *np = dev->of_node; +#if IS_ENABLED(CONFIG_OF_DYNAMIC) + struct device_node *remote_node = NULL, *endpoint = NULL; + struct of_changeset ocs; + struct property *prop; +#endif + struct it6263 *it6263; + int ret; + + it6263 = devm_kzalloc(dev, sizeof(*it6263), GFP_KERNEL); + if (!it6263) + return -ENOMEM; + + it6263->split_mode = of_property_read_bool(np, "split-mode"); + + it6263->hdmi_i2c = client; + it6263->lvds_i2c = i2c_new_dummy_device(client->adapter, + LVDS_INPUT_CTRL_I2C_ADDR); + if (!it6263->lvds_i2c) { + ret = -ENODEV; + goto of_reconfig; + } + + it6263->hdmi_regmap = devm_regmap_init_i2c(client, + &it6263_hdmi_regmap_config); + if (IS_ERR(it6263->hdmi_regmap)) { + ret = PTR_ERR(it6263->hdmi_regmap); + goto unregister_lvds_i2c; + } + + it6263->lvds_regmap = devm_regmap_init_i2c(it6263->lvds_i2c, + &it6263_lvds_regmap_config); + if (IS_ERR(it6263->lvds_regmap)) { + ret = PTR_ERR(it6263->lvds_regmap); + goto unregister_lvds_i2c; + } + + it6263->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(it6263->reset_gpio)) { + ret = PTR_ERR(it6263->reset_gpio); + + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get reset gpio: %d\n", ret); + + goto unregister_lvds_i2c; + } + + it6263_reset(it6263); + + ret = regmap_write(it6263->hdmi_regmap, HDMI_REG_SW_RST, HDMI_RST_ALL); + if (ret) + goto unregister_lvds_i2c; + + usleep_range(1000, 2000); + + ret = regmap_write(it6263->hdmi_regmap, HDMI_REG_LVDS_PORT, + LVDS_INPUT_CTRL_I2C_ADDR << 1); + if (ret) + goto unregister_lvds_i2c; + + ret = regmap_write(it6263->hdmi_regmap, HDMI_REG_LVDS_PORT_EN, 0x01); + if (ret) + goto unregister_lvds_i2c; + + /* select HDMI bank0 */ + ret = regmap_write(it6263->hdmi_regmap, HDMI_REG_BANK_CTRL, + BANK_SEL(0)); + if (ret) + goto unregister_lvds_i2c; + + ret = it6263_check_chipid(it6263); + if (ret) + goto unregister_lvds_i2c; + + it6263_lvds_config(it6263); + it6263_hdmi_config(it6263); + + it6263->bridge.funcs = &it6263_bridge_funcs; + it6263->bridge.of_node = np; + drm_bridge_add(&it6263->bridge); + + i2c_set_clientdata(client, it6263); + + return ret; + +unregister_lvds_i2c: + i2c_unregister_device(it6263->lvds_i2c); + if (ret == -EPROBE_DEFER) + return ret; + +of_reconfig: +#if IS_ENABLED(CONFIG_OF_DYNAMIC) + endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (endpoint) + remote_node = of_graph_get_remote_port_parent(endpoint); + + if (remote_node) { + int num_endpoints = 0; + + /* + * Remote node should have two endpoints (input and output: us) + * If remote node has more than two endpoints, probably that it + * has more outputs, so there is no need to disable it. + */ + endpoint = NULL; + while ((endpoint = of_graph_get_next_endpoint(remote_node, + endpoint))) + num_endpoints++; + + if (num_endpoints > 2) { + of_node_put(remote_node); + return ret; + } + + prop = devm_kzalloc(dev, sizeof(*prop), GFP_KERNEL); + prop->name = devm_kstrdup(dev, "status", GFP_KERNEL); + prop->value = devm_kstrdup(dev, "disabled", GFP_KERNEL); + prop->length = 9; + of_changeset_init(&ocs); + of_changeset_update_property(&ocs, remote_node, prop); + ret = of_changeset_apply(&ocs); + if (!ret) + dev_warn(dev, + "Probe failed. Remote port '%s' disabled\n", + remote_node->full_name); + + of_node_put(remote_node); + }; +#endif + + return ret; +} + +static int it6263_remove(struct i2c_client *client) + +{ + struct it6263 *it6263 = i2c_get_clientdata(client); + + drm_bridge_remove(&it6263->bridge); + i2c_unregister_device(it6263->lvds_i2c); + + return 0; +} + +static const struct of_device_id it6263_dt_ids[] = { + { .compatible = "ite,it6263", }, + { } +}; +MODULE_DEVICE_TABLE(of, it6263_dt_ids); + +static const struct i2c_device_id it6263_i2c_ids[] = { + { "it6263", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, it6263_i2c_ids); + +static struct i2c_driver it6263_driver = { + .probe = it6263_probe, + .remove = it6263_remove, + .driver = { + .name = "it6263", + .of_match_table = it6263_dt_ids, + }, + .id_table = it6263_i2c_ids, +}; +module_i2c_driver(it6263_driver); + +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_DESCRIPTION("ITE Tech. Inc. IT6263 LVDS->HDMI bridge"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi.c index 691039aba87f..b19cf038025d 100644 --- a/drivers/gpu/drm/bridge/nwl-dsi.c +++ b/drivers/gpu/drm/bridge/nwl-dsi.c @@ -9,7 +9,9 @@ #include <linux/bitfield.h> #include <linux/bits.h> #include <linux/clk.h> +#include <linux/component.h> #include <linux/irq.h> +#include <linux/firmware/imx/sci.h> #include <linux/math64.h> #include <linux/mfd/syscon.h> #include <linux/module.h> @@ -22,6 +24,7 @@ #include <linux/sys_soc.h> #include <linux/time64.h> +#include <drm/drm_atomic_helper.h> #include <drm/drm_atomic_state_helper.h> #include <drm/drm_bridge.h> #include <drm/drm_mipi_dsi.h> @@ -29,6 +32,8 @@ #include <drm/drm_panel.h> #include <drm/drm_print.h> +#include <dt-bindings/firmware/imx/rsrc.h> + #include <video/mipi_display.h> #include "nwl-dsi.h" @@ -41,13 +46,54 @@ #define NWL_DSI_MIPI_FIFO_TIMEOUT msecs_to_jiffies(500) +/* Maximum Video PLL frequency: 1.2GHz */ +#define MAX_PLL_FREQ 1200000000 + +#define MBPS(x) ((x) * 1000000) +#define MIN_PHY_RATE MBPS(24) +#define MAX_PHY_RATE MBPS(30) + +#define DC_ID(x) IMX_SC_R_DC_ ## x +#define MIPI_ID(x) IMX_SC_R_MIPI_ ## x +#define SYNC_CTRL(x) IMX_SC_C_SYNC_CTRL ## x +#define PXL_VLD(x) IMX_SC_C_PXL_LINK_MST ## x ## _VLD +#define PXL_ADDR(x) IMX_SC_C_PXL_LINK_MST ## x ## _ADDR + +#define IMX8ULP_DSI_CM_MASK BIT(1) +#define IMX8ULP_DSI_CM_NORMAL BIT(1) + +/* Possible valid PHY reference clock rates*/ +static u32 phyref_rates[] = { + 27000000, + 25000000, + 24000000, +}; + +/* + * TODO: find a better way to access imx_crtc_state + */ +struct imx_crtc_state { + struct drm_crtc_state base; + u32 bus_format; + u32 bus_flags; + int di_hsync_pin; + int di_vsync_pin; +}; + +static inline struct imx_crtc_state *to_imx_crtc_state(struct drm_crtc_state *s) +{ + return container_of(s, struct imx_crtc_state, base); +} + enum transfer_direction { DSI_PACKET_SEND, DSI_PACKET_RECEIVE, }; -#define NWL_DSI_ENDPOINT_LCDIF 0 -#define NWL_DSI_ENDPOINT_DCSS 1 +#define NWL_DSI_ENDPOINT_LCDIF 0 +#define NWL_DSI_ENDPOINT_DCSS 1 +#define NWL_DSI_ENDPOINT_DCNANO 0 +#define NWL_DSI_ENDPOINT_EPDC 1 struct nwl_dsi_transfer { const struct mipi_dsi_msg *msg; @@ -63,7 +109,21 @@ struct nwl_dsi_transfer { size_t rx_len; /* in bytes */ }; +struct mode_config { + int clock; + int crtc_clock; + unsigned int lanes; + unsigned long bitclock; + unsigned long phy_rates[3]; + unsigned long pll_rates[3]; + int phy_rate_idx; + struct list_head list; +}; + +struct nwl_dsi_platform_data; + struct nwl_dsi { + struct drm_encoder encoder; struct drm_bridge bridge; struct mipi_dsi_host dsi_host; struct drm_bridge *panel_bridge; @@ -71,8 +131,11 @@ struct nwl_dsi { struct phy *phy; union phy_configure_opts phy_cfg; unsigned int quirks; + unsigned int instance; + const struct nwl_dsi_platform_data *pdata; struct regmap *regmap; + struct regmap *csr; int irq; /* * The DSI host controller needs this reset sequence according to NWL: @@ -97,6 +160,9 @@ struct nwl_dsi { struct clk *rx_esc_clk; struct clk *tx_esc_clk; struct clk *core_clk; + struct clk *bypass_clk; + struct clk *pixel_clk; + struct clk *pll_clk; /* * hardware bug: the i.MX8MQ needs this clock on during reset * even when not using LCDIF. @@ -111,6 +177,9 @@ struct nwl_dsi { int error; struct nwl_dsi_transfer *xfer; + struct list_head valid_modes; + u32 clk_drop_lvl; + bool use_dcss; }; static const struct regmap_config nwl_dsi_regmap_config = { @@ -121,6 +190,34 @@ static const struct regmap_config nwl_dsi_regmap_config = { .name = DRV_NAME, }; +typedef enum { + NWL_DSI_CORE_CLK = BIT(1), + NWL_DSI_LCDIF_CLK = BIT(2), + NWL_DSI_BYPASS_CLK = BIT(3), + NWL_DSI_PIXEL_CLK = BIT(4), +} nwl_dsi_clks; + +struct nwl_dsi_platform_data { + int (*pclk_reset)(struct nwl_dsi *dsi, bool reset); + int (*mipi_reset)(struct nwl_dsi *dsi, bool reset); + int (*dpi_reset)(struct nwl_dsi *dsi, bool reset); + nwl_dsi_clks clks; + u32 reg_tx_ulps; + u32 reg_pxl2dpi; + u32 reg_cm; + u32 max_instances; + u32 tx_clk_rate; + u32 rx_clk_rate; + bool mux_present; + bool shared_phy; + u32 bit_bta_timeout; + u32 bit_hs_tx_timeout; + bool use_lcdif_or_dcss; + bool use_dcnano_or_epdc; + bool rx_clk_quirk; /* enable rx_esc clock to access registers */ +}; + + static inline struct nwl_dsi *bridge_to_dsi(struct drm_bridge *bridge) { return container_of(bridge, struct nwl_dsi, bridge); @@ -221,13 +318,15 @@ static int nwl_dsi_config_host(struct nwl_dsi *dsi) DRM_DEV_DEBUG_DRIVER(dsi->dev, "DSI Lanes %d\n", dsi->lanes); nwl_dsi_write(dsi, NWL_DSI_CFG_NUM_LANES, dsi->lanes - 1); - if (dsi->dsi_mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) { + if (dsi->dsi_mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) nwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK, 0x01); - nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP, 0x01); - } else { + else nwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK, 0x00); + + if (dsi->dsi_mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET) nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP, 0x00); - } + else + nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP, 0x01); /* values in byte clock cycles */ cycles = ui2bc(cfg->clk_pre); @@ -261,6 +360,7 @@ static int nwl_dsi_config_dpi(struct nwl_dsi *dsi) bool burst_mode; int hfront_porch, hback_porch, vfront_porch, vback_porch; int hsync_len, vsync_len; + int hfp, hbp, hsa; hfront_porch = dsi->mode.hsync_start - dsi->mode.hdisplay; hsync_len = dsi->mode.hsync_end - dsi->mode.hsync_start; @@ -314,9 +414,35 @@ static int nwl_dsi_config_dpi(struct nwl_dsi *dsi) dsi->mode.hdisplay); } - nwl_dsi_write(dsi, NWL_DSI_HFP, hfront_porch); - nwl_dsi_write(dsi, NWL_DSI_HBP, hback_porch); - nwl_dsi_write(dsi, NWL_DSI_HSA, hsync_len); + if (of_device_is_compatible(dsi->panel_bridge->of_node, + "raydium,rm68200") || + of_device_is_compatible(dsi->panel_bridge->of_node, + "rocktech,hx8394f")) { + int bytes = mipi_dsi_pixel_format_to_bpp(dsi->format) >> 3; + + /* + * FIXME: This is a workaround for display shift + * of the RM68200 and HX8394F panels. It is based + * on previous knowledge got from support task for + * external DSI bridges by turning the hfp/hbp/hsa + * to be in bytes and substracting certain magic values. + * Furthermore, rounding hsa up to 2 is needed. + * This can be fixed as soon as formulas to + * determine the settings are available. + */ + hfp = bytes * hfront_porch - 12; + hbp = bytes * hback_porch - 10; + hsa = bytes * hsync_len - 10; + hsa = roundup(hsa, 2); + } else { + hfp = hfront_porch; + hbp = hback_porch; + hsa = hsync_len; + } + + nwl_dsi_write(dsi, NWL_DSI_HFP, hfp); + nwl_dsi_write(dsi, NWL_DSI_HBP, hbp); + nwl_dsi_write(dsi, NWL_DSI_HSA, hsa); nwl_dsi_write(dsi, NWL_DSI_ENABLE_MULT_PKTS, 0x0); nwl_dsi_write(dsi, NWL_DSI_BLLP_MODE, 0x1); @@ -341,7 +467,7 @@ static int nwl_dsi_init_interrupts(struct nwl_dsi *dsi) irq_enable = ~(u32)(NWL_DSI_TX_PKT_DONE_MASK | NWL_DSI_RX_PKT_HDR_RCVD_MASK | NWL_DSI_TX_FIFO_OVFLW_MASK | - NWL_DSI_HS_TX_TIMEOUT_MASK); + dsi->pdata->bit_hs_tx_timeout); nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, irq_enable); @@ -367,6 +493,18 @@ static int nwl_dsi_host_attach(struct mipi_dsi_host *dsi_host, return 0; } +static int nwl_dsi_host_detach(struct mipi_dsi_host *dsi_host, + struct mipi_dsi_device *device) +{ + struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi, dsi_host); + + dsi->lanes = 0; + dsi->format = 0; + dsi->dsi_mode_flags = 0; + + return 0; +} + static bool nwl_dsi_read_packet(struct nwl_dsi *dsi, u32 status) { struct device *dev = dsi->dev; @@ -630,6 +768,7 @@ static ssize_t nwl_dsi_host_transfer(struct mipi_dsi_host *dsi_host, static const struct mipi_dsi_host_ops nwl_dsi_host_ops = { .attach = nwl_dsi_host_attach, + .detach = nwl_dsi_host_detach, .transfer = nwl_dsi_host_transfer, }; @@ -643,7 +782,7 @@ static irqreturn_t nwl_dsi_irq_handler(int irq, void *data) if (irq_status & NWL_DSI_TX_FIFO_OVFLW) DRM_DEV_ERROR_RATELIMITED(dsi->dev, "tx fifo overflow\n"); - if (irq_status & NWL_DSI_HS_TX_TIMEOUT) + if (irq_status & dsi->pdata->bit_hs_tx_timeout) DRM_DEV_ERROR_RATELIMITED(dsi->dev, "HS tx timeout\n"); if (irq_status & NWL_DSI_TX_PKT_DONE || @@ -654,7 +793,7 @@ static irqreturn_t nwl_dsi_irq_handler(int irq, void *data) return IRQ_HANDLED; } -static int nwl_dsi_mode_set(struct nwl_dsi *dsi) +static int nwl_dsi_enable(struct nwl_dsi *dsi) { struct device *dev = dsi->dev; union phy_configure_opts *phy_cfg = &dsi->phy_cfg; @@ -732,45 +871,261 @@ static int nwl_dsi_disable(struct nwl_dsi *dsi) /* Disabling the clock before the phy breaks enabling dsi again */ clk_disable_unprepare(dsi->tx_esc_clk); + /* Disable rx_esc clock as registers are not accessed any more. */ + if (dsi->pdata->rx_clk_quirk) + clk_disable_unprepare(dsi->rx_esc_clk); + return 0; } static void -nwl_dsi_bridge_atomic_disable(struct drm_bridge *bridge, - struct drm_bridge_state *old_bridge_state) +nwl_dsi_bridge_atomic_post_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { struct nwl_dsi *dsi = bridge_to_dsi(bridge); int ret; + /* + * Call panel_bridge's post_disable() callback(if any) so that + * it may send any MIPI DSI command before this MIPI DSI controller + * and it's PHY are disabled. + */ + if (dsi->panel_bridge->funcs->post_disable) + dsi->panel_bridge->funcs->post_disable(dsi->panel_bridge); + nwl_dsi_disable(dsi); - ret = reset_control_assert(dsi->rst_dpi); + ret = dsi->pdata->dpi_reset(dsi, true); if (ret < 0) { DRM_DEV_ERROR(dsi->dev, "Failed to assert DPI: %d\n", ret); return; } - ret = reset_control_assert(dsi->rst_byte); + ret = dsi->pdata->mipi_reset(dsi, true); if (ret < 0) { - DRM_DEV_ERROR(dsi->dev, "Failed to assert ESC: %d\n", ret); + DRM_DEV_ERROR(dsi->dev, "Failed to assert DSI: %d\n", ret); return; } - ret = reset_control_assert(dsi->rst_esc); - if (ret < 0) { - DRM_DEV_ERROR(dsi->dev, "Failed to assert BYTE: %d\n", ret); - return; - } - ret = reset_control_assert(dsi->rst_pclk); + ret = dsi->pdata->pclk_reset(dsi, true); if (ret < 0) { DRM_DEV_ERROR(dsi->dev, "Failed to assert PCLK: %d\n", ret); return; } - clk_disable_unprepare(dsi->core_clk); - clk_disable_unprepare(dsi->lcdif_clk); + if (dsi->core_clk) + clk_disable_unprepare(dsi->core_clk); + if (dsi->bypass_clk) + clk_disable_unprepare(dsi->bypass_clk); + if (dsi->pixel_clk) + clk_disable_unprepare(dsi->pixel_clk); + if (dsi->lcdif_clk) + clk_disable_unprepare(dsi->lcdif_clk); pm_runtime_put(dsi->dev); } +static unsigned long nwl_dsi_get_bit_clock(struct nwl_dsi *dsi, + unsigned long pixclock, u32 lanes) +{ + int bpp; + + if (lanes < 1 || lanes > 4) + return 0; + + bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + + return (pixclock * bpp) / lanes; +} + +/* + * Utility function to calculate least commom multiple, using an improved + * version of the Euclidean algorithm for greatest common factor. + */ +static unsigned long nwl_dsi_get_lcm(unsigned long a, unsigned long b) +{ + u32 gcf = 0; /* greatest common factor */ + unsigned long tmp_a = a; + unsigned long tmp_b = b; + + if (!a || !b) + return 0; + + while (tmp_a % tmp_b) { + gcf = tmp_a % tmp_b; + tmp_a = tmp_b; + tmp_b = gcf; + } + + if (!gcf) + return a; + + return ((unsigned long long)a * b) / gcf; +} + +/* + * This function tries to adjust the crtc_clock for a DSI device in such a way + * that the video pll will be able to satisfy both Display Controller pixel + * clock (feeding out DPI interface) and our input phy_ref clock. + * Also, the DC pixel clock must be lower than the actual clock in order to + * have enough blanking space to send DSI commands, if the device is a panel. + */ +static void nwl_dsi_setup_pll_config(struct mode_config *config, u32 lvl) +{ + unsigned long pll_rate; + int div; + size_t i, num_rates = ARRAY_SIZE(config->phy_rates); + + config->crtc_clock = 0; + + for (i = 0; i < num_rates; i++) { + int crtc_clock; + + if (!config->phy_rates[i]) + break; + /* + * First, we need to check if phy_ref can actually be obtained + * from pixel clock. To do this, we check their lowest common + * multiple, which has to be in PLL range. + */ + pll_rate = nwl_dsi_get_lcm(config->clock, config->phy_rates[i]); + if (pll_rate > MAX_PLL_FREQ) { + /* Drop pll_rate to a realistic value */ + while (pll_rate > MAX_PLL_FREQ) + pll_rate >>= 1; + /* Make sure pll_rate can provide phy_ref rate */ + div = DIV_ROUND_UP(pll_rate, config->phy_rates[i]); + pll_rate = config->phy_rates[i] * div; + } else { + /* + * Increase the pll rate to highest possible rate for + * better accuracy. + */ + while (pll_rate <= MAX_PLL_FREQ) + pll_rate <<= 1; + pll_rate >>= 1; + } + + /* + * Next, we need to tweak the pll_rate to a value that can also + * satisfy the crtc_clock. + */ + div = DIV_ROUND_CLOSEST(pll_rate, config->clock); + if (lvl) + pll_rate -= config->phy_rates[i] * lvl; + crtc_clock = pll_rate / div; + config->pll_rates[i] = pll_rate; + + /* + * Pick a crtc_clock which is closest to pixel clock. + * Also, make sure that the pixel clock is a multiply of + * 50Hz. + */ + if (!(crtc_clock % 50) && + abs(config->clock - crtc_clock) < + abs(config->clock - config->crtc_clock)) { + config->crtc_clock = crtc_clock; + config->phy_rate_idx = i; + } + } +} + + +/* + * This function will try the required phy speed for current mode + * If the phy speed can be achieved, the phy will save the speed + * configuration + */ +static struct mode_config *nwl_dsi_mode_probe(struct nwl_dsi *dsi, + const struct drm_display_mode *mode) +{ + struct device *dev = dsi->dev; + struct mode_config *config; + union phy_configure_opts phy_opts; + unsigned long clock = mode->clock * 1000; + unsigned long bit_clk = 0; + unsigned long phy_rates[3] = {0}; + int match_rates = 0; + u32 lanes = dsi->lanes; + size_t i = 0, num_rates = ARRAY_SIZE(phyref_rates); + + list_for_each_entry(config, &dsi->valid_modes, list) + if (config->clock == clock) + return config; + + phy_mipi_dphy_get_default_config(clock, + mipi_dsi_pixel_format_to_bpp(dsi->format), + lanes, &phy_opts.mipi_dphy); + phy_opts.mipi_dphy.lp_clk_rate = clk_get_rate(dsi->tx_esc_clk); + + while (i < num_rates) { + int ret; + + bit_clk = nwl_dsi_get_bit_clock(dsi, clock, lanes); + + clk_set_rate(dsi->pll_clk, phyref_rates[i] * 32); + clk_set_rate(dsi->phy_ref_clk, phyref_rates[i]); + ret = phy_validate(dsi->phy, PHY_MODE_MIPI_DPHY, 0, &phy_opts); + + /* Pick the non-failing rate, and search for more */ + if (!ret) { + phy_rates[match_rates++] = phyref_rates[i++]; + continue; + } + + if (match_rates) + break; + + /* Reached the end of phyref_rates, try another lane config */ + if ((i++ == num_rates - 1) && (--lanes > 2)) { + i = 0; + continue; + } + } + + /* + * Try swinging between min and max pll rates and see what rate (in terms + * of kHz) we can custom use to get the required bit-clock. + */ + if (!match_rates) { + int min_div, max_div; + int bit_clk_khz; + + lanes = dsi->lanes; + bit_clk = nwl_dsi_get_bit_clock(dsi, clock, lanes); + + min_div = DIV_ROUND_UP(bit_clk, MAX_PHY_RATE); + max_div = DIV_ROUND_DOWN_ULL(bit_clk, MIN_PHY_RATE); + bit_clk_khz = bit_clk / 1000; + + for (i = max_div; i > min_div; i--) { + if (!(bit_clk_khz % i)) { + phy_rates[0] = bit_clk / i; + match_rates = 1; + break; + } + } + } + + if (!match_rates) { + DRM_DEV_DEBUG_DRIVER(dev, + "Cannot setup PHY for mode: %ux%u @%d kHz\n", + mode->hdisplay, + mode->vdisplay, + mode->clock); + + return NULL; + } + + config = devm_kzalloc(dsi->dev, sizeof(struct mode_config), GFP_KERNEL); + config->clock = clock; + config->lanes = lanes; + config->bitclock = bit_clk; + memcpy(&config->phy_rates, &phy_rates, sizeof(phy_rates)); + list_add(&config->list, &dsi->valid_modes); + + return config; +} + + static int nwl_dsi_get_dphy_params(struct nwl_dsi *dsi, const struct drm_display_mode *mode, union phy_configure_opts *phy_opts) @@ -804,14 +1159,29 @@ nwl_dsi_bridge_mode_valid(struct drm_bridge *bridge, const struct drm_display_mode *mode) { struct nwl_dsi *dsi = bridge_to_dsi(bridge); - int bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + struct mode_config *config; + unsigned long pll_rate; + int bit_rate; - if (mode->clock * bpp > 15000000 * dsi->lanes) + bit_rate = nwl_dsi_get_bit_clock(dsi, mode->clock * 1000, dsi->lanes); + + DRM_DEV_DEBUG_DRIVER(dsi->dev, "Validating mode:"); + drm_mode_debug_printmodeline(mode); + + if (bit_rate > MBPS(1500)) return MODE_CLOCK_HIGH; - if (mode->clock * bpp < 80000 * dsi->lanes) + if (bit_rate < MBPS(80)) return MODE_CLOCK_LOW; + config = nwl_dsi_mode_probe(dsi, mode); + if (!config) + return MODE_NOCLOCK; + + pll_rate = config->pll_rates[config->phy_rate_idx]; + if (dsi->pll_clk && !pll_rate) + nwl_dsi_setup_pll_config(config, dsi->clk_drop_lvl); + return MODE_OK; } @@ -820,20 +1190,56 @@ static int nwl_dsi_bridge_atomic_check(struct drm_bridge *bridge, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { - struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + struct nwl_dsi *dsi = bridge_to_dsi(bridge); + struct drm_display_mode *adjusted = &crtc_state->adjusted_mode; + struct mode_config *config; + unsigned long pll_rate; - /* At least LCDIF + NWL needs active high sync */ - adjusted_mode->flags |= (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC); - adjusted_mode->flags &= ~(DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "atomic check:\n"); + drm_mode_debug_printmodeline(adjusted); - /* - * Do a full modeset if crtc_state->active is changed to be true. - * This ensures our ->mode_set() is called to get the DSI controller - * and the PHY ready to send DCS commands, when only the connector's - * DPMS is brought out of "Off" status. - */ - if (crtc_state->active_changed && crtc_state->active) - crtc_state->mode_changed = true; + config = nwl_dsi_mode_probe(dsi, adjusted); + if (!config) + return -EINVAL; + + DRM_DEV_DEBUG_DRIVER(dsi->dev, "lanes=%u, data_rate=%lu\n", + config->lanes, config->bitclock); + if (config->lanes < 2 || config->lanes > 4) + return -EINVAL; + + /* Max data rate for this controller is 1.5Gbps */ + if (config->bitclock > 1500000000) + return -EINVAL; + + pll_rate = config->pll_rates[config->phy_rate_idx]; + if (dsi->pll_clk && pll_rate) { + clk_set_rate(dsi->pll_clk, pll_rate); + DRM_DEV_DEBUG_DRIVER(dsi->dev, + "Video pll rate: %lu (actual: %lu)", + pll_rate, clk_get_rate(dsi->pll_clk)); + } + /* Update the crtc_clock to be used by display controller */ + if (config->crtc_clock) + adjusted->crtc_clock = config->crtc_clock / 1000; + else if (dsi->clk_drop_lvl) { + int div; + unsigned long phy_ref_rate; + + phy_ref_rate = config->phy_rates[config->phy_rate_idx]; + pll_rate = config->bitclock; + div = DIV_ROUND_CLOSEST(pll_rate, config->clock); + pll_rate -= phy_ref_rate * dsi->clk_drop_lvl; + adjusted->crtc_clock = (pll_rate / div) / 1000; + } + + if (!dsi->use_dcss && !dsi->pdata->use_dcnano_or_epdc) { + /* At least LCDIF + NWL needs active high sync */ + adjusted->flags |= (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC); + adjusted->flags &= ~(DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC); + } else { + adjusted->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC); + adjusted->flags |= (DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC); + } return 0; } @@ -847,54 +1253,108 @@ nwl_dsi_bridge_mode_set(struct drm_bridge *bridge, struct device *dev = dsi->dev; union phy_configure_opts new_cfg; unsigned long phy_ref_rate; + struct mode_config *config; int ret; + DRM_DEV_DEBUG_DRIVER(dsi->dev, "Setting mode:\n"); + drm_mode_debug_printmodeline(adjusted_mode); + + config = nwl_dsi_mode_probe(dsi, adjusted_mode); + /* New mode? This should NOT happen */ + if (!config) { + DRM_DEV_ERROR(dsi->dev, "Unsupported mode provided:\n"); + drm_mode_debug_printmodeline(adjusted_mode); + return; + } + + /* + * If bypass and pixel clocks are present, we need to set their rates + * now. + */ + if (dsi->bypass_clk) + clk_set_rate(dsi->bypass_clk, adjusted_mode->crtc_clock * 1000); + if (dsi->pixel_clk) + clk_set_rate(dsi->pixel_clk, adjusted_mode->crtc_clock * 1000); + + memcpy(&dsi->mode, adjusted_mode, sizeof(dsi->mode)); + + phy_ref_rate = config->phy_rates[config->phy_rate_idx]; + clk_set_rate(dsi->phy_ref_clk, phy_ref_rate); ret = nwl_dsi_get_dphy_params(dsi, adjusted_mode, &new_cfg); if (ret < 0) return; - phy_ref_rate = clk_get_rate(dsi->phy_ref_clk); - DRM_DEV_DEBUG_DRIVER(dev, "PHY at ref rate: %lu\n", phy_ref_rate); + DRM_DEV_DEBUG_DRIVER(dev, + "PHY at ref rate: %lu (actual: %lu)\n", + phy_ref_rate, clk_get_rate(dsi->phy_ref_clk)); + /* Save the new desired phy config */ memcpy(&dsi->phy_cfg, &new_cfg, sizeof(new_cfg)); +} - memcpy(&dsi->mode, adjusted_mode, sizeof(dsi->mode)); - drm_mode_debug_printmodeline(adjusted_mode); +static void +nwl_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct nwl_dsi *dsi = bridge_to_dsi(bridge); + int ret; - if (pm_runtime_resume_and_get(dev) < 0) + if (pm_runtime_resume_and_get(dsi->dev) < 0) return; - if (clk_prepare_enable(dsi->lcdif_clk) < 0) + dsi->pdata->dpi_reset(dsi, true); + dsi->pdata->mipi_reset(dsi, true); + dsi->pdata->pclk_reset(dsi, true); + + if (dsi->lcdif_clk && clk_prepare_enable(dsi->lcdif_clk) < 0) goto runtime_put; - if (clk_prepare_enable(dsi->core_clk) < 0) + if (dsi->core_clk && clk_prepare_enable(dsi->core_clk) < 0) + goto runtime_put; + if (dsi->bypass_clk && clk_prepare_enable(dsi->bypass_clk) < 0) + goto runtime_put; + if (dsi->pixel_clk && clk_prepare_enable(dsi->pixel_clk) < 0) + goto runtime_put; + /* + * Enable rx_esc clock for some platforms to access DSI host controller + * and PHY registers. + */ + if (dsi->pdata->rx_clk_quirk && clk_prepare_enable(dsi->rx_esc_clk) < 0) goto runtime_put; + /* Always use normal mode(full mode) for Type-4 display */ + if (dsi->pdata->reg_cm) + regmap_update_bits(dsi->csr, dsi->pdata->reg_cm, + IMX8ULP_DSI_CM_MASK, IMX8ULP_DSI_CM_NORMAL); + /* Step 1 from DSI reset-out instructions */ - ret = reset_control_deassert(dsi->rst_pclk); + ret = dsi->pdata->pclk_reset(dsi, false); if (ret < 0) { - DRM_DEV_ERROR(dev, "Failed to deassert PCLK: %d\n", ret); + DRM_DEV_ERROR(dsi->dev, "Failed to deassert PCLK: %d\n", ret); goto runtime_put; } /* Step 2 from DSI reset-out instructions */ - nwl_dsi_mode_set(dsi); + nwl_dsi_enable(dsi); /* Step 3 from DSI reset-out instructions */ - ret = reset_control_deassert(dsi->rst_esc); + ret = dsi->pdata->mipi_reset(dsi, false); if (ret < 0) { - DRM_DEV_ERROR(dev, "Failed to deassert ESC: %d\n", ret); - goto runtime_put; - } - ret = reset_control_deassert(dsi->rst_byte); - if (ret < 0) { - DRM_DEV_ERROR(dev, "Failed to deassert BYTE: %d\n", ret); + DRM_DEV_ERROR(dsi->dev, "Failed to deassert DSI: %d\n", ret); goto runtime_put; } + /* + * We need to force call enable for the panel here, in order to + * make the panel initialization execute before our call to + * bridge_enable, where we will enable the DPI and start streaming + * pixels on the data lanes. + */ + drm_bridge_chain_enable(dsi->panel_bridge); + return; runtime_put: - pm_runtime_put_sync(dev); + pm_runtime_put_sync(dsi->dev); } static void @@ -905,7 +1365,7 @@ nwl_dsi_bridge_atomic_enable(struct drm_bridge *bridge, int ret; /* Step 5 from DSI reset-out instructions */ - ret = reset_control_deassert(dsi->rst_dpi); + ret = dsi->pdata->dpi_reset(dsi, false); if (ret < 0) DRM_DEV_ERROR(dsi->dev, "Failed to deassert DPI: %d\n", ret); } @@ -916,6 +1376,7 @@ static int nwl_dsi_bridge_attach(struct drm_bridge *bridge, struct nwl_dsi *dsi = bridge_to_dsi(bridge); struct drm_bridge *panel_bridge; struct drm_panel *panel; + struct clk *phy_parent; int ret; ret = drm_of_find_panel_or_bridge(dsi->dev->of_node, 1, 0, &panel, @@ -924,7 +1385,8 @@ static int nwl_dsi_bridge_attach(struct drm_bridge *bridge, return ret; if (panel) { - panel_bridge = drm_panel_bridge_add(panel); + panel_bridge = drm_panel_bridge_add_typed(panel, + DRM_MODE_CONNECTOR_DSI); if (IS_ERR(panel_bridge)) return PTR_ERR(panel_bridge); } @@ -933,6 +1395,27 @@ static int nwl_dsi_bridge_attach(struct drm_bridge *bridge, if (!dsi->panel_bridge) return -EPROBE_DEFER; + phy_parent = devm_clk_get(dsi->dev, "phy_parent"); + if (!IS_ERR_OR_NULL(phy_parent)) { + ret = clk_set_parent(dsi->phy_ref_clk, phy_parent); + ret |= clk_set_parent(dsi->tx_esc_clk, phy_parent); + ret |= clk_set_parent(dsi->rx_esc_clk, phy_parent); + + if (ret) { + dev_err(dsi->dev, + "Error re-parenting phy/tx/rx clocks: %d", + ret); + + return ret; + } + + if (dsi->pdata->tx_clk_rate) + clk_set_rate(dsi->tx_esc_clk, dsi->pdata->tx_clk_rate); + + if (dsi->pdata->rx_clk_rate) + clk_set_rate(dsi->rx_esc_clk, dsi->pdata->rx_clk_rate); + } + return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge, flags); } @@ -982,8 +1465,9 @@ static const struct drm_bridge_funcs nwl_dsi_bridge_funcs = { .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, .atomic_reset = drm_atomic_helper_bridge_reset, .atomic_check = nwl_dsi_bridge_atomic_check, + .atomic_pre_enable = nwl_dsi_bridge_atomic_pre_enable, .atomic_enable = nwl_dsi_bridge_atomic_enable, - .atomic_disable = nwl_dsi_bridge_atomic_disable, + .atomic_post_disable = nwl_dsi_bridge_atomic_post_disable, .atomic_get_input_bus_fmts = nwl_bridge_atomic_get_input_bus_fmts, .mode_set = nwl_dsi_bridge_mode_set, .mode_valid = nwl_dsi_bridge_mode_valid, @@ -991,12 +1475,22 @@ static const struct drm_bridge_funcs nwl_dsi_bridge_funcs = { .detach = nwl_dsi_bridge_detach, }; +static void nwl_dsi_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs nwl_dsi_encoder_funcs = { + .destroy = nwl_dsi_encoder_destroy, +}; + static int nwl_dsi_parse_dt(struct nwl_dsi *dsi) { struct platform_device *pdev = to_platform_device(dsi->dev); + struct device_node *np = dsi->dev->of_node; struct clk *clk; void __iomem *base; - int ret; + int ret, id; dsi->phy = devm_phy_get(dsi->dev, "dphy"); if (IS_ERR(dsi->phy)) { @@ -1006,23 +1500,64 @@ static int nwl_dsi_parse_dt(struct nwl_dsi *dsi) return ret; } - clk = devm_clk_get(dsi->dev, "lcdif"); - if (IS_ERR(clk)) { - ret = PTR_ERR(clk); - DRM_DEV_ERROR(dsi->dev, "Failed to get lcdif clock: %d\n", - ret); - return ret; + id = of_alias_get_id(np, "mipi_dsi"); + if (id > 0) { + if (id > dsi->pdata->max_instances - 1) { + dev_err(dsi->dev, + "Too many instances! (cur: %d, max: %d)\n", + id, dsi->pdata->max_instances); + return -ENODEV; + } + dsi->instance = id; } - dsi->lcdif_clk = clk; - clk = devm_clk_get(dsi->dev, "core"); - if (IS_ERR(clk)) { - ret = PTR_ERR(clk); - DRM_DEV_ERROR(dsi->dev, "Failed to get core clock: %d\n", - ret); - return ret; + if (dsi->pdata->clks & NWL_DSI_LCDIF_CLK) { + clk = devm_clk_get(dsi->dev, "lcdif"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + DRM_DEV_ERROR(dsi->dev, + "Failed to get lcdif clock: %d\n", + ret); + return ret; + } + dsi->lcdif_clk = clk; + } + + if (dsi->pdata->clks & NWL_DSI_CORE_CLK) { + clk = devm_clk_get(dsi->dev, "core"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + DRM_DEV_ERROR(dsi->dev, + "Failed to get core clock: %d\n", + ret); + return ret; + } + dsi->core_clk = clk; + } + + if (dsi->pdata->clks & NWL_DSI_BYPASS_CLK) { + clk = devm_clk_get(dsi->dev, "bypass"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + DRM_DEV_ERROR(dsi->dev, + "Failed to get bypass clock: %d\n", + ret); + return ret; + } + dsi->bypass_clk = clk; + } + + if (dsi->pdata->clks & NWL_DSI_PIXEL_CLK) { + clk = devm_clk_get(dsi->dev, "pixel"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + DRM_DEV_ERROR(dsi->dev, + "Failed to get pixel clock: %d\n", + ret); + return ret; + } + dsi->pixel_clk = clk; } - dsi->core_clk = clk; clk = devm_clk_get(dsi->dev, "phy_ref"); if (IS_ERR(clk)) { @@ -1051,12 +1586,21 @@ static int nwl_dsi_parse_dt(struct nwl_dsi *dsi) } dsi->tx_esc_clk = clk; - dsi->mux = devm_mux_control_get(dsi->dev, NULL); - if (IS_ERR(dsi->mux)) { - ret = PTR_ERR(dsi->mux); - if (ret != -EPROBE_DEFER) - DRM_DEV_ERROR(dsi->dev, "Failed to get mux: %d\n", ret); - return ret; + /* The video_pll clock is optional */ + clk = devm_clk_get(dsi->dev, "video_pll"); + if (!IS_ERR(clk)) + dsi->pll_clk = clk; + + + if (dsi->pdata->mux_present) { + dsi->mux = devm_mux_control_get(dsi->dev, NULL); + if (IS_ERR(dsi->mux)) { + ret = PTR_ERR(dsi->mux); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dsi->dev, + "Failed to get mux: %d\n", ret); + return ret; + } } base = devm_platform_ioremap_resource(pdev, 0); @@ -1072,6 +1616,18 @@ static int nwl_dsi_parse_dt(struct nwl_dsi *dsi) return ret; } + /* For these three regs, we need a mapping to MIPI-DSI CSR */ + if (dsi->pdata->reg_tx_ulps || dsi->pdata->reg_pxl2dpi || + dsi->pdata->reg_cm) { + dsi->csr = syscon_regmap_lookup_by_phandle(np, "csr"); + if (IS_ERR(dsi->csr)) { + ret = PTR_ERR(dsi->csr); + dev_err(dsi->dev, + "Failed to get CSR regmap: %d\n", ret); + return ret; + } + } + dsi->irq = platform_get_irq(pdev, 0); if (dsi->irq < 0) { DRM_DEV_ERROR(dsi->dev, "Failed to get device IRQ: %d\n", @@ -1079,60 +1635,100 @@ static int nwl_dsi_parse_dt(struct nwl_dsi *dsi) return dsi->irq; } - dsi->rst_pclk = devm_reset_control_get_exclusive(dsi->dev, "pclk"); + dsi->rst_pclk = devm_reset_control_get_optional_exclusive(dsi->dev, + "pclk"); if (IS_ERR(dsi->rst_pclk)) { DRM_DEV_ERROR(dsi->dev, "Failed to get pclk reset: %ld\n", PTR_ERR(dsi->rst_pclk)); return PTR_ERR(dsi->rst_pclk); } - dsi->rst_byte = devm_reset_control_get_exclusive(dsi->dev, "byte"); + dsi->rst_byte = devm_reset_control_get_optional_exclusive(dsi->dev, + "byte"); if (IS_ERR(dsi->rst_byte)) { DRM_DEV_ERROR(dsi->dev, "Failed to get byte reset: %ld\n", PTR_ERR(dsi->rst_byte)); return PTR_ERR(dsi->rst_byte); } - dsi->rst_esc = devm_reset_control_get_exclusive(dsi->dev, "esc"); + dsi->rst_esc = devm_reset_control_get_optional_exclusive(dsi->dev, + "esc"); if (IS_ERR(dsi->rst_esc)) { DRM_DEV_ERROR(dsi->dev, "Failed to get esc reset: %ld\n", PTR_ERR(dsi->rst_esc)); return PTR_ERR(dsi->rst_esc); } - dsi->rst_dpi = devm_reset_control_get_exclusive(dsi->dev, "dpi"); + dsi->rst_dpi = devm_reset_control_get_optional_exclusive(dsi->dev, + "dpi"); if (IS_ERR(dsi->rst_dpi)) { DRM_DEV_ERROR(dsi->dev, "Failed to get dpi reset: %ld\n", PTR_ERR(dsi->rst_dpi)); return PTR_ERR(dsi->rst_dpi); } + + of_property_read_u32(np, "fsl,clock-drop-level", &dsi->clk_drop_lvl); + + INIT_LIST_HEAD(&dsi->valid_modes); + return 0; } static int nwl_dsi_select_input(struct nwl_dsi *dsi) { struct device_node *remote; - u32 use_dcss = 1; + u32 use_dcss_or_epdc = 1; int ret; - remote = of_graph_get_remote_node(dsi->dev->of_node, 0, - NWL_DSI_ENDPOINT_LCDIF); - if (remote) { - use_dcss = 0; - } else { + /* If there is no mux, nothing to do here */ + if (!dsi->pdata->mux_present) + return 0; + + if (dsi->pdata->use_lcdif_or_dcss) { remote = of_graph_get_remote_node(dsi->dev->of_node, 0, - NWL_DSI_ENDPOINT_DCSS); - if (!remote) { - DRM_DEV_ERROR(dsi->dev, - "No valid input endpoint found\n"); - return -EINVAL; + NWL_DSI_ENDPOINT_LCDIF); + if (remote) { + use_dcss_or_epdc = 0; + } else { + remote = of_graph_get_remote_node(dsi->dev->of_node, 0, + NWL_DSI_ENDPOINT_DCSS); + if (!remote) { + DRM_DEV_ERROR(dsi->dev, + "No valid input endpoint found\n"); + return -EINVAL; + } } + + DRM_DEV_INFO(dsi->dev, "Using %s as input source\n", + (use_dcss_or_epdc) ? "DCSS" : "LCDIF"); + } else if (dsi->pdata->use_dcnano_or_epdc) { + remote = of_graph_get_remote_node(dsi->dev->of_node, 0, + NWL_DSI_ENDPOINT_DCNANO); + if (remote) { + use_dcss_or_epdc = 0; + } else { + remote = of_graph_get_remote_node(dsi->dev->of_node, 0, + NWL_DSI_ENDPOINT_EPDC); + if (!remote) { + DRM_DEV_ERROR(dsi->dev, + "No valid input endpoint found\n"); + return -EINVAL; + } + } + + DRM_DEV_INFO(dsi->dev, "Using %s as input source\n", + (use_dcss_or_epdc) ? "EPDC" : "DCNANO"); + } else { + DRM_DEV_ERROR(dsi->dev, "No valid input endpoint found\n"); + return -EINVAL; } - DRM_DEV_INFO(dsi->dev, "Using %s as input source\n", - (use_dcss) ? "DCSS" : "LCDIF"); - ret = mux_control_try_select(dsi->mux, use_dcss); + ret = mux_control_try_select(dsi->mux, use_dcss_or_epdc); if (ret < 0) DRM_DEV_ERROR(dsi->dev, "Failed to select input: %d\n", ret); of_node_put(remote); + + if (of_device_is_compatible(dsi->dev->of_node, "fsl,imx8mq-nwl-dsi")) + dsi->use_dcss = use_dcss_or_epdc; + return ret; } @@ -1140,6 +1736,10 @@ static int nwl_dsi_deselect_input(struct nwl_dsi *dsi) { int ret; + /* If there is no mux, nothing to do here */ + if (!dsi->pdata->mux_present) + return 0; + ret = mux_control_deselect(dsi->mux); if (ret < 0) DRM_DEV_ERROR(dsi->dev, "Failed to deselect input: %d\n", ret); @@ -1147,12 +1747,213 @@ static int nwl_dsi_deselect_input(struct nwl_dsi *dsi) return ret; } +static int imx8_common_dsi_pclk_reset(struct nwl_dsi *dsi, bool reset) +{ + int ret = 0; + + if (dsi->rst_pclk) { + if (reset) + ret = reset_control_assert(dsi->rst_pclk); + else + ret = reset_control_deassert(dsi->rst_pclk); + } + + return ret; + +} + +static int imx8_common_dsi_mipi_reset(struct nwl_dsi *dsi, bool reset) +{ + int ret = 0; + + if (dsi->rst_esc) { + if (reset) + ret = reset_control_assert(dsi->rst_esc); + else + ret = reset_control_deassert(dsi->rst_esc); + } + + if (dsi->rst_byte) { + if (reset) + ret = reset_control_assert(dsi->rst_byte); + else + ret = reset_control_deassert(dsi->rst_byte); + } + + return ret; + +} + +static int imx8_common_dsi_dpi_reset(struct nwl_dsi *dsi, bool reset) +{ + int ret = 0; + + if (dsi->rst_dpi) { + if (reset) + ret = reset_control_assert(dsi->rst_dpi); + else + ret = reset_control_deassert(dsi->rst_dpi); + } + + return ret; + +} + +static int imx8q_dsi_pclk_reset(struct nwl_dsi *dsi, bool reset) +{ + struct imx_sc_ipc *handle; + u32 mipi_id, dc_id; + u8 ctrl; + bool shared_phy = dsi->pdata->shared_phy; + int ret = 0; + + ret = imx_scu_get_handle(&handle); + if (ret) { + DRM_DEV_ERROR(dsi->dev, + "Failed to get scu ipc handle (%d)\n", ret); + return ret; + } + + mipi_id = (dsi->instance)?MIPI_ID(1):MIPI_ID(0); + dc_id = (!shared_phy && dsi->instance)?DC_ID(1):DC_ID(0); + DRM_DEV_DEBUG_DRIVER(dsi->dev, + "Power %s PCLK MIPI:%u DC:%u\n", + (reset)?"OFF":"ON", mipi_id, dc_id); + + if (shared_phy) { + ret |= imx_sc_misc_set_control(handle, + mipi_id, IMX_SC_C_MODE, reset); + ret |= imx_sc_misc_set_control(handle, + mipi_id, IMX_SC_C_DUAL_MODE, reset); + ret |= imx_sc_misc_set_control(handle, + mipi_id, IMX_SC_C_PXL_LINK_SEL, reset); + } + + ctrl = (shared_phy && dsi->instance)?PXL_VLD(2):PXL_VLD(1); + ret |= imx_sc_misc_set_control(handle, dc_id, ctrl, !reset); + + ctrl = (shared_phy && dsi->instance)?SYNC_CTRL(1):SYNC_CTRL(0); + ret |= imx_sc_misc_set_control(handle, dc_id, ctrl, !reset); + + return ret; +} + +static int imx8q_dsi_mipi_reset(struct nwl_dsi *dsi, bool reset) +{ + struct imx_sc_ipc *handle; + u32 mipi_id; + int ret = 0; + + ret = imx_scu_get_handle(&handle); + if (ret) { + DRM_DEV_ERROR(dsi->dev, + "Failed to get scu ipc handle (%d)\n", ret); + return ret; + } + + mipi_id = (dsi->instance)?MIPI_ID(1):MIPI_ID(0); + DRM_DEV_DEBUG_DRIVER(dsi->dev, + "Power %s HOST MIPI:%u\n", + (reset)?"OFF":"ON", mipi_id); + + ret |= imx_sc_misc_set_control(handle, mipi_id, + IMX_SC_C_PHY_RESET, !reset); + ret |= imx_sc_misc_set_control(handle, mipi_id, + IMX_SC_C_MIPI_RESET, !reset); + + return ret; +} + +static int imx8q_dsi_dpi_reset(struct nwl_dsi *dsi, bool reset) +{ + struct imx_sc_ipc *handle; + u32 mipi_id; + int ret = 0; + + ret = imx_scu_get_handle(&handle); + if (ret) { + DRM_DEV_ERROR(dsi->dev, + "Failed to get scu ipc handle (%d)\n", ret); + return ret; + } + + mipi_id = (dsi->instance)?MIPI_ID(1):MIPI_ID(0); + DRM_DEV_DEBUG_DRIVER(dsi->dev, + "Power %s DPI MIPI:%u\n", + (reset)?"OFF":"ON", mipi_id); + + regmap_write(dsi->csr, dsi->pdata->reg_tx_ulps, 0); + regmap_write(dsi->csr, dsi->pdata->reg_pxl2dpi, NWL_DSI_DPI_24_BIT); + + ret |= imx_sc_misc_set_control(handle, mipi_id, + IMX_SC_C_DPI_RESET, !reset); + + return ret; +} + static const struct drm_bridge_timings nwl_dsi_timings = { .input_bus_flags = DRM_BUS_FLAG_DE_LOW, }; +static const struct nwl_dsi_platform_data imx8mq_dev = { + .pclk_reset = &imx8_common_dsi_pclk_reset, + .mipi_reset = &imx8_common_dsi_mipi_reset, + .dpi_reset = &imx8_common_dsi_dpi_reset, + .clks = NWL_DSI_CORE_CLK | NWL_DSI_LCDIF_CLK, + .mux_present = true, + .bit_hs_tx_timeout = BIT(29), + .bit_bta_timeout = BIT(31), + .use_lcdif_or_dcss = true, +}; + +static const struct nwl_dsi_platform_data imx8qm_dev = { + .pclk_reset = &imx8q_dsi_pclk_reset, + .mipi_reset = &imx8q_dsi_mipi_reset, + .dpi_reset = &imx8q_dsi_dpi_reset, + .clks = NWL_DSI_BYPASS_CLK | NWL_DSI_PIXEL_CLK, + .reg_tx_ulps = 0x00, + .reg_pxl2dpi = 0x04, + .max_instances = 2, + .tx_clk_rate = 18000000, + .rx_clk_rate = 72000000, + .shared_phy = false, + .bit_hs_tx_timeout = BIT(29), + .bit_bta_timeout = BIT(31), +}; + +static const struct nwl_dsi_platform_data imx8qx_dev = { + .pclk_reset = &imx8q_dsi_pclk_reset, + .mipi_reset = &imx8q_dsi_mipi_reset, + .dpi_reset = &imx8q_dsi_dpi_reset, + .clks = NWL_DSI_BYPASS_CLK | NWL_DSI_PIXEL_CLK, + .reg_tx_ulps = 0x30, + .reg_pxl2dpi = 0x40, + .max_instances = 2, + .tx_clk_rate = 18000000, + .rx_clk_rate = 72000000, + .shared_phy = true, + .bit_hs_tx_timeout = BIT(29), + .bit_bta_timeout = BIT(31), +}; + +static const struct nwl_dsi_platform_data imx8ulp_dev = { + .pclk_reset = &imx8_common_dsi_pclk_reset, + .mipi_reset = &imx8_common_dsi_mipi_reset, + .dpi_reset = &imx8_common_dsi_dpi_reset, + .clks = NWL_DSI_CORE_CLK, + .reg_cm = 0x8, + .mux_present = true, + .bit_hs_tx_timeout = BIT(31), + .bit_bta_timeout = BIT(29), + .use_dcnano_or_epdc = true, + .rx_clk_quirk = true, +}; + static const struct of_device_id nwl_dsi_dt_ids[] = { - { .compatible = "fsl,imx8mq-nwl-dsi", }, + { .compatible = "fsl,imx8mq-nwl-dsi", .data = &imx8mq_dev, }, + { .compatible = "fsl,imx8qm-nwl-dsi", .data = &imx8qm_dev, }, + { .compatible = "fsl,imx8qx-nwl-dsi", .data = &imx8qx_dev, }, + { .compatible = "fsl,imx8ulp-nwl-dsi", .data = &imx8ulp_dev, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, nwl_dsi_dt_ids); @@ -1163,18 +1964,110 @@ static const struct soc_device_attribute nwl_dsi_quirks_match[] = { { /* sentinel. */ }, }; +static int nwl_dsi_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); + + imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB101010_1X30; + + return 0; +} + +static const struct drm_encoder_helper_funcs nwl_dsi_encoder_helper_funcs = { + .atomic_check = nwl_dsi_encoder_atomic_check, +}; + +static int nwl_dsi_bind(struct device *dev, + struct device *master, + void *data) +{ + struct drm_device *drm = data; + uint32_t crtc_mask; + struct nwl_dsi *dsi = dev_get_drvdata(dev); + int ret = 0; + + DRM_DEV_DEBUG_DRIVER(dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0"); + + crtc_mask = drm_of_find_possible_crtcs(drm, dev->of_node); + /* + * If we failed to find the CRTC(s) which this encoder is + * supposed to be connected to, it's because the CRTC has + * not been registered yet. Defer probing, and hope that + * the required CRTC is added later. + */ + if (crtc_mask == 0) + return -EPROBE_DEFER; + + dsi->encoder.possible_crtcs = crtc_mask; + dsi->encoder.possible_clones = 0; + + drm_encoder_helper_add(&dsi->encoder, + &nwl_dsi_encoder_helper_funcs); + ret = drm_encoder_init(drm, + &dsi->encoder, + &nwl_dsi_encoder_funcs, + DRM_MODE_ENCODER_DSI, + NULL); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init DSI encoder (%d)\n", ret); + return ret; + } + + ret = drm_bridge_attach(&dsi->encoder, &dsi->bridge, NULL, 0); + if (ret) + drm_encoder_cleanup(&dsi->encoder); + + /* + * -ENODEV is returned when there is no node connected to us. Since + * it might be disabled because the device is not actually connected, + * just cleanup and return 0. + */ + if (ret == -ENODEV) + return 0; + + return ret; +} + +static void nwl_dsi_unbind(struct device *dev, + struct device *master, + void *data) +{ + struct nwl_dsi *dsi = dev_get_drvdata(dev); + + DRM_DEV_DEBUG_DRIVER(dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0"); + + if (dsi->encoder.dev) + drm_encoder_cleanup(&dsi->encoder); +} + +static const struct component_ops nwl_dsi_component_ops = { + .bind = nwl_dsi_bind, + .unbind = nwl_dsi_unbind, +}; + static int nwl_dsi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + const struct of_device_id *of_id = of_match_device(nwl_dsi_dt_ids, dev); const struct soc_device_attribute *attr; struct nwl_dsi *dsi; int ret; + if (!of_id || !of_id->data) + return -ENODEV; + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); if (!dsi) return -ENOMEM; dsi->dev = dev; + dsi->pdata = of_id->data; + + attr = soc_device_match(nwl_dsi_quirks_match); + if (attr) + dsi->quirks = (uintptr_t)attr->data; ret = nwl_dsi_parse_dt(dsi); if (ret) @@ -1196,10 +2089,6 @@ static int nwl_dsi_probe(struct platform_device *pdev) return ret; } - attr = soc_device_match(nwl_dsi_quirks_match); - if (attr) - dsi->quirks = (uintptr_t)attr->data; - dsi->bridge.driver_private = dsi; dsi->bridge.funcs = &nwl_dsi_bridge_funcs; dsi->bridge.of_node = dev->of_node; @@ -1216,13 +2105,31 @@ static int nwl_dsi_probe(struct platform_device *pdev) } drm_bridge_add(&dsi->bridge); - return 0; + + if (of_property_read_bool(dev->of_node, "use-disp-ss")) + ret = component_add(&pdev->dev, &nwl_dsi_component_ops); + + if (ret) { + pm_runtime_disable(dev); + drm_bridge_remove(&dsi->bridge); + mipi_dsi_host_unregister(&dsi->dsi_host); + } + + return ret; } static int nwl_dsi_remove(struct platform_device *pdev) { struct nwl_dsi *dsi = platform_get_drvdata(pdev); + struct mode_config *config; + struct list_head *pos, *tmp; + list_for_each_safe(pos, tmp, &dsi->valid_modes) { + config = list_entry(pos, struct mode_config, list); + list_del(pos); + devm_kfree(dsi->dev, config); + } + nwl_dsi_deselect_input(dsi); mipi_dsi_host_unregister(&dsi->dsi_host); drm_bridge_remove(&dsi->bridge); diff --git a/drivers/gpu/drm/bridge/nwl-dsi.h b/drivers/gpu/drm/bridge/nwl-dsi.h index a247a8a11c7c..d34fffec00c0 100644 --- a/drivers/gpu/drm/bridge/nwl-dsi.h +++ b/drivers/gpu/drm/bridge/nwl-dsi.h @@ -69,9 +69,7 @@ #define NWL_DSI_RX_FIFO_UDFLW BIT(6) #define NWL_DSI_RX_PKT_HDR_RCVD BIT(7) #define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD BIT(8) -#define NWL_DSI_BTA_TIMEOUT BIT(29) #define NWL_DSI_LP_RX_TIMEOUT BIT(30) -#define NWL_DSI_HS_TX_TIMEOUT BIT(31) #define NWL_DSI_IRQ_STATUS2 0x2a4 #define NWL_DSI_SINGLE_BIT_ECC_ERR BIT(0) @@ -88,9 +86,7 @@ #define NWL_DSI_RX_FIFO_UDFLW_MASK BIT(6) #define NWL_DSI_RX_PKT_HDR_RCVD_MASK BIT(7) #define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD_MASK BIT(8) -#define NWL_DSI_BTA_TIMEOUT_MASK BIT(29) #define NWL_DSI_LP_RX_TIMEOUT_MASK BIT(30) -#define NWL_DSI_HS_TX_TIMEOUT_MASK BIT(31) #define NWL_DSI_IRQ_MASK2 0x2ac #define NWL_DSI_SINGLE_BIT_ECC_ERR_MASK BIT(0) diff --git a/drivers/gpu/drm/bridge/nxp-seiko-43wvfig.c b/drivers/gpu/drm/bridge/nxp-seiko-43wvfig.c new file mode 100644 index 000000000000..f09b0b6920c8 --- /dev/null +++ b/drivers/gpu/drm/bridge/nxp-seiko-43wvfig.c @@ -0,0 +1,265 @@ +/* + * DRM driver for the legacy Freescale adapter card holding + * Seiko RA169Z20 43WVFIG LCD panel + * + * Copyright (C) 2018 NXP + * + * 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 <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/of_platform.h> + +struct seiko_adapter { + struct device *dev; + struct drm_panel *panel; + struct drm_bridge bridge; + struct drm_connector connector; + + u32 bpc; + u32 bus_format; +}; + +static enum drm_connector_status seiko_adapter_connector_detect( + struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static int seiko_adapter_connector_get_modes(struct drm_connector *connector) +{ + int num_modes; + + struct seiko_adapter *adap = container_of(connector, + struct seiko_adapter, + connector); + + num_modes = drm_panel_get_modes(adap->panel, connector); + + /* + * The panel will populate the connector display_info properties with + * fixed numbers, but we need to change them according to our + * configuration. + */ + connector->display_info.bpc = adap->bpc; + drm_display_info_set_bus_formats(&connector->display_info, + &adap->bus_format, 1); + + return num_modes; +} + +static const struct drm_connector_funcs seiko_adapter_connector_funcs = { + .detect = seiko_adapter_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_connector_helper_funcs + seiko_adapter_connector_helper_funcs = { + .get_modes = seiko_adapter_connector_get_modes, +}; + +static int seiko_adapter_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct seiko_adapter *adap = bridge->driver_private; + struct device *dev = adap->dev; + struct drm_encoder *encoder = bridge->encoder; + struct drm_device *drm; + int ret = 0; + + if (!encoder) { + DRM_DEV_ERROR(dev, "Parent encoder object not found\n"); + return -ENODEV; + } + + drm = encoder->dev; + + /* + * Create the connector for our panel + */ + + ret = drm_connector_init(drm, &adap->connector, + &seiko_adapter_connector_funcs, + DRM_MODE_CONNECTOR_DPI); + if (ret) { + DRM_DEV_ERROR(dev, "Failed to init drm connector: %d\n", ret); + return ret; + } + + drm_connector_helper_add(&adap->connector, + &seiko_adapter_connector_helper_funcs); + + adap->connector.dpms = DRM_MODE_DPMS_OFF; + drm_connector_attach_encoder(&adap->connector, encoder); + + return ret; +} + +static void seiko_adapter_bridge_detach(struct drm_bridge *bridge) +{ + struct seiko_adapter *adap = bridge->driver_private; + + drm_connector_cleanup(&adap->connector); +} + +static void seiko_adapter_bridge_enable(struct drm_bridge *bridge) +{ + struct seiko_adapter *adap = bridge->driver_private; + struct device *dev = adap->dev; + + if (drm_panel_prepare(adap->panel)) { + DRM_DEV_ERROR(dev, "Failed to prepare panel\n"); + return; + } + + if (drm_panel_enable(adap->panel)) { + DRM_DEV_ERROR(dev, "Failed to enable panel\n"); + drm_panel_unprepare(adap->panel); + } +} + +static void seiko_adapter_bridge_disable(struct drm_bridge *bridge) +{ + struct seiko_adapter *adap = bridge->driver_private; + struct device *dev = adap->dev; + + if (drm_panel_disable(adap->panel)) { + DRM_DEV_ERROR(dev, "failed to disable panel\n"); + return; + } + + if (drm_panel_unprepare(adap->panel)) + DRM_DEV_ERROR(dev, "failed to unprepare panel\n"); +} + +#define MAX_INPUT_FORMATS 1 +static u32 * +seiko_adapter_atomic_get_input_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) { + struct seiko_adapter *adap = bridge->driver_private; + u32 *input_fmts; + + *num_input_fmts = 0; + + input_fmts = kcalloc(MAX_INPUT_FORMATS, sizeof(*input_fmts), + GFP_KERNEL); + if (!input_fmts) + return NULL; + + input_fmts[0] = adap->bus_format; + *num_input_fmts = MAX_INPUT_FORMATS; + + return input_fmts; +} + +static const struct drm_bridge_funcs seiko_adapter_bridge_funcs = { + .enable = seiko_adapter_bridge_enable, + .disable = seiko_adapter_bridge_disable, + .attach = seiko_adapter_bridge_attach, + .detach = seiko_adapter_bridge_detach, + + .atomic_get_input_bus_fmts = seiko_adapter_atomic_get_input_bus_fmts, + + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, +}; + +static int seiko_adapter_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct seiko_adapter *adap; + u32 bus_mode; + int port, ret; + + adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL); + if (!adap) + return -ENOMEM; + + of_property_read_u32(dev->of_node, "bus_mode", &bus_mode); + if (bus_mode != 18 && bus_mode != 24) { + dev_err(dev, "Invalid bus_mode: %d\n", bus_mode); + return -EINVAL; + } + + switch (bus_mode) { + case 18: + adap->bpc = 6; + adap->bus_format = MEDIA_BUS_FMT_RGB666_1X18; + break; + case 24: + adap->bpc = 8; + adap->bus_format = MEDIA_BUS_FMT_RGB888_1X24; + break; + } + + for (port = 0; port < 2; port++) { + ret = drm_of_find_panel_or_bridge(dev->of_node, + port, 0, + &adap->panel, NULL); + if (!ret) + break; + } + + if (ret) { + dev_err(dev, "No panel found: %d\n", ret); + return ret; + } + + adap->dev = dev; + adap->bridge.driver_private = adap; + adap->bridge.funcs = &seiko_adapter_bridge_funcs; + adap->bridge.of_node = dev->of_node; + + drm_bridge_add(&adap->bridge); + + return 0; +} + +static int seiko_adapter_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id seiko_adapter_dt_ids[] = { + { .compatible = "nxp,seiko-43wvfig" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, seiko_adapter_dt_ids); + +static struct platform_driver seiko_adapter_driver = { + .probe = seiko_adapter_probe, + .remove = seiko_adapter_remove, + .driver = { + .of_match_table = seiko_adapter_dt_ids, + .name = "nxp-seiko-adapter", + }, +}; + +module_platform_driver(seiko_adapter_driver); + +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_DESCRIPTION("Seiko 43WVFIG adapter card driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/sec-dsim.c b/drivers/gpu/drm/bridge/sec-dsim.c new file mode 100644 index 000000000000..1eb34e755509 --- /dev/null +++ b/drivers/gpu/drm/bridge/sec-dsim.c @@ -0,0 +1,2087 @@ +/* + * Samsung MIPI DSIM Bridge + * + * Copyright 2018-2022 NXP + * + * 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 <asm/unaligned.h> +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/gcd.h> +#include <linux/log2.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/pm_runtime.h> +#include <drm/bridge/sec_mipi_dsim.h> +#include <drm/drm_vblank.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_encoder.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> +#include <video/videomode.h> +#include <video/mipi_display.h> + +/* dsim registers */ +#define DSIM_VERSION 0x00 +#define DSIM_STATUS 0x04 +#define DSIM_RGB_STATUS 0x08 +#define DSIM_SWRST 0x0c +#define DSIM_CLKCTRL 0x10 +#define DSIM_TIMEOUT 0x14 +#define DSIM_CONFIG 0x18 +#define DSIM_ESCMODE 0x1c +#define DSIM_MDRESOL 0x20 +#define DSIM_MVPORCH 0x24 +#define DSIM_MHPORCH 0x28 +#define DSIM_MSYNC 0x2c +#define DSIM_SDRESOL 0x30 +#define DSIM_INTSRC 0x34 +#define DSIM_INTMSK 0x38 + +/* packet */ +#define DSIM_PKTHDR 0x3c +#define DSIM_PAYLOAD 0x40 +#define DSIM_RXFIFO 0x44 +#define DSIM_FIFOTHLD 0x48 +#define DSIM_FIFOCTRL 0x4c +#define DSIM_MEMACCHR 0x50 +#define DSIM_MULTI_PKT 0x78 + +/* pll control */ +#define DSIM_PLLCTRL_1G 0x90 +#define DSIM_PLLCTRL 0x94 +#define DSIM_PLLCTRL1 0x98 +#define DSIM_PLLCTRL2 0x9c +#define DSIM_PLLTMR 0xa0 + +/* dphy */ +#define DSIM_PHYTIMING 0xb4 +#define DSIM_PHYTIMING1 0xb8 +#define DSIM_PHYTIMING2 0xbc + +/* reg bit manipulation */ +#define REG_MASK(e, s) (((1 << ((e) - (s) + 1)) - 1) << (s)) +#define REG_PUT(x, e, s) (((x) << (s)) & REG_MASK(e, s)) +#define REG_GET(x, e, s) (((x) & REG_MASK(e, s)) >> (s)) + +/* register bit fields */ +#define STATUS_PLLSTABLE BIT(31) +#define STATUS_SWRSTRLS BIT(20) +#define STATUS_TXREADYHSCLK BIT(10) +#define STATUS_ULPSCLK BIT(9) +#define STATUS_STOPSTATECLK BIT(8) +#define STATUS_GET_ULPSDAT(x) REG_GET(x, 7, 4) +#define STATUS_GET_STOPSTATEDAT(x) REG_GET(x, 3, 0) + +#define RGB_STATUS_CMDMODE_INSEL BIT(31) +#define RGB_STATUS_GET_RGBSTATE(x) REG_GET(x, 12, 0) + +#define CLKCTRL_TXREQUESTHSCLK BIT(31) +#define CLKCTRL_DPHY_SEL_1G BIT(29) +#define CLKCTRL_DPHY_SEL_1P5G (0x0 << 29) +#define CLKCTRL_ESCCLKEN BIT(28) +#define CLKCTRL_PLLBYPASS BIT(29) +#define CLKCTRL_BYTECLKSRC_DPHY_PLL REG_PUT(0, 26, 25) +#define CLKCTRL_BYTECLKEN BIT(24) +#define CLKCTRL_SET_LANEESCCLKEN(x) REG_PUT(x, 23, 19) +#define CLKCTRL_SET_ESCPRESCALER(x) REG_PUT(x, 15, 0) + +#define TIMEOUT_SET_BTAOUT(x) REG_PUT(x, 23, 16) +#define TIMEOUT_SET_LPDRTOUT(x) REG_PUT(x, 15, 0) + +#define CONFIG_NON_CONTINUOUS_CLOCK_LANE BIT(31) +#define CONFIG_CLKLANE_STOP_START BIT(30) +#define CONFIG_MFLUSH_VS BIT(29) +#define CONFIG_EOT_R03 BIT(28) +#define CONFIG_SYNCINFORM BIT(27) +#define CONFIG_BURSTMODE BIT(26) +#define CONFIG_VIDEOMODE BIT(25) +#define CONFIG_AUTOMODE BIT(24) +#define CONFIG_HSEDISABLEMODE BIT(23) +#define CONFIG_HFPDISABLEMODE BIT(22) +#define CONFIG_HBPDISABLEMODE BIT(21) +#define CONFIG_HSADISABLEMODE BIT(20) +#define CONFIG_SET_MAINVC(x) REG_PUT(x, 19, 18) +#define CONFIG_SET_SUBVC(x) REG_PUT(x, 17, 16) +#define CONFIG_SET_MAINPIXFORMAT(x) REG_PUT(x, 14, 12) +#define CONFIG_SET_SUBPIXFORMAT(x) REG_PUT(x, 10, 8) +#define CONFIG_SET_NUMOFDATLANE(x) REG_PUT(x, 6, 5) +#define CONFIG_SET_LANEEN(x) REG_PUT(x, 4, 0) + +#define ESCMODE_SET_STOPSTATE_CNT(X) REG_PUT(x, 31, 21) +#define ESCMODE_FORCESTOPSTATE BIT(20) +#define ESCMODE_FORCEBTA BIT(16) +#define ESCMODE_CMDLPDT BIT(7) +#define ESCMODE_TXLPDT BIT(6) +#define ESCMODE_TXTRIGGERRST BIT(5) + +#define MDRESOL_MAINSTANDBY BIT(31) +#define MDRESOL_SET_MAINVRESOL(x) REG_PUT(x, 27, 16) +#define MDRESOL_SET_MAINHRESOL(x) REG_PUT(x, 11, 0) + +#define MVPORCH_SET_CMDALLOW(x) REG_PUT(x, 31, 28) +#define MVPORCH_SET_STABLEVFP(x) REG_PUT(x, 26, 16) +#define MVPORCH_SET_MAINVBP(x) REG_PUT(x, 10, 0) + +#define MHPORCH_SET_MAINHFP(x) REG_PUT(x, 31, 16) +#define MHPORCH_SET_MAINHBP(x) REG_PUT(x, 15, 0) + +#define MSYNC_SET_MAINVSA(x) REG_PUT(x, 31, 22) +#define MSYNC_SET_MAINHSA(x) REG_PUT(x, 15, 0) + +#define INTSRC_PLLSTABLE BIT(31) +#define INTSRC_SWRSTRELEASE BIT(30) +#define INTSRC_SFRPLFIFOEMPTY BIT(29) +#define INTSRC_SFRPHFIFOEMPTY BIT(28) +#define INTSRC_FRAMEDONE BIT(24) +#define INTSRC_LPDRTOUT BIT(21) +#define INTSRC_TATOUT BIT(20) +#define INTSRC_RXDATDONE BIT(18) +#define INTSRC_RXTE BIT(17) +#define INTSRC_RXACK BIT(16) +#define INTSRC_MASK (INTSRC_PLLSTABLE | \ + INTSRC_SWRSTRELEASE | \ + INTSRC_SFRPLFIFOEMPTY | \ + INTSRC_SFRPHFIFOEMPTY | \ + INTSRC_FRAMEDONE | \ + INTSRC_LPDRTOUT | \ + INTSRC_TATOUT | \ + INTSRC_RXDATDONE | \ + INTSRC_RXTE | \ + INTSRC_RXACK) + +#define INTMSK_MSKPLLSTABLE BIT(31) +#define INTMSK_MSKSWRELEASE BIT(30) +#define INTMSK_MSKSFRPLFIFOEMPTY BIT(29) +#define INTMSK_MSKSFRPHFIFOEMPTY BIT(28) +#define INTMSK_MSKFRAMEDONE BIT(24) +#define INTMSK_MSKLPDRTOUT BIT(21) +#define INTMSK_MSKTATOUT BIT(20) +#define INTMSK_MSKRXDATDONE BIT(18) +#define INTMSK_MSKRXTE BIT(17) +#define INTMSK_MSKRXACK BIT(16) + +#define PKTHDR_SET_DATA1(x) REG_PUT(x, 23, 16) +#define PKTHDR_GET_DATA1(x) REG_GET(x, 23, 16) +#define PKTHDR_SET_DATA0(x) REG_PUT(x, 15, 8) +#define PKTHDR_GET_DATA0(x) REG_GET(x, 15, 8) +#define PKTHDR_GET_WC(x) REG_GET(x, 23, 8) +#define PKTHDR_SET_DI(x) REG_PUT(x, 7, 0) +#define PKTHDR_GET_DI(x) REG_GET(x, 7, 0) +#define PKTHDR_SET_DT(x) REG_PUT(x, 5, 0) +#define PKTHDR_GET_DT(x) REG_GET(x, 5, 0) +#define PKTHDR_SET_VC(x) REG_PUT(x, 7, 6) +#define PKTHDR_GET_VC(x) REG_GET(x, 7, 6) + +#define FIFOCTRL_FULLRX BIT(25) +#define FIFOCTRL_EMPTYRX BIT(24) +#define FIFOCTRL_FULLHSFR BIT(23) +#define FIFOCTRL_EMPTYHSFR BIT(22) +#define FIFOCTRL_FULLLSFR BIT(21) +#define FIFOCTRL_EMPTYLSFR BIT(20) +#define FIFOCTRL_FULLHMAIN BIT(11) +#define FIFOCTRL_EMPTYHMAIN BIT(10) +#define FIFOCTRL_FULLLMAIN BIT(9) +#define FIFOCTRL_EMPTYLMAIN BIT(8) +#define FIFOCTRL_NINITRX BIT(4) +#define FIFOCTRL_NINITSFR BIT(3) +#define FIFOCTRL_NINITI80 BIT(2) +#define FIFOCTRL_NINITSUB BIT(1) +#define FIFOCTRL_NINITMAIN BIT(0) + +#define PLLCTRL_DPDNSWAP_CLK BIT(25) +#define PLLCTRL_DPDNSWAP_DAT BIT(24) +#define PLLCTRL_PLLEN BIT(23) +#define PLLCTRL_SET_PMS(x) REG_PUT(x, 19, 1) + #define PLLCTRL_SET_P(x) REG_PUT(x, 18, 13) + #define PLLCTRL_SET_M(x) REG_PUT(x, 12, 3) + #define PLLCTRL_SET_S(x) REG_PUT(x, 2, 0) + +#define PHYTIMING_SET_M_TLPXCTL(x) REG_PUT(x, 15, 8) +#define PHYTIMING_SET_M_THSEXITCTL(x) REG_PUT(x, 7, 0) + +#define PHYTIMING1_SET_M_TCLKPRPRCTL(x) REG_PUT(x, 31, 24) +#define PHYTIMING1_SET_M_TCLKZEROCTL(x) REG_PUT(x, 23, 16) +#define PHYTIMING1_SET_M_TCLKPOSTCTL(x) REG_PUT(x, 15, 8) +#define PHYTIMING1_SET_M_TCLKTRAILCTL(x) REG_PUT(x, 7, 0) + +#define PHYTIMING2_SET_M_THSPRPRCTL(x) REG_PUT(x, 23, 16) +#define PHYTIMING2_SET_M_THSZEROCTL(x) REG_PUT(x, 15, 8) +#define PHYTIMING2_SET_M_THSTRAILCTL(x) REG_PUT(x, 7, 0) + +#define dsim_read(dsim, reg) readl(dsim->base + reg) +#define dsim_write(dsim, val, reg) writel(val, dsim->base + reg) + +#define MAX_MAIN_HRESOL 2047 +#define MAX_MAIN_VRESOL 2047 +#define MAX_SUB_HRESOL 1024 +#define MAX_SUB_VRESOL 1024 + +/* in KHZ */ +#define MAX_ESC_CLK_FREQ 20000 + +/* dsim all irqs index */ +#define PLLSTABLE 1 +#define SWRSTRELEASE 2 +#define SFRPLFIFOEMPTY 3 +#define SFRPHFIFOEMPTY 4 +#define SYNCOVERRIDE 5 +#define BUSTURNOVER 6 +#define FRAMEDONE 7 +#define LPDRTOUT 8 +#define TATOUT 9 +#define RXDATDONE 10 +#define RXTE 11 +#define RXACK 12 +#define ERRRXECC 13 +#define ERRRXCRC 14 +#define ERRESC3 15 +#define ERRESC2 16 +#define ERRESC1 17 +#define ERRESC0 18 +#define ERRSYNC3 19 +#define ERRSYNC2 20 +#define ERRSYNC1 21 +#define ERRSYNC0 22 +#define ERRCONTROL3 23 +#define ERRCONTROL2 24 +#define ERRCONTROL1 25 +#define ERRCONTROL0 26 + +#define MIPI_FIFO_TIMEOUT msecs_to_jiffies(250) + +#define MIPI_HFP_PKT_OVERHEAD 6 +#define MIPI_HBP_PKT_OVERHEAD 6 +#define MIPI_HSA_PKT_OVERHEAD 6 + +#define to_sec_mipi_dsim(dsi) container_of(dsi, struct sec_mipi_dsim, dsi_host) +#define conn_to_sec_mipi_dsim(conn) \ + container_of(conn, struct sec_mipi_dsim, connector) + +/* used for CEA standard modes */ +struct dsim_hblank_par { + char *name; /* drm display mode name */ + int vrefresh; + int hfp_wc; + int hbp_wc; + int hsa_wc; + int lanes; +}; + +struct dsim_pll_pms { + uint32_t bit_clk; /* kHz */ + uint32_t p; + uint32_t m; + uint32_t s; + uint32_t k; +}; + +struct sec_mipi_dsim { + struct mipi_dsi_host dsi_host; + struct drm_connector connector; + struct drm_encoder *encoder; + struct drm_bridge *bridge; + struct drm_bridge *next; + struct drm_panel *panel; + struct device *dev; + + void __iomem *base; + + /* kHz clocks */ + uint32_t pix_clk; + uint32_t bit_clk; + uint32_t pref_clk; /* phy ref clock rate in KHz */ + + unsigned int lanes; + unsigned int channel; /* virtual channel */ + enum mipi_dsi_pixel_format format; + unsigned long mode_flags; + const struct dsim_hblank_par *hpar; + unsigned int pms; + unsigned int p; + unsigned int m; + unsigned int s; + unsigned long long lp_data_rate; + unsigned long long hs_data_rate; + struct videomode vmode; + bool enabled; + + struct completion pll_stable; + struct completion ph_tx_done; + struct completion pl_tx_done; + struct completion rx_done; + const struct sec_mipi_dsim_plat_data *pdata; +}; + +#define DSIM_HBLANK_PARAM(nm, vf, hfp, hbp, hsa, num) \ + .name = (nm), \ + .vrefresh = (vf), \ + .hfp_wc = (hfp), \ + .hbp_wc = (hbp), \ + .hsa_wc = (hsa), \ + .lanes = (num) + +#define DSIM_PLL_PMS(c, pp, mm, ss) \ + .bit_clk = (c), \ + .p = (pp), \ + .m = (mm), \ + .s = (ss) + +static const struct dsim_hblank_par hblank_4lanes[] = { + /* { 88, 148, 44 } */ + { DSIM_HBLANK_PARAM("1920x1080", 60, 60, 105, 27, 4), }, + /* { 528, 148, 44 } */ + { DSIM_HBLANK_PARAM("1920x1080", 50, 390, 105, 27, 4), }, + /* { 88, 148, 44 } */ + { DSIM_HBLANK_PARAM("1920x1080", 30, 60, 105, 27, 4), }, + /* { 110, 220, 40 } */ + { DSIM_HBLANK_PARAM("1280x720" , 60, 78, 159, 24, 4), }, + /* { 440, 220, 40 } */ + { DSIM_HBLANK_PARAM("1280x720" , 50, 324, 159, 24, 4), }, + /* { 16, 60, 62 } */ + { DSIM_HBLANK_PARAM("720x480" , 60, 6, 39, 40, 4), }, + /* { 12, 68, 64 } */ + { DSIM_HBLANK_PARAM("720x576" , 50, 3, 45, 42, 4), }, + /* { 16, 48, 96 } */ + { DSIM_HBLANK_PARAM("640x480" , 60, 6, 30, 66, 4), }, +}; + +static const struct dsim_hblank_par hblank_2lanes[] = { + /* { 88, 148, 44 } */ + { DSIM_HBLANK_PARAM("1920x1080", 30, 114, 210, 60, 2), }, + /* { 110, 220, 40 } */ + { DSIM_HBLANK_PARAM("1280x720" , 60, 159, 320, 40, 2), }, + /* { 440, 220, 40 } */ + { DSIM_HBLANK_PARAM("1280x720" , 50, 654, 320, 40, 2), }, + /* { 16, 60, 62 } */ + { DSIM_HBLANK_PARAM("720x480" , 60, 16, 66, 88, 2), }, + /* { 12, 68, 64 } */ + { DSIM_HBLANK_PARAM("720x576" , 50, 12, 96, 72, 2), }, + /* { 16, 48, 96 } */ + { DSIM_HBLANK_PARAM("640x480" , 60, 18, 66, 138, 2), }, +}; + +static const struct dsim_hblank_par *sec_mipi_dsim_get_hblank_par(const char *name, + int vrefresh, + int lanes) +{ + int i, size; + const struct dsim_hblank_par *hpar, *hblank; + + if (unlikely(!name)) + return NULL; + + switch (lanes) { + case 2: + hblank = hblank_2lanes; + size = ARRAY_SIZE(hblank_2lanes); + break; + case 4: + hblank = hblank_4lanes; + size = ARRAY_SIZE(hblank_4lanes); + break; + default: + pr_err("No hblank data for mode %s with %d lanes\n", + name, lanes); + return NULL; + } + + for (i = 0; i < size; i++) { + hpar = &hblank[i]; + + if (!strcmp(name, hpar->name)) { + if (vrefresh != hpar->vrefresh) + continue; + + /* found */ + return hpar; + } + } + + return NULL; +} + +static int sec_mipi_dsim_set_pref_rate(struct sec_mipi_dsim *dsim) +{ + int ret; + uint32_t rate; + const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata; + const struct sec_mipi_dsim_pll *dpll = pdata->dphy_pll; + const struct sec_mipi_dsim_range *fin_range = &dpll->fin; + + ret = pdata->determine_pll_ref_rate(&rate, fin_range->min, fin_range->max); + if (ret) + return ret; + + dsim->pref_clk = rate; + + return 0; +} + +static void sec_mipi_dsim_irq_init(struct sec_mipi_dsim *dsim); + +/* For now, dsim only support one device attached */ +static int sec_mipi_dsim_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *dsi) +{ + struct sec_mipi_dsim *dsim = to_sec_mipi_dsim(host); + const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata; + struct device *dev = dsim->dev; + struct drm_panel *panel; + + if (!dsi->lanes || dsi->lanes > pdata->max_data_lanes) { + dev_err(dev, "invalid data lanes number\n"); + return -EINVAL; + } + + if (dsim->channel) + return -EINVAL; + + if ((dsi->mode_flags & MIPI_DSI_MODE_VIDEO) && + !((dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) || + (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE))) { + dev_err(dev, "unsupported dsi mode\n"); + return -EINVAL; + } + + if (dsi->format != MIPI_DSI_FMT_RGB888 && + dsi->format != MIPI_DSI_FMT_RGB565 && + dsi->format != MIPI_DSI_FMT_RGB666 && + dsi->format != MIPI_DSI_FMT_RGB666_PACKED) { + dev_err(dev, "unsupported pixel format: %#x\n", dsi->format); + return -EINVAL; + } + + if (!dsim->next) { + /* 'dsi' must be panel device */ + panel = of_drm_find_panel(dsi->dev.of_node); + + if (!panel) { + dev_err(dev, "refuse unknown dsi device attach\n"); + WARN_ON(!panel); + return -ENODEV; + } + + /* Don't support multiple panels */ + if (dsim->panel && panel && dsim->panel != panel) { + dev_err(dev, "don't support multiple panels\n"); + return -EBUSY; + } + + dsim->panel = panel; + } + + /* TODO: DSIM 3 lanes has some display issue, so + * avoid 3 lanes enable, and force data lanes to + * be 2. + */ + if (dsi->lanes == 3) + dsi->lanes = 2; + + dsim->lanes = dsi->lanes; + dsim->channel = dsi->channel; + dsim->format = dsi->format; + dsim->mode_flags = dsi->mode_flags; + + /* TODO: support later */ +#if 0 + if (dsim->connector.dev) + drm_helper_hpd_irq_event(dsim->connector.dev); +#endif + + return 0; +} + +static int sec_mipi_dsim_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *dsi) +{ + struct sec_mipi_dsim *dsim = to_sec_mipi_dsim(host); + + if (WARN_ON(!dsim->next && !dsim->panel)) + return -ENODEV; + + /* clear the saved dsi parameters */ + dsim->lanes = 0; + dsim->channel = 0; + dsim->format = 0; + dsim->mode_flags = 0; + + /* detached panel should be NULL */ + dsim->panel = NULL; + + return 0; +} + +static void sec_mipi_dsim_config_cmd_lpm(struct sec_mipi_dsim *dsim, + bool enable) +{ + uint32_t escmode; + + escmode = dsim_read(dsim, DSIM_ESCMODE); + + if (enable) + escmode |= ESCMODE_CMDLPDT; + else + escmode &= ~ESCMODE_CMDLPDT; + + dsim_write(dsim, escmode, DSIM_ESCMODE); +} + +static void sec_mipi_dsim_write_pl_to_sfr_fifo(struct sec_mipi_dsim *dsim, + const void *payload, + size_t length) +{ + uint32_t pl_data; + + if (!length) + return; + + while (length >= 4) { + pl_data = get_unaligned_le32(payload); + dsim_write(dsim, pl_data, DSIM_PAYLOAD); + payload += 4; + length -= 4; + } + + pl_data = 0; + switch (length) { + case 3: + pl_data |= ((u8 *)payload)[2] << 16; + fallthrough; + case 2: + pl_data |= ((u8 *)payload)[1] << 8; + fallthrough; + case 1: + pl_data |= ((u8 *)payload)[0]; + dsim_write(dsim, pl_data, DSIM_PAYLOAD); + break; + } +} + +static void sec_mipi_dsim_write_ph_to_sfr_fifo(struct sec_mipi_dsim *dsim, + void *header, + bool use_lpm) +{ + uint32_t pkthdr; + + pkthdr = PKTHDR_SET_DATA1(((u8 *)header)[2]) | /* WC MSB */ + PKTHDR_SET_DATA0(((u8 *)header)[1]) | /* WC LSB */ + PKTHDR_SET_DI(((u8 *)header)[0]); /* Data ID */ + + dsim_write(dsim, pkthdr, DSIM_PKTHDR); +} + +static int sec_mipi_dsim_read_pl_from_sfr_fifo(struct sec_mipi_dsim *dsim, + void *payload, + size_t length) +{ + uint8_t data_type; + uint16_t word_count = 0; + uint32_t fifoctrl, ph, pl; + + fifoctrl = dsim_read(dsim, DSIM_FIFOCTRL); + + if (WARN_ON(fifoctrl & FIFOCTRL_EMPTYRX)) + return -EINVAL; + + ph = dsim_read(dsim, DSIM_RXFIFO); + data_type = PKTHDR_GET_DT(ph); + switch (data_type) { + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: + dev_err(dsim->dev, "peripheral report error: (0-7)%x, (8-15)%x\n", + PKTHDR_GET_DATA0(ph), PKTHDR_GET_DATA1(ph)); + return -EPROTO; + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE: + if (!WARN_ON(length < 2)) { + ((u8 *)payload)[1] = PKTHDR_GET_DATA1(ph); + word_count++; + } + fallthrough; + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE: + ((u8 *)payload)[0] = PKTHDR_GET_DATA0(ph); + word_count++; + length = word_count; + break; + case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE: + case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE: + word_count = PKTHDR_GET_WC(ph); + if (word_count > length) { + dev_err(dsim->dev, "invalid receive buffer length\n"); + return -EINVAL; + } + + length = word_count; + + while (word_count >= 4) { + pl = dsim_read(dsim, DSIM_RXFIFO); + ((u8 *)payload)[0] = pl & 0xff; + ((u8 *)payload)[1] = (pl >> 8) & 0xff; + ((u8 *)payload)[2] = (pl >> 16) & 0xff; + ((u8 *)payload)[3] = (pl >> 24) & 0xff; + payload += 4; + word_count -= 4; + } + + if (word_count > 0) { + pl = dsim_read(dsim, DSIM_RXFIFO); + + switch (word_count) { + case 3: + ((u8 *)payload)[2] = (pl >> 16) & 0xff; + fallthrough; + case 2: + ((u8 *)payload)[1] = (pl >> 8) & 0xff; + fallthrough; + case 1: + ((u8 *)payload)[0] = pl & 0xff; + break; + } + } + + break; + default: + return -EINVAL; + } + + return length; +} + +static ssize_t sec_mipi_dsim_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + int ret; + ssize_t bytes = 0; + bool use_lpm; + struct mipi_dsi_packet packet; + struct sec_mipi_dsim *dsim = to_sec_mipi_dsim(host); + + if ((msg->rx_buf && !msg->rx_len) || (msg->rx_len && !msg->rx_buf)) + return -EINVAL; + + ret = mipi_dsi_create_packet(&packet, msg); + if (ret) { + dev_err(dsim->dev, "failed to create dsi packet: %d\n", ret); + return ret; + } + + /* need to read data from peripheral */ + if (unlikely(msg->rx_buf)) + reinit_completion(&dsim->rx_done); + + /* config LPM for CMD TX */ + use_lpm = msg->flags & MIPI_DSI_MSG_USE_LPM ? true : false; + sec_mipi_dsim_config_cmd_lpm(dsim, use_lpm); + + if (packet.payload_length) { /* Long Packet case */ + reinit_completion(&dsim->pl_tx_done); + + /* write packet payload */ + sec_mipi_dsim_write_pl_to_sfr_fifo(dsim, + packet.payload, + packet.payload_length); + + /* write packet header */ + sec_mipi_dsim_write_ph_to_sfr_fifo(dsim, + packet.header, + use_lpm); + + ret = wait_for_completion_timeout(&dsim->ph_tx_done, + MIPI_FIFO_TIMEOUT); + if (!ret) { + dev_err(dsim->dev, "wait payload tx done time out\n"); + return -EBUSY; + } + } else { + reinit_completion(&dsim->ph_tx_done); + + /* write packet header */ + sec_mipi_dsim_write_ph_to_sfr_fifo(dsim, + packet.header, + use_lpm); + + ret = wait_for_completion_timeout(&dsim->ph_tx_done, + MIPI_FIFO_TIMEOUT); + if (!ret) { + dev_err(dsim->dev, "wait pkthdr tx done time out\n"); + return -EBUSY; + } + } + + /* read packet payload */ + if (unlikely(msg->rx_buf)) { + ret = wait_for_completion_timeout(&dsim->rx_done, + MIPI_FIFO_TIMEOUT); + if (!ret) { + dev_err(dsim->dev, "wait rx done time out\n"); + return -EBUSY; + } + + ret = sec_mipi_dsim_read_pl_from_sfr_fifo(dsim, + msg->rx_buf, + msg->rx_len); + if (ret < 0) + return ret; + + bytes = msg->rx_len; + } else { + bytes = packet.size; + } + + return bytes; +} + +static const struct mipi_dsi_host_ops sec_mipi_dsim_host_ops = { + .attach = sec_mipi_dsim_host_attach, + .detach = sec_mipi_dsim_host_detach, + .transfer = sec_mipi_dsim_host_transfer, +}; + +static int sec_mipi_dsim_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + int ret; + bool attach_bridge = false; + struct sec_mipi_dsim *dsim = bridge->driver_private; + struct device *dev = dsim->dev; + struct device_node *np = dev->of_node; + struct device_node *endpoint, *remote = NULL; + struct drm_bridge *next = ERR_PTR(-ENODEV); + struct drm_encoder *encoder = dsim->encoder; + + /* TODO: All bridges and planes should have already been added */ + + /* A panel has been found, ignor other dsi devices */ + if (dsim->panel) + return 0; + + /* find next bridge */ + endpoint = of_graph_get_next_endpoint(np, NULL); + /* At least one endpoint should be existed */ + if (!endpoint) + return -ENODEV; + + while (endpoint) { + /* check the endpoint can attach bridge or not */ + attach_bridge = of_property_read_bool(endpoint, "attach-bridge"); + if (!attach_bridge) { + endpoint = of_graph_get_next_endpoint(np, endpoint); + continue; + } + + remote = of_graph_get_remote_port_parent(endpoint); + + if (!remote || !of_device_is_available(remote)) { + of_node_put(remote); + endpoint = of_graph_get_next_endpoint(np, endpoint); + continue; + } + + next = of_drm_find_bridge(remote); + if (next) { + /* Found */ + of_node_put(endpoint); + break; + } + + endpoint = of_graph_get_next_endpoint(np, endpoint); + } + + /* No workable bridge exists */ + if (IS_ERR(next)) + return PTR_ERR(next); + + /* For the panel driver loading is after dsim bridge, + * defer bridge binding to wait for panel driver ready. + * The disadvantage of probe defer is endless probing + * in some cases. + */ + if (!next) + return -EPROBE_DEFER; + + /* duplicate bridges or next bridge exists */ + WARN_ON(bridge == next || drm_bridge_get_next_bridge(bridge) || dsim->next); + + dsim->next = next; + next->encoder = encoder; + ret = drm_bridge_attach(encoder, next, bridge, flags); + if (ret) { + dev_err(dev, "Unable to attach bridge %s: %d\n", + remote->name, ret); + dsim->next = NULL; + return ret; + } + + return 0; +} + +static int sec_mipi_dsim_config_pll(struct sec_mipi_dsim *dsim) +{ + int ret; + uint32_t pllctrl = 0, status, data_lanes_en, stop; + + dsim_write(dsim, 0x8000, DSIM_PLLTMR); + + /* TODO: config dp/dn swap if requires */ + + pllctrl |= PLLCTRL_SET_PMS(dsim->pms) | PLLCTRL_PLLEN; + dsim_write(dsim, pllctrl, DSIM_PLLCTRL); + + ret = wait_for_completion_timeout(&dsim->pll_stable, HZ / 10); + if (!ret) { + dev_err(dsim->dev, "wait for pll stable time out\n"); + return -EBUSY; + } + + /* wait for clk & data lanes to go to stop state */ + mdelay(1); + + data_lanes_en = (0x1 << dsim->lanes) - 1; + status = dsim_read(dsim, DSIM_STATUS); + if (!(status & STATUS_STOPSTATECLK)) { + dev_err(dsim->dev, "clock is not in stop state\n"); + return -EBUSY; + } + + stop = STATUS_GET_STOPSTATEDAT(status); + if ((stop & data_lanes_en) != data_lanes_en) { + dev_err(dsim->dev, + "one or more data lanes is not in stop state\n"); + return -EBUSY; + } + + return 0; +} + +static void sec_mipi_dsim_set_main_mode(struct sec_mipi_dsim *dsim) +{ + uint32_t bpp, hfp_wc, hbp_wc, hsa_wc, wc; + uint32_t mdresol = 0, mvporch = 0, mhporch = 0, msync = 0; + struct videomode *vmode = &dsim->vmode; + + mdresol |= MDRESOL_SET_MAINVRESOL(vmode->vactive) | + MDRESOL_SET_MAINHRESOL(vmode->hactive); + dsim_write(dsim, mdresol, DSIM_MDRESOL); + + mvporch |= MVPORCH_SET_MAINVBP(vmode->vback_porch) | + MVPORCH_SET_STABLEVFP(vmode->vfront_porch) | + MVPORCH_SET_CMDALLOW(0x0); + dsim_write(dsim, mvporch, DSIM_MVPORCH); + + bpp = mipi_dsi_pixel_format_to_bpp(dsim->format); + + /* calculate hfp & hbp word counts */ + if (!dsim->hpar) { + wc = DIV_ROUND_UP(vmode->hfront_porch * (bpp >> 3), + dsim->lanes); + hfp_wc = wc > MIPI_HFP_PKT_OVERHEAD ? + wc - MIPI_HFP_PKT_OVERHEAD : vmode->hfront_porch; + wc = DIV_ROUND_UP(vmode->hback_porch * (bpp >> 3), + dsim->lanes); + hbp_wc = wc > MIPI_HBP_PKT_OVERHEAD ? + wc - MIPI_HBP_PKT_OVERHEAD : vmode->hback_porch; + } else { + hfp_wc = dsim->hpar->hfp_wc; + hbp_wc = dsim->hpar->hbp_wc; + } + + mhporch |= MHPORCH_SET_MAINHFP(hfp_wc) | + MHPORCH_SET_MAINHBP(hbp_wc); + + dsim_write(dsim, mhporch, DSIM_MHPORCH); + + /* calculate hsa word counts */ + if (!dsim->hpar) { + wc = DIV_ROUND_UP(vmode->hsync_len * (bpp >> 3), + dsim->lanes); + hsa_wc = wc > MIPI_HSA_PKT_OVERHEAD ? + wc - MIPI_HSA_PKT_OVERHEAD : vmode->hsync_len; + } else + hsa_wc = dsim->hpar->hsa_wc; + + msync |= MSYNC_SET_MAINVSA(vmode->vsync_len) | + MSYNC_SET_MAINHSA(hsa_wc); + + dsim_write(dsim, msync, DSIM_MSYNC); +} + +static void sec_mipi_dsim_config_dpi(struct sec_mipi_dsim *dsim) +{ + uint32_t config = 0, rgb_status = 0, data_lanes_en; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO) + rgb_status &= ~RGB_STATUS_CMDMODE_INSEL; + else + rgb_status |= RGB_STATUS_CMDMODE_INSEL; + + dsim_write(dsim, rgb_status, DSIM_RGB_STATUS); + + if (dsim->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) { + config |= CONFIG_NON_CONTINUOUS_CLOCK_LANE; + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO) + config |= CONFIG_CLKLANE_STOP_START; + } + + if (dsim->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH) + config |= CONFIG_MFLUSH_VS; + + /* disable EoT packets in HS mode */ + if (dsim->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET) + config |= CONFIG_EOT_R03; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO) { + config |= CONFIG_VIDEOMODE; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) + config |= CONFIG_BURSTMODE; + + else if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + config |= CONFIG_SYNCINFORM; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_AUTO_VERT) + config |= CONFIG_AUTOMODE; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_HSE) + config |= CONFIG_HSEDISABLEMODE; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HFP) + config |= CONFIG_HFPDISABLEMODE; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HBP) + config |= CONFIG_HBPDISABLEMODE; + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HSA) + config |= CONFIG_HSADISABLEMODE; + } + + config |= CONFIG_SET_MAINVC(dsim->channel); + + switch (dsim->format) { + case MIPI_DSI_FMT_RGB565: + config |= dsim->mode_flags & MIPI_DSI_MODE_VIDEO ? + CONFIG_SET_MAINPIXFORMAT(0x4) : + CONFIG_SET_MAINPIXFORMAT(0x3); + break; + case MIPI_DSI_FMT_RGB666_PACKED: + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO) + config |= CONFIG_SET_MAINPIXFORMAT(0x5); + break; + case MIPI_DSI_FMT_RGB666: + config |= CONFIG_SET_MAINPIXFORMAT(0x6); + break; + case MIPI_DSI_FMT_RGB888: + config |= CONFIG_SET_MAINPIXFORMAT(0x7); + break; + default: + config |= CONFIG_SET_MAINPIXFORMAT(0x7); + break; + } + + /* config data lanes number and enable lanes */ + data_lanes_en = (0x1 << dsim->lanes) - 1; + config |= CONFIG_SET_NUMOFDATLANE(dsim->lanes - 1); + config |= CONFIG_SET_LANEEN(0x1 | data_lanes_en << 1); + + dsim_write(dsim, config, DSIM_CONFIG); +} + +static void sec_mipi_dsim_config_dphy(struct sec_mipi_dsim *dsim) +{ + struct sec_mipi_dsim_dphy_timing key = { 0 }; + const struct sec_mipi_dsim_dphy_timing *match = NULL; + const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata; + uint32_t phytiming = 0, phytiming1 = 0, phytiming2 = 0, timeout = 0; + uint32_t hactive, vactive; + struct videomode *vmode = &dsim->vmode; + struct drm_display_mode mode; + + key.bit_clk = DIV_ROUND_CLOSEST_ULL(dsim->bit_clk, 1000); + + /* '1280x720@60Hz' mode with 2 data lanes + * requires special fine tuning for DPHY + * TIMING config according to the tests. + */ + if (dsim->lanes == 2) { + hactive = vmode->hactive; + vactive = vmode->vactive; + + if (hactive == 1280 && vactive == 720) { + memset(&mode, 0x0, sizeof(mode)); + drm_display_mode_from_videomode(vmode, &mode); + + if (drm_mode_vrefresh(&mode) == 60) + key.bit_clk >>= 1; + } + } + + match = bsearch(&key, pdata->dphy_timing, pdata->num_dphy_timing, + sizeof(struct sec_mipi_dsim_dphy_timing), + pdata->dphy_timing_cmp); + if (WARN_ON(!match)) + return; + + phytiming |= PHYTIMING_SET_M_TLPXCTL(match->lpx) | + PHYTIMING_SET_M_THSEXITCTL(match->hs_exit); + dsim_write(dsim, phytiming, DSIM_PHYTIMING); + + phytiming1 |= PHYTIMING1_SET_M_TCLKPRPRCTL(match->clk_prepare) | + PHYTIMING1_SET_M_TCLKZEROCTL(match->clk_zero) | + PHYTIMING1_SET_M_TCLKPOSTCTL(match->clk_post) | + PHYTIMING1_SET_M_TCLKTRAILCTL(match->clk_trail); + dsim_write(dsim, phytiming1, DSIM_PHYTIMING1); + + phytiming2 |= PHYTIMING2_SET_M_THSPRPRCTL(match->hs_prepare) | + PHYTIMING2_SET_M_THSZEROCTL(match->hs_zero) | + PHYTIMING2_SET_M_THSTRAILCTL(match->hs_trail); + dsim_write(dsim, phytiming2, DSIM_PHYTIMING2); + + timeout |= TIMEOUT_SET_BTAOUT(0xff) | + TIMEOUT_SET_LPDRTOUT(0xff); + dsim_write(dsim, timeout, DSIM_TIMEOUT); +} + +static void sec_mipi_dsim_init_fifo_pointers(struct sec_mipi_dsim *dsim) +{ + uint32_t fifoctrl, fifo_ptrs; + + fifoctrl = dsim_read(dsim, DSIM_FIFOCTRL); + + fifo_ptrs = FIFOCTRL_NINITRX | + FIFOCTRL_NINITSFR | + FIFOCTRL_NINITI80 | + FIFOCTRL_NINITSUB | + FIFOCTRL_NINITMAIN; + + fifoctrl &= ~fifo_ptrs; + dsim_write(dsim, fifoctrl, DSIM_FIFOCTRL); + udelay(500); + + fifoctrl |= fifo_ptrs; + dsim_write(dsim, fifoctrl, DSIM_FIFOCTRL); + udelay(500); +} + +static void sec_mipi_dsim_config_clkctrl(struct sec_mipi_dsim *dsim) +{ + uint32_t clkctrl = 0, data_lanes_en; + uint32_t byte_clk, esc_prescaler; + + clkctrl |= CLKCTRL_TXREQUESTHSCLK; + + /* using 1.5Gbps PHY */ + clkctrl |= CLKCTRL_DPHY_SEL_1P5G; + + clkctrl |= CLKCTRL_ESCCLKEN; + + clkctrl &= ~CLKCTRL_PLLBYPASS; + + clkctrl |= CLKCTRL_BYTECLKSRC_DPHY_PLL; + + clkctrl |= CLKCTRL_BYTECLKEN; + + data_lanes_en = (0x1 << dsim->lanes) - 1; + clkctrl |= CLKCTRL_SET_LANEESCCLKEN(0x1 | data_lanes_en << 1); + + /* calculate esc prescaler from byte clock: + * EscClk = ByteClk / EscPrescaler; + */ + byte_clk = dsim->bit_clk >> 3; + esc_prescaler = DIV_ROUND_UP(byte_clk, MAX_ESC_CLK_FREQ); + clkctrl |= CLKCTRL_SET_ESCPRESCALER(esc_prescaler); + + dsim_write(dsim, clkctrl, DSIM_CLKCTRL); +} + +static void sec_mipi_dsim_set_standby(struct sec_mipi_dsim *dsim, + bool standby) +{ + uint32_t mdresol = 0; + + mdresol = dsim_read(dsim, DSIM_MDRESOL); + + if (standby) + mdresol |= MDRESOL_MAINSTANDBY; + else + mdresol &= ~MDRESOL_MAINSTANDBY; + + dsim_write(dsim, mdresol, DSIM_MDRESOL); +} + +struct dsim_pll_pms *sec_mipi_dsim_calc_pmsk(struct sec_mipi_dsim *dsim) +{ + uint32_t p, m, s; + uint32_t best_p = 0, best_m = 0, best_s = 0; + uint32_t fin, fout; + uint32_t s_pow_2, raw_s; + uint64_t mfin, pfvco, pfout, psfout; + uint32_t delta, best_delta = ~0U; + struct dsim_pll_pms *pll_pms; + struct device *dev = dsim->dev; + const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata; + struct sec_mipi_dsim_pll dpll = *pdata->dphy_pll; + struct sec_mipi_dsim_range *prange = &dpll.p; + struct sec_mipi_dsim_range *mrange = &dpll.m; + struct sec_mipi_dsim_range *srange = &dpll.s; + struct sec_mipi_dsim_range *krange = &dpll.k; + struct sec_mipi_dsim_range *fvco_range = &dpll.fvco; + struct sec_mipi_dsim_range *fpref_range = &dpll.fpref; + struct sec_mipi_dsim_range pr_new = *prange; + struct sec_mipi_dsim_range sr_new = *srange; + + pll_pms = devm_kzalloc(dev, sizeof(*pll_pms), GFP_KERNEL); + if (!pll_pms) { + dev_err(dev, "Unable to allocate 'pll_pms'\n"); + return ERR_PTR(-ENOMEM); + } + + fout = dsim->bit_clk; + fin = dsim->pref_clk; + + /* TODO: ignore 'k' for PMS calculation, + * only use 'p', 'm' and 's' to generate + * the requested PLL output clock. + */ + krange->min = 0; + krange->max = 0; + + /* narrow 'p' range via 'Fpref' limitation: + * Fpref : [2MHz ~ 30MHz] (Fpref = Fin / p) + */ + prange->min = max(prange->min, DIV_ROUND_UP(fin, fpref_range->max)); + prange->max = min(prange->max, fin / fpref_range->min); + + /* narrow 'm' range via 'Fvco' limitation: + * Fvco: [1050MHz ~ 2100MHz] (Fvco = ((m + k / 65536) * Fin) / p) + * So, m = Fvco * p / Fin and Fvco > Fin; + */ + pfvco = (uint64_t)fvco_range->min * prange->min; + mrange->min = max_t(uint32_t, mrange->min, + DIV_ROUND_UP_ULL(pfvco, fin)); + pfvco = (uint64_t)fvco_range->max * prange->max; + mrange->max = min_t(uint32_t, mrange->max, + DIV_ROUND_UP_ULL(pfvco, fin)); + + dev_dbg(dev, "p: min = %u, max = %u, " + "m: min = %u, max = %u, " + "s: min = %u, max = %u\n", + prange->min, prange->max, mrange->min, + mrange->max, srange->min, srange->max); + + /* first determine 'm', then can determine 'p', last determine 's' */ + for (m = mrange->min; m <= mrange->max; m++) { + /* p = m * Fin / Fvco */ + mfin = (uint64_t)m * fin; + pr_new.min = max_t(uint32_t, prange->min, + DIV_ROUND_UP_ULL(mfin, fvco_range->max)); + pr_new.max = min_t(uint32_t, prange->max, + (mfin / fvco_range->min)); + + if (pr_new.max < pr_new.min || pr_new.min < prange->min) + continue; + + for (p = pr_new.min; p <= pr_new.max; p++) { + /* s = order_pow_of_two((m * Fin) / (p * Fout)) */ + pfout = (uint64_t)p * fout; + raw_s = DIV_ROUND_CLOSEST_ULL(mfin, pfout); + + s_pow_2 = rounddown_pow_of_two(raw_s); + sr_new.min = max_t(uint32_t, srange->min, + order_base_2(s_pow_2)); + + s_pow_2 = roundup_pow_of_two(DIV_ROUND_CLOSEST_ULL(mfin, pfout)); + sr_new.max = min_t(uint32_t, srange->max, + order_base_2(s_pow_2)); + + if (sr_new.max < sr_new.min || sr_new.min < srange->min) + continue; + + for (s = sr_new.min; s <= sr_new.max; s++) { + /* fout = m * Fin / (p * 2^s) */ + psfout = pfout * (1 << s); + delta = abs(psfout - mfin); + if (delta < best_delta) { + best_p = p; + best_m = m; + best_s = s; + best_delta = delta; + } + } + } + } + + if (best_delta == ~0U) { + devm_kfree(dev, pll_pms); + return ERR_PTR(-EINVAL); + } + + pll_pms->p = best_p; + pll_pms->m = best_m; + pll_pms->s = best_s; + + dev_dbg(dev, "fout = %u, fin = %u, m = %u, " + "p = %u, s = %u, best_delta = %u\n", + fout, fin, pll_pms->m, pll_pms->p, pll_pms->s, best_delta); + + return pll_pms; +} + +int sec_mipi_dsim_check_pll_out(void *driver_private, + const struct drm_display_mode *mode) +{ + int bpp; + uint32_t pix_clk, bit_clk; + struct sec_mipi_dsim *dsim = driver_private; + const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata; + const struct dsim_hblank_par *hpar; + const struct dsim_pll_pms *pmsk; + + bpp = mipi_dsi_pixel_format_to_bpp(dsim->format); + if (bpp < 0) + return -EINVAL; + + pix_clk = mode->clock; + bit_clk = DIV_ROUND_UP(pix_clk * bpp, dsim->lanes); + + if (bit_clk * 1000 > pdata->max_data_rate) { + dev_err(dsim->dev, + "reuest bit clk freq exceeds lane's maximum value\n"); + return -EINVAL; + } + + dsim->pix_clk = pix_clk; + dsim->bit_clk = bit_clk; + dsim->hpar = NULL; + + pmsk = sec_mipi_dsim_calc_pmsk(dsim); + if (IS_ERR(pmsk)) { + dev_err(dsim->dev, + "failed to get pmsk for: fin = %u, fout = %u\n", + dsim->pref_clk, dsim->bit_clk); + return -EINVAL; + } + + dsim->pms = PLLCTRL_SET_P(pmsk->p) | + PLLCTRL_SET_M(pmsk->m) | + PLLCTRL_SET_S(pmsk->s); + + /* free 'dsim_pll_pms' structure data which is + * allocated in 'sec_mipi_dsim_calc_pmsk()'. + */ + devm_kfree(dsim->dev, (void *)pmsk); + + if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { + hpar = sec_mipi_dsim_get_hblank_par(mode->name, + drm_mode_vrefresh(mode), + dsim->lanes); + dsim->hpar = hpar; + if (!hpar) + dev_dbg(dsim->dev, "no pre-exist hpar can be used\n"); + } + + return 0; +} +EXPORT_SYMBOL(sec_mipi_dsim_check_pll_out); + +static +struct drm_crtc *sec_mipi_dsim_get_new_crtc(struct sec_mipi_dsim *dsim, + struct drm_atomic_state *state) +{ + struct drm_encoder *encoder = dsim->encoder; + struct drm_connector *connector; + struct drm_connector_state *conn_state; + + connector = drm_atomic_get_new_connector_for_encoder(state, encoder); + if (!connector) + return NULL; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (!conn_state) + return NULL; + + return conn_state->crtc; +} + +static void +sec_mipi_dsim_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + int ret; + struct sec_mipi_dsim *dsim = bridge->driver_private; + struct drm_atomic_state *old_state = old_bridge_state->base.state; + struct drm_crtc *crtc; + struct drm_crtc_state *old_crtc_state; + + /* At this moment, the dsim bridge's preceding encoder has + * already been enabled. So the dsim can be configed here + */ + + crtc = sec_mipi_dsim_get_new_crtc(dsim, old_state); + if (!crtc) { + dev_err(dsim->dev, "bridge is enabling without CRTC\n"); + return; + } + + old_crtc_state = drm_atomic_get_old_crtc_state(old_state, crtc); + /* Don't do enablement operation if we're coming back from PSR. */ + if (old_crtc_state && old_crtc_state->self_refresh_active && + dsim->enabled) + return; + + if (dsim->enabled) + return; + + /* config main display mode */ + sec_mipi_dsim_set_main_mode(dsim); + + /* config dsim dpi */ + sec_mipi_dsim_config_dpi(dsim); + + /* config dsim pll */ + ret = sec_mipi_dsim_config_pll(dsim); + if (ret) { + dev_err(dsim->dev, "dsim pll config failed: %d\n", ret); + return; + } + + /* config dphy timings */ + sec_mipi_dsim_config_dphy(dsim); + + /* initialize FIFO pointers */ + sec_mipi_dsim_init_fifo_pointers(dsim); + + /* prepare panel if exists */ + if (dsim->panel) { + ret = drm_panel_prepare(dsim->panel); + if (unlikely(ret)) { + dev_err(dsim->dev, "panel prepare failed: %d\n", ret); + return; + } + } + + /* config esc clock, byte clock and etc */ + sec_mipi_dsim_config_clkctrl(dsim); + + /* enable panel if exists */ + if (dsim->panel) { + ret = drm_panel_enable(dsim->panel); + if (unlikely(ret)) { + dev_err(dsim->dev, "panel enable failed: %d\n", ret); + goto panel_unprepare; + } + } + + /* enable data transfer of dsim */ + sec_mipi_dsim_set_standby(dsim, true); + + dsim->enabled = true; + + return; + +panel_unprepare: + ret = drm_panel_unprepare(dsim->panel); + if (unlikely(ret)) + dev_err(dsim->dev, "panel unprepare failed: %d\n", ret); +} + +static void sec_mipi_dsim_disable_clkctrl(struct sec_mipi_dsim *dsim) +{ + uint32_t clkctrl; + + clkctrl = dsim_read(dsim, DSIM_CLKCTRL); + + clkctrl &= ~CLKCTRL_TXREQUESTHSCLK; + + clkctrl &= ~CLKCTRL_ESCCLKEN; + + clkctrl &= ~CLKCTRL_BYTECLKEN; + + dsim_write(dsim, clkctrl, DSIM_CLKCTRL); +} + +static void sec_mipi_dsim_disable_pll(struct sec_mipi_dsim *dsim) +{ + uint32_t pllctrl; + + pllctrl = dsim_read(dsim, DSIM_PLLCTRL); + + pllctrl &= ~PLLCTRL_PLLEN; + + dsim_write(dsim, pllctrl, DSIM_PLLCTRL); +} + +static void +sec_mipi_dsim_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + int ret; + struct sec_mipi_dsim *dsim = bridge->driver_private; + struct drm_atomic_state *old_state = old_bridge_state->base.state; + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + + crtc = sec_mipi_dsim_get_new_crtc(dsim, old_state); + /* No CRTC means we're doing a full shutdown. */ + if (!crtc) + goto disable; + + new_crtc_state = drm_atomic_get_new_crtc_state(old_state, crtc); + /* Don't do disablement operation if we're entering PSR. */ + if (!new_crtc_state || new_crtc_state->self_refresh_active) + return; + +disable: + if (!dsim->enabled) + return; + + /* disable panel if exists */ + if (dsim->panel) { + ret = drm_panel_disable(dsim->panel); + if (unlikely(ret)) + dev_err(dsim->dev, "panel disable failed: %d\n", ret); + } + + /* disable data transfer of dsim */ + sec_mipi_dsim_set_standby(dsim, false); + + /* disable esc clock & byte clock */ + sec_mipi_dsim_disable_clkctrl(dsim); + + /* disable dsim pll */ + sec_mipi_dsim_disable_pll(dsim); + + /* unprepare panel if exists */ + if (dsim->panel) { + ret = drm_panel_unprepare(dsim->panel); + if (unlikely(ret)) + dev_err(dsim->dev, "panel unprepare failed: %d\n", ret); + } + + dsim->enabled = false; +} + +static void sec_mipi_dsim_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct sec_mipi_dsim *dsim = bridge->driver_private; + + /* This hook is called when the display pipe is completely + * off. And since the pm runtime is implemented, the dsim + * hardware cannot be accessed at this moment. So move all + * the mode_set config to ->enable() hook. + * And this hook is called only when 'mode_changed' is true, + * so it is called not every time atomic commit. + */ + + drm_display_mode_to_videomode(adjusted_mode, &dsim->vmode); +} + +static u32 *sec_mipi_dsim_atomic_get_input_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) +{ + u32 *input_fmts; + struct sec_mipi_dsim *dsim = bridge->driver_private; + + /* use dsi format to determine output bus format + * if it is passed with MEDIA_BUS_FMT_FIXED + */ + if (output_fmt == MEDIA_BUS_FMT_FIXED) { + switch (dsim->format) { + case MIPI_DSI_FMT_RGB888: + output_fmt = MEDIA_BUS_FMT_RGB888_1X24; + break; + case MIPI_DSI_FMT_RGB666: + output_fmt = MEDIA_BUS_FMT_RGB666_1X24_CPADHI; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + output_fmt = MEDIA_BUS_FMT_RGB666_1X18; + break; + case MIPI_DSI_FMT_RGB565: + output_fmt = MEDIA_BUS_FMT_RGB565_1X16; + break; + default: + return NULL; + } + } else { + /* check if the output format matches with + * DSI device requested format + */ + switch (dsim->format) { + case MIPI_DSI_FMT_RGB888: + if (output_fmt != MEDIA_BUS_FMT_RGB888_1X24) + return NULL; + break; + case MIPI_DSI_FMT_RGB666: + if (output_fmt != MEDIA_BUS_FMT_RGB666_1X24_CPADHI) + return NULL; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + if (output_fmt != MEDIA_BUS_FMT_RGB666_1X18) + return NULL; + break; + case MIPI_DSI_FMT_RGB565: + if (output_fmt != MEDIA_BUS_FMT_RGB565_1X16) + return NULL; + break; + default: + return NULL; + } + } + + /* Since dsim cannot do any color conversion, so the + * bus format output by mipi dsi should just be + * propagated to the bus format recieved by mipi dsi + * directly. + */ + input_fmts = drm_atomic_helper_bridge_propagate_bus_fmt(bridge, + bridge_state, + crtc_state, + conn_state, + output_fmt, + num_input_fmts); + + return input_fmts; +} + +static int sec_mipi_dsim_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + struct drm_bus_cfg *input_bus_cfg = &bridge_state->input_bus_cfg; + struct sec_mipi_dsim *dsim = bridge->driver_private; + + /* + * If the DSI mode is not video mode, it implies + * that the connector is self refresh aware. + */ + if (!(dsim->mode_flags & MIPI_DSI_MODE_VIDEO)) + conn_state->self_refresh_aware = true; + + /* seems unnecessary to check the input bus format + * again, since during the negotiation process, it + * has already been checked + */ + if (bridge_state->output_bus_cfg.format == MEDIA_BUS_FMT_FIXED) + bridge_state->output_bus_cfg.format = bridge_state->input_bus_cfg.format; + + /* adjust Hsync and Vsync polarities for drm display mode, + * since DSIM can only accept active high Hsync and Vsync + * signals + */ + if (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) { + adjusted_mode->flags &= ~DRM_MODE_FLAG_NHSYNC; + adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC; + } + + if (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) { + adjusted_mode->flags &= ~DRM_MODE_FLAG_NVSYNC; + adjusted_mode->flags |= DRM_MODE_FLAG_PVSYNC; + } + + /* adjust input DE and pixel data sample polarities for + * input bus_flags, since DSIM can only accept active + * high DE and sample pixel data at clock postive edge + */ + if (input_bus_cfg->flags & DRM_BUS_FLAG_DE_LOW || + !(input_bus_cfg->flags & DRM_BUS_FLAG_DE_HIGH)) { + input_bus_cfg->flags &= ~DRM_BUS_FLAG_DE_LOW; + input_bus_cfg->flags |= DRM_BUS_FLAG_DE_HIGH; + } + + if (input_bus_cfg->flags & DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE || + !(input_bus_cfg->flags & DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE)) { + input_bus_cfg->flags &= ~DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE; + input_bus_cfg->flags |= DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE; + } + + /* workaround for CEA standard mode "1280x720@60" "1920x1080p24" + * display on 4 data lanes with Non-burst with sync + * pulse DSI mode, since use the standard horizontal + * timings cannot display correctly. And this code + * cannot be put into the dsim Bridge's mode_fixup, + * since the DSI device lane number change always + * happens after that. + */ + if (!strcmp(adjusted_mode->name, "1280x720") && + drm_mode_vrefresh(adjusted_mode) == 60 && + dsim->lanes == 4 && + dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { + adjusted_mode->hsync_start += 2; + adjusted_mode->hsync_end += 2; + adjusted_mode->htotal += 2; + } + + if (!strcmp(adjusted_mode->name, "1920x1080") && + drm_mode_vrefresh(adjusted_mode) == 24 && + dsim->lanes == 4 && + dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { + adjusted_mode->hsync_start += 2; + adjusted_mode->hsync_end += 2; + adjusted_mode->htotal += 2; + } + + return 0; +} + +static const struct drm_bridge_funcs sec_mipi_dsim_bridge_funcs = { + .atomic_check = sec_mipi_dsim_bridge_atomic_check, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_get_input_bus_fmts = sec_mipi_dsim_atomic_get_input_bus_fmts, + .attach = sec_mipi_dsim_bridge_attach, + .atomic_enable = sec_mipi_dsim_bridge_atomic_enable, + .atomic_disable = sec_mipi_dsim_bridge_atomic_disable, + .mode_set = sec_mipi_dsim_bridge_mode_set, +}; + +void sec_mipi_dsim_suspend(struct device *dev) +{ + /* TODO: add dsim reset */ +} +EXPORT_SYMBOL(sec_mipi_dsim_suspend); + +void sec_mipi_dsim_resume(struct device *dev) +{ + struct sec_mipi_dsim *dsim = dev_get_drvdata(dev); + + sec_mipi_dsim_irq_init(dsim); + + /* TODO: add dsim de-reset */ +} +EXPORT_SYMBOL(sec_mipi_dsim_resume); + +static void __maybe_unused sec_mipi_dsim_irq_mask(struct sec_mipi_dsim *dsim, + int irq_idx) +{ + uint32_t intmsk; + + intmsk = dsim_read(dsim, DSIM_INTMSK); + + switch (irq_idx) { + case PLLSTABLE: + intmsk |= INTMSK_MSKPLLSTABLE; + break; + case SWRSTRELEASE: + intmsk |= INTMSK_MSKSWRELEASE; + break; + case SFRPLFIFOEMPTY: + intmsk |= INTMSK_MSKSFRPLFIFOEMPTY; + break; + case SFRPHFIFOEMPTY: + intmsk |= INTMSK_MSKSFRPHFIFOEMPTY; + break; + case FRAMEDONE: + intmsk |= INTMSK_MSKFRAMEDONE; + break; + case LPDRTOUT: + intmsk |= INTMSK_MSKLPDRTOUT; + break; + case TATOUT: + intmsk |= INTMSK_MSKTATOUT; + break; + case RXDATDONE: + intmsk |= INTMSK_MSKRXDATDONE; + break; + case RXTE: + intmsk |= INTMSK_MSKRXTE; + break; + case RXACK: + intmsk |= INTMSK_MSKRXACK; + break; + default: + /* unsupported irq */ + return; + } + + writel(intmsk, dsim->base + DSIM_INTMSK); +} + +static void sec_mipi_dsim_irq_unmask(struct sec_mipi_dsim *dsim, + int irq_idx) +{ + uint32_t intmsk; + + intmsk = dsim_read(dsim, DSIM_INTMSK); + + switch (irq_idx) { + case PLLSTABLE: + intmsk &= ~INTMSK_MSKPLLSTABLE; + break; + case SWRSTRELEASE: + intmsk &= ~INTMSK_MSKSWRELEASE; + break; + case SFRPLFIFOEMPTY: + intmsk &= ~INTMSK_MSKSFRPLFIFOEMPTY; + break; + case SFRPHFIFOEMPTY: + intmsk &= ~INTMSK_MSKSFRPHFIFOEMPTY; + break; + case FRAMEDONE: + intmsk &= ~INTMSK_MSKFRAMEDONE; + break; + case LPDRTOUT: + intmsk &= ~INTMSK_MSKLPDRTOUT; + break; + case TATOUT: + intmsk &= ~INTMSK_MSKTATOUT; + break; + case RXDATDONE: + intmsk &= ~INTMSK_MSKRXDATDONE; + break; + case RXTE: + intmsk &= ~INTMSK_MSKRXTE; + break; + case RXACK: + intmsk &= ~INTMSK_MSKRXACK; + break; + default: + /* unsupported irq */ + return; + } + + dsim_write(dsim, intmsk, DSIM_INTMSK); +} + +/* write 1 clear irq */ +static void sec_mipi_dsim_irq_clear(struct sec_mipi_dsim *dsim, + int irq_idx) +{ + uint32_t intsrc = 0; + + switch (irq_idx) { + case PLLSTABLE: + intsrc |= INTSRC_PLLSTABLE; + break; + case SWRSTRELEASE: + intsrc |= INTSRC_SWRSTRELEASE; + break; + case SFRPLFIFOEMPTY: + intsrc |= INTSRC_SFRPLFIFOEMPTY; + break; + case SFRPHFIFOEMPTY: + intsrc |= INTSRC_SFRPHFIFOEMPTY; + break; + case FRAMEDONE: + intsrc |= INTSRC_FRAMEDONE; + break; + case LPDRTOUT: + intsrc |= INTSRC_LPDRTOUT; + break; + case TATOUT: + intsrc |= INTSRC_TATOUT; + break; + case RXDATDONE: + intsrc |= INTSRC_RXDATDONE; + break; + case RXTE: + intsrc |= INTSRC_RXTE; + break; + case RXACK: + intsrc |= INTSRC_RXACK; + break; + default: + /* unsupported irq */ + return; + } + + dsim_write(dsim, intsrc, DSIM_INTSRC); +} + +static void sec_mipi_dsim_irq_init(struct sec_mipi_dsim *dsim) +{ + sec_mipi_dsim_irq_unmask(dsim, PLLSTABLE); + sec_mipi_dsim_irq_unmask(dsim, SWRSTRELEASE); + + if (dsim->panel) { + sec_mipi_dsim_irq_unmask(dsim, SFRPLFIFOEMPTY); + sec_mipi_dsim_irq_unmask(dsim, SFRPHFIFOEMPTY); + sec_mipi_dsim_irq_unmask(dsim, LPDRTOUT); + sec_mipi_dsim_irq_unmask(dsim, TATOUT); + sec_mipi_dsim_irq_unmask(dsim, RXDATDONE); + sec_mipi_dsim_irq_unmask(dsim, RXTE); + sec_mipi_dsim_irq_unmask(dsim, RXACK); + } +} + +static irqreturn_t sec_mipi_dsim_irq_handler(int irq, void *data) +{ + uint32_t intsrc, status; + struct sec_mipi_dsim *dsim = data; + + intsrc = dsim_read(dsim, DSIM_INTSRC); + status = dsim_read(dsim, DSIM_STATUS); + + if (WARN_ON(!intsrc)) { + dev_err(dsim->dev, "interrupt is not from dsim\n"); + return IRQ_NONE; + } + + if (WARN_ON(!(intsrc & INTSRC_MASK))) { + dev_warn(dsim->dev, "unenable irq happens: %#x\n", intsrc); + /* just clear irqs */ + dsim_write(dsim, intsrc, DSIM_INTSRC); + return IRQ_NONE; + } + + if (intsrc & INTSRC_PLLSTABLE) { + WARN_ON(!(status & STATUS_PLLSTABLE)); + sec_mipi_dsim_irq_clear(dsim, PLLSTABLE); + complete(&dsim->pll_stable); + } + + if (intsrc & INTSRC_SWRSTRELEASE) + sec_mipi_dsim_irq_clear(dsim, SWRSTRELEASE); + + if (intsrc & INTSRC_SFRPLFIFOEMPTY) { + sec_mipi_dsim_irq_clear(dsim, SFRPLFIFOEMPTY); + complete(&dsim->pl_tx_done); + } + + if (intsrc & INTSRC_SFRPHFIFOEMPTY) { + sec_mipi_dsim_irq_clear(dsim, SFRPHFIFOEMPTY); + complete(&dsim->ph_tx_done); + } + + if (WARN_ON(intsrc & INTSRC_LPDRTOUT)) { + sec_mipi_dsim_irq_clear(dsim, LPDRTOUT); + dev_warn(dsim->dev, "LP RX timeout\n"); + } + + if (WARN_ON(intsrc & INTSRC_TATOUT)) { + sec_mipi_dsim_irq_clear(dsim, TATOUT); + dev_warn(dsim->dev, "Turns around Acknowledge timeout\n"); + } + + if (intsrc & INTSRC_RXDATDONE) { + sec_mipi_dsim_irq_clear(dsim, RXDATDONE); + complete(&dsim->rx_done); + } + + if (intsrc & INTSRC_RXTE) { + sec_mipi_dsim_irq_clear(dsim, RXTE); + dev_dbg(dsim->dev, "TE Rx trigger received\n"); + } + + if (intsrc & INTSRC_RXACK) { + sec_mipi_dsim_irq_clear(dsim, RXACK); + dev_dbg(dsim->dev, "ACK Rx trigger received\n"); + } + + return IRQ_HANDLED; +} + +static int sec_mipi_dsim_connector_get_modes(struct drm_connector *connector) +{ + struct sec_mipi_dsim *dsim = conn_to_sec_mipi_dsim(connector); + + if (WARN_ON(!dsim->panel)) + return -ENODEV; + + return drm_panel_get_modes(dsim->panel, connector); +} + +static const struct drm_connector_helper_funcs + sec_mipi_dsim_connector_helper_funcs = { + .get_modes = sec_mipi_dsim_connector_get_modes, +}; + +static enum drm_connector_status + sec_mipi_dsim_connector_detect(struct drm_connector *connector, + bool force) +{ + /* TODO: add support later */ + + return connector_status_connected; +} + +static const struct drm_connector_funcs sec_mipi_dsim_connector_funcs = { + .detect = sec_mipi_dsim_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +int sec_mipi_dsim_bind(struct device *dev, struct device *master, void *data, + struct drm_encoder *encoder, void __iomem *base, + int irq, const struct sec_mipi_dsim_plat_data *pdata) +{ + int ret, version; + struct drm_device *drm_dev = data; + struct drm_bridge *bridge; + struct drm_connector *connector; + struct sec_mipi_dsim *dsim; + struct device_node *node = NULL; + + dev_dbg(dev, "sec-dsim bridge bind begin\n"); + + dsim = devm_kzalloc(dev, sizeof(*dsim), GFP_KERNEL); + if (!dsim) { + dev_err(dev, "Unable to allocate 'dsim'\n"); + return -ENOMEM; + } + + dsim->dev = dev; + dsim->base = base; + dsim->pdata = pdata; + dsim->encoder = encoder; + + dsim->dsi_host.ops = &sec_mipi_dsim_host_ops; + dsim->dsi_host.dev = dev; + + dev_set_drvdata(dev, dsim); + + pm_runtime_get_sync(dev); + version = dsim_read(dsim, DSIM_VERSION); + WARN_ON(version != pdata->version); + pm_runtime_put_sync(dev); + + dev_info(dev, "version number is %#x\n", version); + + /* set suitable rate for phy ref clock */ + ret = sec_mipi_dsim_set_pref_rate(dsim); + if (ret) { + dev_err(dev, "failed to set pll ref clock rate\n"); + return ret; + } + + ret = devm_request_irq(dev, irq, sec_mipi_dsim_irq_handler, + 0, dev_name(dev), dsim); + if (ret) { + dev_err(dev, "failed to request dsim irq: %d\n", ret); + return ret; + } + + init_completion(&dsim->pll_stable); + init_completion(&dsim->ph_tx_done); + init_completion(&dsim->pl_tx_done); + init_completion(&dsim->rx_done); + + /* Initialize and attach sec dsim bridge */ + bridge = devm_kzalloc(dev, sizeof(*bridge), GFP_KERNEL); + if (!bridge) { + dev_err(dev, "Unable to allocate 'bridge'\n"); + return -ENOMEM; + } + + /* mipi dsi host needs to be registered before bridge attach, since: + * 1. Have Panel + * The 'mipi_dsi_host_register' will allocate a mipi_dsi_device + * if the dsi host node has a panel child node in DTB. And dsi + * host ->attach() will be called in panel's probe(). + * + * 2. Have Bridge + * The dsi host ->attach() will be called through the below + * 'drm_bridge_attach()' which will attach next bridge in a + * chain. + */ + ret = mipi_dsi_host_register(&dsim->dsi_host); + if (ret) { + dev_err(dev, "Unable to register mipi dsi host: %d\n", ret); + return ret; + } + + dsim->bridge = bridge; + bridge->driver_private = dsim; + bridge->funcs = &sec_mipi_dsim_bridge_funcs; + bridge->of_node = dev->of_node; + bridge->encoder = encoder; + + /* attach sec dsim bridge and its next bridge if exists */ + ret = drm_bridge_attach(encoder, bridge, NULL, 0); + if (ret) { + dev_err(dev, "Failed to attach bridge: %s\n", dev_name(dev)); + + /* no bridge exists, so defer probe to wait + * panel driver loading + */ + if (ret != -EPROBE_DEFER) { + for_each_available_child_of_node(dev->of_node, node) { + /* skip nodes without reg property */ + if (!of_find_property(node, "reg", NULL)) + continue; + + /* error codes only ENODEV or EPROBE_DEFER */ + dsim->panel = of_drm_find_panel(node); + if (!IS_ERR(dsim->panel)) + goto panel; + + ret = PTR_ERR(dsim->panel); + } + } + + mipi_dsi_host_unregister(&dsim->dsi_host); + return ret; + } + +panel: + if (dsim->panel) { + /* A panel has been attached */ + connector = &dsim->connector; + + drm_connector_helper_add(connector, + &sec_mipi_dsim_connector_helper_funcs); + ret = drm_connector_init(drm_dev, connector, + &sec_mipi_dsim_connector_funcs, + DRM_MODE_CONNECTOR_DSI); + if (ret) + goto host_unregister; + + /* TODO */ + connector->dpms = DRM_MODE_DPMS_OFF; + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + goto cleanup_connector; + } + + dev_dbg(dev, "sec-dsim bridge bind end\n"); + + return 0; + +cleanup_connector: + drm_connector_cleanup(connector); +host_unregister: + mipi_dsi_host_unregister(&dsim->dsi_host); + return ret; +} +EXPORT_SYMBOL(sec_mipi_dsim_bind); + +void sec_mipi_dsim_unbind(struct device *dev, struct device *master, void *data) +{ + struct sec_mipi_dsim *dsim = dev_get_drvdata(dev); + + if (dsim->panel) + drm_connector_cleanup(&dsim->connector); + + mipi_dsi_host_unregister(&dsim->dsi_host); +} +EXPORT_SYMBOL(sec_mipi_dsim_unbind); + +MODULE_DESCRIPTION("Samsung MIPI DSI Host Controller bridge driver"); +MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig index 21a1be3ced0f..e398b58c996d 100644 --- a/drivers/gpu/drm/bridge/synopsys/Kconfig +++ b/drivers/gpu/drm/bridge/synopsys/Kconfig @@ -25,6 +25,17 @@ config DRM_DW_HDMI_I2S_AUDIO Support the I2S Audio interface which is part of the Synopsys Designware HDMI block. +config DRM_DW_HDMI_GP_AUDIO + tristate "Synopsys Designware GP Audio interface" + depends on DRM_DW_HDMI && SND + select SND_PCM + select SND_PCM_ELD + select SND_PCM_IEC958 + help + Support the GP Audio interface which is part of the Synopsys + Designware HDMI block. This is used in conjunction with + the i.MX865 HDMI driver. + config DRM_DW_HDMI_CEC tristate "Synopsis Designware CEC interface" depends on DRM_DW_HDMI diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile index 91d746ad5de1..ce715562e9e5 100644 --- a/drivers/gpu/drm/bridge/synopsys/Makefile +++ b/drivers/gpu/drm/bridge/synopsys/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o +obj-$(CONFIG_DRM_DW_HDMI_GP_AUDIO) += dw-hdmi-gp-audio.o obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o obj-$(CONFIG_DRM_DW_HDMI_CEC) += dw-hdmi-cec.o diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c index 70ab4fbdc23e..061fe6f47077 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c @@ -18,6 +18,8 @@ #include "dw-hdmi-cec.h" +static u8 cec_saved_regs[5]; + enum { HDMI_IH_CEC_STAT0 = 0x0106, HDMI_IH_MUTE_CEC_STAT0 = 0x0186, @@ -308,11 +310,44 @@ static int dw_hdmi_cec_remove(struct platform_device *pdev) return 0; } +static int __maybe_unused dw_hdmi_cec_resume(struct device *dev) +{ + struct dw_hdmi_cec *cec = dev_get_drvdata(dev); + + /* Restore logical address and interrupt status/mask register */ + dw_hdmi_write(cec, cec_saved_regs[0], HDMI_CEC_ADDR_L); + dw_hdmi_write(cec, cec_saved_regs[1], HDMI_CEC_ADDR_H); + dw_hdmi_write(cec, cec_saved_regs[2], HDMI_CEC_POLARITY); + dw_hdmi_write(cec, cec_saved_regs[3], HDMI_CEC_MASK); + dw_hdmi_write(cec, cec_saved_regs[4], HDMI_IH_MUTE_CEC_STAT0); + + return 0; +} + +static int __maybe_unused dw_hdmi_cec_suspend(struct device *dev) +{ + struct dw_hdmi_cec *cec = dev_get_drvdata(dev); + + /* store logical address and interrupt status/mask register */ + cec_saved_regs[0] = dw_hdmi_read(cec, HDMI_CEC_ADDR_L); + cec_saved_regs[1] = dw_hdmi_read(cec, HDMI_CEC_ADDR_H); + cec_saved_regs[2] = dw_hdmi_read(cec, HDMI_CEC_POLARITY); + cec_saved_regs[3] = dw_hdmi_read(cec, HDMI_CEC_MASK); + cec_saved_regs[4] = dw_hdmi_read(cec, HDMI_IH_MUTE_CEC_STAT0); + + return 0; +} + +static const struct dev_pm_ops dw_hdmi_cec_pm = { + SET_SYSTEM_SLEEP_PM_OPS(dw_hdmi_cec_suspend, dw_hdmi_cec_resume) +}; + static struct platform_driver dw_hdmi_cec_driver = { .probe = dw_hdmi_cec_probe, .remove = dw_hdmi_cec_remove, .driver = { .name = "dw-hdmi-cec", + .pm = &dw_hdmi_cec_pm, }, }; module_platform_driver(dw_hdmi_cec_driver); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c new file mode 100644 index 000000000000..43624ea6a937 --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright 2020 NXP +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <drm/bridge/dw_hdmi.h> +#include <drm/drm_edid.h> +#include <drm/drm_connector.h> + +#include <sound/hdmi-codec.h> +#include <sound/asoundef.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_drm_eld.h> +#include <sound/pcm_iec958.h> +#include <sound/dmaengine_pcm.h> + +#include "dw-hdmi-audio.h" + +#define DRIVER_NAME "dw-hdmi-gp-audio" +#define DRV_NAME "hdmi-gp-audio" + +struct snd_dw_hdmi { + struct dw_hdmi_audio_data data; + struct platform_device *audio_pdev; + unsigned int pos; +}; + +struct dw_hdmi_channel_conf { + u8 conf1; + u8 ca; +}; + +/* + * The default mapping of ALSA channels to HDMI channels and speaker + * allocation bits. Note that we can't do channel remapping here - + * channels must be in the same order. + * + * Mappings for alsa-lib pcm/surround*.conf files: + * + * Front Sur4.0 Sur4.1 Sur5.0 Sur5.1 Sur7.1 + * Channels 2 4 6 6 6 8 + * + * Our mapping from ALSA channel to CEA686D speaker name and HDMI channel: + * + * Number of ALSA channels + * ALSA Channel 2 3 4 5 6 7 8 + * 0 FL:0 = = = = = = + * 1 FR:1 = = = = = = + * 2 FC:3 RL:4 LFE:2 = = = + * 3 RR:5 RL:4 FC:3 = = + * 4 RR:5 RL:4 = = + * 5 RR:5 = = + * 6 RC:6 = + * 7 RLC/FRC RLC/FRC + */ +static struct dw_hdmi_channel_conf default_hdmi_channel_config[7] = { + { 0x03, 0x00 }, /* FL,FR */ + { 0x0b, 0x02 }, /* FL,FR,FC */ + { 0x33, 0x08 }, /* FL,FR,RL,RR */ + { 0x37, 0x09 }, /* FL,FR,LFE,RL,RR */ + { 0x3f, 0x0b }, /* FL,FR,LFE,FC,RL,RR */ + { 0x7f, 0x0f }, /* FL,FR,LFE,FC,RL,RR,RC */ + { 0xff, 0x13 }, /* FL,FR,LFE,FC,RL,RR,[FR]RC,[FR]LC */ +}; + +static int audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct snd_dw_hdmi *dw = dev_get_drvdata(dev); + int ret = 0; + u8 ca; + + dw_hdmi_set_sample_rate(dw->data.hdmi, params->sample_rate); + + ca = default_hdmi_channel_config[params->channels - 2].ca; + + dw_hdmi_set_channel_count(dw->data.hdmi, params->channels); + dw_hdmi_set_channel_allocation(dw->data.hdmi, ca); + + dw_hdmi_set_sample_non_pcm(dw->data.hdmi, + params->iec.status[0] & IEC958_AES0_NONAUDIO); + dw_hdmi_set_sample_width(dw->data.hdmi, params->sample_width); + + return ret; +} + +static void audio_shutdown(struct device *dev, void *data) +{ +} + +static int audio_mute_stream(struct device *dev, void *data, + bool enable, int direction) +{ + struct snd_dw_hdmi *dw = dev_get_drvdata(dev); + int ret = 0; + + if (!enable) + dw_hdmi_audio_enable(dw->data.hdmi); + else + dw_hdmi_audio_disable(dw->data.hdmi); + + return ret; +} + +static int audio_get_eld(struct device *dev, void *data, + u8 *buf, size_t len) +{ + struct dw_hdmi_audio_data *audio = data; + u8 *eld; + + eld = audio->get_eld(audio->hdmi); + if (eld) + memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len)); + else + /* Pass en empty ELD if connector not available */ + memset(buf, 0, len); + + return 0; +} + +static int audio_hook_plugged_cb(struct device *dev, void *data, + hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + struct snd_dw_hdmi *dw = dev_get_drvdata(dev); + + return dw_hdmi_set_plugged_cb(dw->data.hdmi, fn, codec_dev); +} + +static const struct hdmi_codec_ops audio_codec_ops = { + .hw_params = audio_hw_params, + .audio_shutdown = audio_shutdown, + .mute_stream = audio_mute_stream, + .get_eld = audio_get_eld, + .hook_plugged_cb = audio_hook_plugged_cb, +}; + +static int snd_dw_hdmi_probe(struct platform_device *pdev) +{ + const struct dw_hdmi_audio_data *data = pdev->dev.platform_data; + struct snd_dw_hdmi *dw; + + struct hdmi_codec_pdata codec_data = { + .i2s = 1, + .spdif = 0, + .ops = &audio_codec_ops, + .max_i2s_channels = 8, + .data = (void *)data, + }; + + dw = devm_kzalloc(&pdev->dev, sizeof(*dw), GFP_KERNEL); + if (!dw) + return -ENOMEM; + + dw->data = *data; + + platform_set_drvdata(pdev, dw); + + dw->audio_pdev = platform_device_register_data(&pdev->dev, + HDMI_CODEC_DRV_NAME, 1, + &codec_data, + sizeof(codec_data)); + + return PTR_ERR_OR_ZERO(dw->audio_pdev); +} + +static int snd_dw_hdmi_remove(struct platform_device *pdev) +{ + struct snd_dw_hdmi *dw = platform_get_drvdata(pdev); + + platform_device_unregister(dw->audio_pdev); + + return 0; +} + +static struct platform_driver snd_dw_hdmi_driver = { + .probe = snd_dw_hdmi_probe, + .remove = snd_dw_hdmi_remove, + .driver = { + .name = DRIVER_NAME, + }, +}; + +module_platform_driver(snd_dw_hdmi_driver); + +MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>"); +MODULE_DESCRIPTION("Synopsis Designware HDMI GPA ALSA interface"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 8bb403bc712a..de48ecddbb39 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -191,7 +191,10 @@ struct dw_hdmi { spinlock_t audio_lock; struct mutex audio_mutex; + unsigned int sample_non_pcm; + unsigned int sample_width; unsigned int sample_rate; + unsigned int channels; unsigned int audio_cts; unsigned int audio_n; bool audio_enable; @@ -589,6 +592,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk) n = 4096; else if (pixel_clk == 74176000 || pixel_clk == 148352000) n = 11648; + else if (pixel_clk == 297000000) + n = 3072; else n = 4096; n *= mult; @@ -601,6 +606,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk) n = 17836; else if (pixel_clk == 148352000) n = 8918; + else if (pixel_clk == 297000000) + n = 4704; else n = 6272; n *= mult; @@ -615,6 +622,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk) n = 11648; else if (pixel_clk == 148352000) n = 5824; + else if (pixel_clk == 297000000) + n = 5120; else n = 6144; n *= mult; @@ -660,7 +669,7 @@ static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi, config3 = hdmi_readb(hdmi, HDMI_CONFIG3_ID); /* Only compute CTS when using internal AHB audio */ - if (config3 & HDMI_CONFIG3_AHBAUDDMA) { + if ((config3 & HDMI_CONFIG3_AHBAUDDMA) || (config3 & HDMI_CONFIG3_GPAUD)) { /* * Compute the CTS value from the N value. Note that CTS and N * can be up to 20 bits in total, so we need 64-bit math. Also @@ -702,6 +711,22 @@ static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi *hdmi) mutex_unlock(&hdmi->audio_mutex); } +void dw_hdmi_set_sample_width(struct dw_hdmi *hdmi, unsigned int width) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi->sample_width = width; + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_width); + +void dw_hdmi_set_sample_non_pcm(struct dw_hdmi *hdmi, unsigned int non_pcm) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi->sample_non_pcm = non_pcm; + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_non_pcm); + void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate) { mutex_lock(&hdmi->audio_mutex); @@ -717,6 +742,7 @@ void dw_hdmi_set_channel_count(struct dw_hdmi *hdmi, unsigned int cnt) u8 layout; mutex_lock(&hdmi->audio_mutex); + hdmi->channels = cnt; /* * For >2 channel PCM audio, we need to select layout 1 @@ -757,6 +783,87 @@ static void hdmi_enable_audio_clk(struct dw_hdmi *hdmi, bool enable) hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); } +static void dw_hdmi_gp_audio_enable(struct dw_hdmi *hdmi) +{ + int sample_freq = 0x2, org_sample_freq = 0xD; + int ch_mask = BIT(hdmi->channels) - 1; + + switch (hdmi->sample_rate) { + case 32000: + sample_freq = 0x03; + org_sample_freq = 0x0C; + break; + case 44100: + sample_freq = 0x00; + org_sample_freq = 0x0F; + break; + case 48000: + sample_freq = 0x02; + org_sample_freq = 0x0D; + break; + case 88200: + sample_freq = 0x08; + org_sample_freq = 0x07; + break; + case 96000: + sample_freq = 0x0A; + org_sample_freq = 0x05; + break; + case 176400: + sample_freq = 0x0C; + org_sample_freq = 0x03; + break; + case 192000: + sample_freq = 0x0E; + org_sample_freq = 0x01; + break; + default: + break; + } + + hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); + hdmi_enable_audio_clk(hdmi, true); + + hdmi_writeb(hdmi, 0x1, HDMI_FC_AUDSCHNLS0); + hdmi_writeb(hdmi, hdmi->channels, HDMI_FC_AUDSCHNLS2); + hdmi_writeb(hdmi, 0x22, HDMI_FC_AUDSCHNLS3); + hdmi_writeb(hdmi, 0x22, HDMI_FC_AUDSCHNLS4); + hdmi_writeb(hdmi, 0x11, HDMI_FC_AUDSCHNLS5); + hdmi_writeb(hdmi, 0x11, HDMI_FC_AUDSCHNLS6); + hdmi_writeb(hdmi, (0x3 << 4) | sample_freq, HDMI_FC_AUDSCHNLS7); + hdmi_writeb(hdmi, (org_sample_freq << 4) | 0xb, HDMI_FC_AUDSCHNLS8); + + hdmi_writeb(hdmi, ch_mask, HDMI_GP_CONF1); + hdmi_writeb(hdmi, 0x02, HDMI_GP_CONF2); + hdmi_writeb(hdmi, 0x01, HDMI_GP_CONF0); + + hdmi_modb(hdmi, 0x3, 0x3, HDMI_FC_DATAUTO3); + + /* hbr */ + if (hdmi->sample_rate == 192000 && hdmi->channels == 8 && + hdmi->sample_width == 32 && hdmi->sample_non_pcm) { + hdmi_modb(hdmi, 0x01, 0x01, HDMI_GP_CONF2); + } + + if (hdmi->phy.ops->enable_audio) + hdmi->phy.ops->enable_audio(hdmi, hdmi->phy.data, + hdmi->channels, + hdmi->sample_width, + hdmi->sample_rate, + hdmi->sample_non_pcm); +} + +static void dw_hdmi_gp_audio_disable(struct dw_hdmi *hdmi) +{ + hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0); + + hdmi_modb(hdmi, 0, 0x3, HDMI_FC_DATAUTO3); + if (hdmi->phy.ops->disable_audio) + hdmi->phy.ops->disable_audio(hdmi, hdmi->phy.data); + + hdmi_enable_audio_clk(hdmi, false); +} + static u8 *hdmi_audio_get_eld(struct dw_hdmi *hdmi) { if (!hdmi->curr_conn) @@ -1160,6 +1267,14 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi) HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK); hdmi_writeb(hdmi, val, HDMI_VP_PR_CD); + val = hdmi_readb(hdmi, HDMI_FC_DATAUTO3); + if (color_depth == 4) + /* disable Auto GCP when bpp 24 */ + val &= ~0x4; + else + val |= 0x4; + hdmi_writeb(hdmi, val, HDMI_FC_DATAUTO3); + hdmi_modb(hdmi, HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE, HDMI_VP_STUFF_PR_STUFFING_MASK, HDMI_VP_STUFF); @@ -1359,11 +1474,19 @@ static void dw_hdmi_phy_sel_interface_control(struct dw_hdmi *hdmi, u8 enable) void dw_hdmi_phy_reset(struct dw_hdmi *hdmi) { + /* PHY reset. The reset signal is active high on Gen1 PHYs. */ + hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ); + hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_reset); + +void dw_hdmi_phy_gen2_reset(struct dw_hdmi *hdmi) +{ /* PHY reset. The reset signal is active high on Gen2 PHYs. */ hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ); hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ); } -EXPORT_SYMBOL_GPL(dw_hdmi_phy_reset); +EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen2_reset); void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address) { @@ -1517,7 +1640,7 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, if (phy->has_svsret) dw_hdmi_phy_enable_svsret(hdmi, 1); - dw_hdmi_phy_reset(hdmi); + dw_hdmi_phy_gen2_reset(hdmi); hdmi_writeb(hdmi, HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST); @@ -2087,9 +2210,9 @@ static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi) * * The number of iterations matters and depends on the HDMI TX revision * (and possibly on the platform). So far i.MX6Q (v1.30a), i.MX6DL - * (v1.31a) and multiple Allwinner SoCs (v1.32a) have been identified - * as needing the workaround, with 4 iterations for v1.30a and 1 - * iteration for others. + * (v1.31a), iMX865(v2.13a) and multiple Allwinner SoCs (v1.32a) + * have been identified as needing the workaround, + * with 4 iterations for v1.30a and 1 iteration for others. * The Amlogic Meson GX SoCs (v2.01a) have been identified as needing * the workaround with a single iteration. * The Rockchip RK3288 SoC (v2.00a) and RK3328/RK3399 SoCs (v2.11a) have @@ -2106,6 +2229,7 @@ static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi) case 0x201a: case 0x211a: case 0x212a: + case 0x213a: count = 1; break; default: @@ -2863,6 +2987,14 @@ static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge, hdmi->curr_conn = connector; dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); + + /* Workaround for update hdmi audio eld data when cable plugin. + * plugged_cb is called in bridge_detect to update eld, but eld + * data is not updated until get_edid. Call handle_plugged_change + * in bridge_atomic_enable to make sure eld data is update after + * edid data are read */ + handle_plugged_change(hdmi, + hdmi->last_connector_result == connector_status_connected); mutex_unlock(&hdmi->mutex); } @@ -3245,6 +3377,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, hdmi->plat_data = plat_data; hdmi->dev = dev; hdmi->sample_rate = 48000; + hdmi->channels = 2; hdmi->disabled = true; hdmi->rxsense = true; hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE); @@ -3467,6 +3600,24 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, pdevinfo.size_data = sizeof(audio); pdevinfo.dma_mask = DMA_BIT_MASK(32); hdmi->audio = platform_device_register_full(&pdevinfo); + } else if (iores && config3 & HDMI_CONFIG3_GPAUD) { + struct dw_hdmi_audio_data audio; + + audio.phys = iores->start; + audio.base = hdmi->regs; + audio.irq = irq; + audio.hdmi = hdmi; + audio.get_eld = hdmi_audio_get_eld; + + hdmi->enable_audio = dw_hdmi_gp_audio_enable; + hdmi->disable_audio = dw_hdmi_gp_audio_disable; + + pdevinfo.name = "dw-hdmi-gp-audio"; + pdevinfo.id = PLATFORM_DEVID_NONE; + pdevinfo.data = &audio; + pdevinfo.size_data = sizeof(audio); + pdevinfo.dma_mask = DMA_BIT_MASK(32); + hdmi->audio = platform_device_register_full(&pdevinfo); } if (!plat_data->disable_cec && (config0 & HDMI_CONFIG0_CEC)) { diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h index 1999db05bc3b..99aa1c03343b 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h @@ -158,8 +158,17 @@ #define HDMI_FC_SPDDEVICEINF 0x1062 #define HDMI_FC_AUDSCONF 0x1063 #define HDMI_FC_AUDSSTAT 0x1064 -#define HDMI_FC_AUDSCHNLS7 0x106e -#define HDMI_FC_AUDSCHNLS8 0x106f +#define HDMI_FC_AUDSV 0x1065 +#define HDMI_FC_AUDSU 0x1066 +#define HDMI_FC_AUDSCHNLS0 0x1067 +#define HDMI_FC_AUDSCHNLS1 0x1068 +#define HDMI_FC_AUDSCHNLS2 0x1069 +#define HDMI_FC_AUDSCHNLS3 0x106A +#define HDMI_FC_AUDSCHNLS4 0x106B +#define HDMI_FC_AUDSCHNLS5 0x106C +#define HDMI_FC_AUDSCHNLS6 0x106D +#define HDMI_FC_AUDSCHNLS7 0x106E +#define HDMI_FC_AUDSCHNLS8 0x106F #define HDMI_FC_DATACH0FILL 0x1070 #define HDMI_FC_DATACH1FILL 0x1071 #define HDMI_FC_DATACH2FILL 0x1072 diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c index 56c3fd08c6a0..e642c15587ac 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c @@ -12,6 +12,7 @@ #include <linux/component.h> #include <linux/debugfs.h> #include <linux/iopoll.h> +#include <linux/math64.h> #include <linux/module.h> #include <linux/of_device.h> #include <linux/pm_runtime.h> @@ -386,8 +387,6 @@ static int dw_mipi_dsi_host_detach(struct mipi_dsi_host *host, drm_of_panel_bridge_remove(host->dev->of_node, 1, 0); - drm_bridge_remove(&dsi->bridge); - return 0; } @@ -654,7 +653,7 @@ static void dw_mipi_dsi_init(struct dw_mipi_dsi *dsi) * timeout clock division should be computed with the * high speed transmission counter timeout and byte lane... */ - dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVISION(10) | + dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVISION(0) | TX_ESC_CLK_DIVISION(esc_clk_division)); } @@ -733,8 +732,15 @@ static u32 dw_mipi_dsi_get_hcomponent_lbcc(struct dw_mipi_dsi *dsi, u32 hcomponent) { u32 frac, lbcc; + int bpp; + + bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + if (bpp < 0) { + dev_err(dsi->dev, "failed to get bpp\n"); + return 0; + } - lbcc = hcomponent * dsi->lane_mbps * MSEC_PER_SEC / 8; + lbcc = div_u64((u64)hcomponent * mode->clock * bpp, dsi->lanes * 8); frac = lbcc % mode->clock; lbcc = lbcc / mode->clock; @@ -1216,6 +1222,8 @@ __dw_mipi_dsi_probe(struct platform_device *pdev, static void __dw_mipi_dsi_remove(struct dw_mipi_dsi *dsi) { + drm_bridge_remove(&dsi->bridge); + mipi_dsi_host_unregister(&dsi->dsi_host); pm_runtime_disable(dsi->dev); diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c index 37c34146eea8..ac56279bfb71 100644 --- a/drivers/gpu/drm/drm_of.c +++ b/drivers/gpu/drm/drm_of.c @@ -100,8 +100,10 @@ void drm_of_component_match_add(struct device *master, EXPORT_SYMBOL_GPL(drm_of_component_match_add); /** - * drm_of_component_probe - Generic probe function for a component based master + * drm_of_component_probe_with_match - Generic probe function with match + * entries for a component based master * @dev: master device containing the OF node + * @match: component match pointer provided to store matches * @compare_of: compare function used for matching components * @m_ops: component master ops to be used * @@ -112,12 +114,12 @@ EXPORT_SYMBOL_GPL(drm_of_component_match_add); * * Returns zero if successful, or one of the standard error codes if it fails. */ -int drm_of_component_probe(struct device *dev, +int drm_of_component_probe_with_match(struct device *dev, + struct component_match *match, int (*compare_of)(struct device *, void *), const struct component_master_ops *m_ops) { struct device_node *ep, *port, *remote; - struct component_match *match = NULL; int i; if (!dev->of_node) @@ -183,6 +185,29 @@ int drm_of_component_probe(struct device *dev, return component_master_add_with_match(dev, m_ops, match); } +EXPORT_SYMBOL(drm_of_component_probe_with_match); + +/** + * drm_of_component_probe - Generic probe function for a component based master + * @dev: master device containing the OF node + * @compare_of: compare function used for matching components + * @master_ops: component master ops to be used + * + * Parse the platform device OF node and bind all the components associated + * with the master. Interface ports are added before the encoders in order to + * satisfy their .bind requirements + * See Documentation/devicetree/bindings/graph.txt for the bindings. + * + * Returns zero if successful, or one of the standard error codes if it fails. + */ +int drm_of_component_probe(struct device *dev, + int (*compare_of)(struct device *, void *), + const struct component_master_ops *m_ops) +{ + struct component_match *match = NULL; + + return drm_of_component_probe_with_match(dev, match, compare_of, m_ops); +} EXPORT_SYMBOL(drm_of_component_probe); /* diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig index b5fa0e45a839..6580101a7729 100644 --- a/drivers/gpu/drm/imx/Kconfig +++ b/drivers/gpu/drm/imx/Kconfig @@ -6,10 +6,15 @@ config DRM_IMX select DRM_GEM_CMA_HELPER select DRM_KMS_CMA_HELPER depends on DRM && (ARCH_MXC || ARCH_MULTIPLATFORM || COMPILE_TEST) - depends on IMX_IPUV3_CORE + depends on IMX_IPUV3_CORE || IMX_DPU_CORE || IMX_LCDIF_CORE help enable i.MX graphics support +config DRM_IMX_LCDIF_MUX_DISPLAY + tristate "Support for LCDIF mux displays" + select DRM_PANEL_BRIDGE + depends on DRM_IMX && OF && MFD_SYSCON + config DRM_IMX_PARALLEL_DISPLAY tristate "Support for parallel displays" select DRM_PANEL @@ -34,11 +39,86 @@ config DRM_IMX_LDB Choose this to enable the internal LVDS Display Bridge (LDB) found on i.MX53 and i.MX6 processors. +config DRM_IMX8QM_LDB + tristate "Support for i.MX8qm LVDS displays" + depends on DRM_IMX && DRM_FSL_IMX_LVDS_BRIDGE + depends on PHY_MIXEL_LVDS + help + Choose this to enable the internal LVDS Display Bridge (LDB) + found on i.MX8qm processors. + +config DRM_IMX8QXP_LDB + tristate "Support for i.MX8qxp LVDS displays" + depends on DRM_IMX && DRM_FSL_IMX_LVDS_BRIDGE + depends on PHY_MIXEL_LVDS_COMBO + help + Choose this to enable the internal LVDS Display Bridge (LDB) + found on i.MX8qxp processors. + +config DRM_IMX8MP_LDB + tristate "Support for i.MX8mp LVDS displays" + depends on DRM_IMX && DRM_FSL_IMX_LVDS_BRIDGE + depends on PHY_FSL_IMX8MP_LVDS + help + Choose this to enable the internal LVDS Display Bridge (LDB) + found on i.MX8mp processors. + +config DRM_IMX93_LDB + tristate "Support for i.MX93 LVDS displays" + depends on DRM_IMX && DRM_FSL_IMX_LVDS_BRIDGE + depends on PHY_FSL_IMX8MP_LVDS + help + Choose this to enable the internal LVDS Display Bridge (LDB) + found on i.MX93 processors. + +config DRM_IMX93_PARALLEL_DISPLAY_FORMAT + tristate "Support for i.MX93 parallel display format" + select DRM_PANEL_BRIDGE + depends on DRM_IMX && OF && MFD_SYSCON + help + Choose this to enable the internal parallel display format + configuration found on i.MX93 processors. + +config DRM_IMX_IPUV3 + tristate + depends on DRM_IMX + depends on IMX_IPUV3_CORE + default y if DRM_IMX=y + default m if DRM_IMX=m + +config IMX8MP_HDMI_PAVI + tristate "NXP i.MX8MP HDMI Audio Video (PVI/PAI)" + help + Choose this if you want to use HDMI PAI/PVI on i.MX8MP. + +config DRM_IMX_DW_MIPI_DSI + tristate "Freescale i.MX DRM Synopsys DesignWare MIPI DSI" + select DRM_DW_MIPI_DSI + depends on DRM_IMX + help + Choose this if you want to use Synopsys DesignWare MIPI DSI on i.MX93. + config DRM_IMX_HDMI tristate "Freescale i.MX DRM HDMI" select DRM_DW_HDMI + select IMX8MP_HDMI_PAVI depends on DRM_IMX && OF help - Choose this if you want to use HDMI on i.MX6. + Choose this if you want to use HDMI on i.MX6/i.MX8. + +config DRM_IMX_SEC_DSIM + tristate "Support for Samsung MIPI DSIM displays" + depends on DRM_IMX + select MFD_SYSCON + select DRM_SEC_MIPI_DSIM + help + Choose this to enable the internal SEC MIPI DSIM controller + found on i.MX platform. +source "drivers/gpu/drm/imx/dcnano/Kconfig" +source "drivers/gpu/drm/imx/dpu/Kconfig" source "drivers/gpu/drm/imx/dcss/Kconfig" +source "drivers/gpu/drm/imx/mhdp/Kconfig" +source "drivers/gpu/drm/imx/ipuv3/Kconfig" +source "drivers/gpu/drm/imx/lcdif/Kconfig" +source "drivers/gpu/drm/imx/lcdifv3/Kconfig" diff --git a/drivers/gpu/drm/imx/Makefile b/drivers/gpu/drm/imx/Makefile index b644deffe948..e35d08773f65 100644 --- a/drivers/gpu/drm/imx/Makefile +++ b/drivers/gpu/drm/imx/Makefile @@ -1,12 +1,27 @@ # SPDX-License-Identifier: GPL-2.0 -imxdrm-objs := imx-drm-core.o ipuv3-crtc.o ipuv3-plane.o +imxdrm-objs := imx-drm-core.o obj-$(CONFIG_DRM_IMX) += imxdrm.o +obj-$(CONFIG_DRM_IMX_LCDIF_MUX_DISPLAY) += lcdif-mux-display.o obj-$(CONFIG_DRM_IMX_PARALLEL_DISPLAY) += parallel-display.o obj-$(CONFIG_DRM_IMX_TVE) += imx-tve.o obj-$(CONFIG_DRM_IMX_LDB) += imx-ldb.o +obj-$(CONFIG_DRM_IMX8QM_LDB) += imx8qm-ldb.o +obj-$(CONFIG_DRM_IMX8QXP_LDB) += imx8qxp-ldb.o +obj-$(CONFIG_DRM_IMX8MP_LDB) += imx8mp-ldb.o +obj-$(CONFIG_DRM_IMX93_LDB) += imx93-ldb.o +obj-$(CONFIG_DRM_IMX93_PARALLEL_DISPLAY_FORMAT) += imx93-parallel-disp-fmt.o +obj-$(CONFIG_DRM_IMX_DCNANO) += dcnano/ +obj-$(CONFIG_DRM_IMX_DPU) += dpu/ +obj-$(CONFIG_DRM_IMX_DW_MIPI_DSI) += dw_mipi_dsi-imx.o +obj-$(CONFIG_DRM_IMX_IPUV3) += ipuv3/ obj-$(CONFIG_DRM_IMX_HDMI) += dw_hdmi-imx.o +obj-$(CONFIG_DRM_IMX_SEC_DSIM) += sec_mipi_dsim-imx.o +obj-$(CONFIG_IMX8MP_HDMI_PAVI) += imx8mp-hdmi-pavi.o obj-$(CONFIG_DRM_IMX_DCSS) += dcss/ +obj-$(CONFIG_DRM_IMX_CDNS_MHDP) += mhdp/ +obj-$(CONFIG_DRM_IMX_LCDIF) += lcdif/ +obj-$(CONFIG_DRM_IMX_LCDIFV3) += lcdifv3/ diff --git a/drivers/gpu/drm/imx/dcnano/Kconfig b/drivers/gpu/drm/imx/dcnano/Kconfig new file mode 100644 index 000000000000..ec3ba3ad7b00 --- /dev/null +++ b/drivers/gpu/drm/imx/dcnano/Kconfig @@ -0,0 +1,10 @@ +config DRM_IMX_DCNANO + tristate "DRM support for NXP i.MX DCNANO Graphics" + select DRM_KMS_HELPER + select VIDEOMODE_HELPERS + select DRM_GEM_CMA_HELPER + select DRM_KMS_CMA_HELPER + depends on DRM && OF && ARCH_MXC + depends on COMMON_CLK + help + enable NXP i.MX DCNANO graphics support diff --git a/drivers/gpu/drm/imx/dcnano/Makefile b/drivers/gpu/drm/imx/dcnano/Makefile new file mode 100644 index 000000000000..b08d6712861f --- /dev/null +++ b/drivers/gpu/drm/imx/dcnano/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 + +imx-dcnano-drm-objs := dcnano-crtc.o dcnano-drv.o dcnano-kms.o dcnano-plane.o + +obj-$(CONFIG_DRM_IMX_DCNANO) += imx-dcnano-drm.o diff --git a/drivers/gpu/drm/imx/dcnano/dcnano-crtc.c b/drivers/gpu/drm/imx/dcnano/dcnano-crtc.c new file mode 100644 index 000000000000..bc256c2d861e --- /dev/null +++ b/drivers/gpu/drm/imx/dcnano/dcnano-crtc.c @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Copyright 2020,2021 NXP + */ + +#include <linux/clk.h> +#include <linux/irq.h> +#include <linux/pm_runtime.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_plane.h> +#include <drm/drm_print.h> +#include <drm/drm_vblank.h> + +#include "dcnano-drv.h" +#include "dcnano-reg.h" + +#define DCNANO_CRTC_PLL_MIN_RATE 271500000 +#define DCNANO_CRTC_PLL_MAX_RATE 792000000 + +#define DCNANO_CRTC_PLL_MIN_DIV 1 +#define DCNANO_CRTC_PLL_MAX_DIV 64 + +#define dcnano_crtc_dbg(crtc, fmt, ...) \ + drm_dbg_kms((crtc)->dev, "[CRTC:%d:%s] " fmt, \ + (crtc)->base.id, (crtc)->name, ##__VA_ARGS__) + +#define dcnano_crtc_err(crtc, fmt, ...) \ + drm_err((crtc)->dev, "[CRTC:%d:%s] " fmt, \ + (crtc)->base.id, (crtc)->name, ##__VA_ARGS__) + +static inline struct dcnano_dev *crtc_to_dcnano_dev(struct drm_crtc *crtc) +{ + return to_dcnano_dev(crtc->dev); +} + +static void dcnano_crtc_mode_set_nofb_dpi(struct drm_crtc *crtc) +{ + struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc); + struct drm_display_mode *adj = &crtc->state->adjusted_mode; + u32 val; + + /* select output bus */ + dcnano_write(dcnano, DCNANO_DBICONFIG, DBICFG_BUS_OUTPUT_SEL_DPI); + + /* set bus format */ + dcnano_write(dcnano, DCNANO_DPICONFIG, DPICFG_DATA_FORMAT_D24); + + /* horizontal timing */ + val = HDISPLAY_END(adj->crtc_hdisplay) | + HDISPLAY_TOTAL(adj->crtc_htotal); + dcnano_write(dcnano, DCNANO_HDISPLAY, val); + + val = HSYNC_START(adj->crtc_hsync_start) | + HSYNC_END(adj->crtc_hsync_end) | HSYNC_PULSE_ENABLE; + if (adj->flags & DRM_MODE_FLAG_PHSYNC) + val |= HSYNC_POL_POSITIVE; + else + val |= HSYNC_POL_NEGATIVE; + dcnano_write(dcnano, DCNANO_HSYNC, val); + + /* vertical timing */ + val = VDISPLAY_END(adj->crtc_vdisplay) | + VDISPLAY_TOTAL(adj->crtc_vtotal); + dcnano_write(dcnano, DCNANO_VDISPLAY, val); + + val = VSYNC_START(adj->crtc_vsync_start) | + VSYNC_END(adj->crtc_vsync_end) | VSYNC_PULSE_ENABLE; + if (adj->flags & DRM_MODE_FLAG_PVSYNC) + val |= VSYNC_POL_POSITIVE; + else + val |= VSYNC_POL_NEGATIVE; + dcnano_write(dcnano, DCNANO_VSYNC, val); + + /* panel configuration */ + val = PANELCFG_DE_ENABLE | PANELCFG_DE_POL_POSITIVE | + PANELCFG_DATA_ENABLE | PANELCFG_DATA_POL_POSITIVE | + PANELCFG_CLOCK_ENABLE | PANELCFG_CLOCK_POL_POSITIVE | + PANELCFG_SEQUENCING_SOFTWARE; + dcnano_write(dcnano, DCNANO_PANELCONFIG, val); +} + +static bool dcnano_crtc_pll_clock_rate_is_valid(unsigned long pll_clk_rate) +{ + return pll_clk_rate >= DCNANO_CRTC_PLL_MIN_RATE && + pll_clk_rate <= DCNANO_CRTC_PLL_MAX_RATE; +} + +static unsigned long +dcnano_crtc_find_pll_clock_rate(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc); + unsigned long pll_clk_rate, rounded_pll_clk_rate; + int i; + + for (i = DCNANO_CRTC_PLL_MIN_DIV; i <= DCNANO_CRTC_PLL_MAX_DIV; i++) { + pll_clk_rate = mode->clock * 1000 * i; + + if (!dcnano_crtc_pll_clock_rate_is_valid(pll_clk_rate)) + continue; + + rounded_pll_clk_rate = clk_round_rate(dcnano->pll_clk, + pll_clk_rate); + if (rounded_pll_clk_rate != pll_clk_rate) { + dcnano_crtc_dbg(crtc, + "rounded pll clock rate %lu, expected %lu\n", + rounded_pll_clk_rate, pll_clk_rate); + continue; + } + + dcnano_crtc_dbg(crtc, "find pll clock rate %lu with div %d\n", + pll_clk_rate, i); + + return pll_clk_rate; + } + + dcnano_crtc_dbg(crtc, "failed to find pll clock rate\n"); + + return 0; +} + +static void dcnano_crtc_set_pixel_clock(struct drm_crtc *crtc) +{ + struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc); + struct drm_display_mode *adj = &crtc->state->adjusted_mode; + struct clk *parent; + unsigned long pixel_clk_rate = adj->crtc_clock * 1000; + unsigned long pll_clk_rate; + int ret; + + parent = clk_get_parent(dcnano->pixel_clk); + if (!parent) { + dcnano_crtc_err(crtc, "%s: no pixel clock's parent\n", __func__); + } else if (IS_ERR(parent)) { + ret = PTR_ERR(parent); + dcnano_crtc_err(crtc, + "%s: failed to get pixel clock's parent: %d\n", + __func__, ret); + return; + } + + pll_clk_rate = dcnano_crtc_find_pll_clock_rate(crtc, adj); + if (pll_clk_rate == 0) + dcnano_crtc_err(crtc, "%s: failed to find pll clock rate\n", + __func__); + + ret = clk_set_rate(dcnano->pll_clk, pll_clk_rate); + if (ret) + dcnano_crtc_err(crtc, "%s: failed to set pll clock rate: %d\n", + __func__, ret); + + /* FIXME: The rate of pixel clock's parent is pixel clock rate. */ + ret = clk_set_rate(parent, pixel_clk_rate); + if (ret) + dcnano_crtc_err(crtc, + "%s: failed to set pixel clock's parent rate: %d\n", + __func__, ret); + + ret = clk_set_rate(dcnano->pixel_clk, pixel_clk_rate); + if (ret) + dcnano_crtc_err(crtc, "%s: failed to set pixel clock rate: %d\n", + __func__, ret); + + ret = clk_prepare_enable(dcnano->pixel_clk); + if (ret) + dcnano_crtc_err(crtc, "%s: failed to enable pixel clock: %d\n", + __func__, ret); + + dcnano_crtc_dbg(crtc, "%s: get pll clock rate: %lu\n", + __func__, clk_get_rate(dcnano->pll_clk)); + + dcnano_crtc_dbg(crtc, "%s: get rate of pixel clock's parent: %lu\n", + __func__, clk_get_rate(parent)); + + dcnano_crtc_dbg(crtc, "%s: get pixel clock rate %lu\n", + __func__, clk_get_rate(dcnano->pixel_clk)); +} + +static enum drm_mode_status +dcnano_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + dcnano_crtc_dbg(crtc, "validating mode " DRM_MODE_FMT "\n", + DRM_MODE_ARG(mode)); + + if (dcnano_crtc_find_pll_clock_rate(crtc, mode) == 0) + return MODE_NOCLOCK; + + return MODE_OK; +} + +static void dcnano_crtc_queue_state_event(struct drm_crtc *crtc) +{ + struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc); + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event) { + WARN_ON(drm_crtc_vblank_get(crtc)); + WARN_ON(dcnano->event); + dcnano->event = crtc->state->event; + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); +} + +static int dcnano_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); + bool has_primary = crtc_state->plane_mask & + drm_plane_mask(crtc->primary); + + if (crtc_state->active && !has_primary) + return -EINVAL; + + if (crtc_state->active_changed && crtc_state->active) + crtc_state->mode_changed = true; + + return 0; +} + +static void dcnano_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, + crtc); + + if (!crtc->state->active && !old_crtc_state->active) + return; + + if (!drm_atomic_crtc_needs_modeset(crtc->state)) + dcnano_crtc_queue_state_event(crtc); +} + +static void dcnano_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_device *drm = crtc->dev; + struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc); + struct drm_plane *plane; + struct drm_plane_state *new_plane_state; + struct drm_display_mode *adj = &crtc->state->adjusted_mode; + int i; + u32 primary_fb_fmt = 0; + u32 val; + + dcnano_crtc_dbg(crtc, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj)); + + dcnano_crtc_set_pixel_clock(crtc); + + /* enable power when we start to set mode for CRTC */ + pm_runtime_get_sync(drm->dev); + + if (dcnano->port == DCNANO_DPI_PORT) + dcnano_crtc_mode_set_nofb_dpi(crtc); + + drm_crtc_vblank_on(crtc); + + for_each_new_plane_in_state(state, plane, new_plane_state, i) { + if (!new_plane_state->fb) + continue; + + if (plane->type != DRM_PLANE_TYPE_PRIMARY) + continue; + + switch (new_plane_state->fb->format->format) { + case DRM_FORMAT_RGB565: + primary_fb_fmt = FBCFG_FORMAT_R5G6B5; + break; + case DRM_FORMAT_XRGB8888: + primary_fb_fmt = FBCFG_FORMAT_R8G8B8; + break; + } + } + + val = FBCFG_OUTPUT_ENABLE | primary_fb_fmt; + + /* enable DPI timing and start a DPI transfer, if needed */ + if (dcnano->port == DCNANO_DPI_PORT) + val |= FBCFG_RESET_ENABLE; + + dcnano_write(dcnano, DCNANO_FRAMEBUFFERCONFIG, val); + + dcnano_crtc_queue_state_event(crtc); +} + +static void dcnano_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_device *drm = crtc->dev; + struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc); + + /* simply write '0' to the framebuffer and timing control register */ + dcnano_write(dcnano, DCNANO_FRAMEBUFFERCONFIG, 0); + + drm_crtc_vblank_off(crtc); + + /* disable power when CRTC is disabled */ + pm_runtime_put_sync(drm->dev); + + clk_disable_unprepare(dcnano->pixel_clk); + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event && !crtc->state->active) { + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); +} + +static bool +dcnano_crtc_get_scanout_position(struct drm_crtc *crtc, + bool in_vblank_irq, + int *vpos, int *hpos, + ktime_t *stime, ktime_t *etime, + const struct drm_display_mode *mode) +{ + struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc); + int hdisplay = mode->crtc_hdisplay; + int htotal = mode->crtc_htotal; + int vdisplay = mode->crtc_vdisplay; + int vtotal = mode->crtc_vtotal; + int x, y; + u32 val; + bool reliable; + + if (stime) + *stime = ktime_get(); + + val = dcnano_read(dcnano, DCNANO_DISPLAYCURRENTLOCATION); + + x = CURRENTLOCATION_X_GET(val); + y = CURRENTLOCATION_Y_GET(val); + + if (x < hdisplay) + *hpos = x + 1; /* active scanout area - positive */ + else + *hpos = x - (htotal - 1); /* inside vblank - negative */ + + if (y < vdisplay) + *vpos = y + 1; /* active scanout area - positive */ + else + *vpos = y - (vtotal - 1); /* inside vblank - negative */ + + reliable = true; + + if (etime) + *etime = ktime_get(); + + return reliable; +} + +static const struct drm_crtc_helper_funcs dcnano_crtc_helper_funcs = { + .mode_valid = dcnano_crtc_mode_valid, + .atomic_check = dcnano_crtc_atomic_check, + .atomic_flush = dcnano_crtc_atomic_flush, + .atomic_enable = dcnano_crtc_atomic_enable, + .atomic_disable = dcnano_crtc_atomic_disable, + .get_scanout_position = dcnano_crtc_get_scanout_position, +}; + +static u32 dcnano_crtc_get_vblank_counter(struct drm_crtc *crtc) +{ + struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc); + + dcnano_write(dcnano, DCNANO_DEBUGCOUNTERSELECT, TOTAL_FRAME_CNT); + return dcnano_read(dcnano, DCNANO_DEBUGCOUNTERVALUE); +} + +static int dcnano_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc); + + dcnano_write(dcnano, DCNANO_DISPLAYINTRENABLE, DISPLAYINTR_DISP0); + + return 0; +} + +static void dcnano_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc); + + dcnano_write(dcnano, DCNANO_DISPLAYINTRENABLE, 0); +} + +static const struct drm_crtc_funcs dcnano_crtc_funcs = { + .reset = drm_atomic_helper_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .get_vblank_counter = dcnano_crtc_get_vblank_counter, + .enable_vblank = dcnano_crtc_enable_vblank, + .disable_vblank = dcnano_crtc_disable_vblank, + .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, +}; + +irqreturn_t dcnano_irq_handler(int irq, void *data) +{ + struct drm_device *drm = data; + struct dcnano_dev *dcnano = to_dcnano_dev(drm); + unsigned long flags; + + /* DCNANO_DISPLAYINTR will automatically clear after a read. */ + dcnano_read(dcnano, DCNANO_DISPLAYINTR); + + drm_crtc_handle_vblank(&dcnano->crtc); + + spin_lock_irqsave(&drm->event_lock, flags); + if (dcnano->event) { + drm_crtc_send_vblank_event(&dcnano->crtc, dcnano->event); + dcnano->event = NULL; + drm_crtc_vblank_put(&dcnano->crtc); + } + spin_unlock_irqrestore(&drm->event_lock, flags); + + return IRQ_HANDLED; +} + +static int dcnano_get_pll_clock(struct dcnano_dev *dcnano) +{ + int ret, i; + + /* + * There are one divider clock and gate clock between the pixel + * clock and the pll clock, so walk through the clock tree 3 levels + * up to get the pll clock. + */ + dcnano->pll_clk = dcnano->pixel_clk; + for (i = 0; i < 3; i++) { + dcnano->pll_clk = clk_get_parent(dcnano->pll_clk); + if (IS_ERR(dcnano->pll_clk)) { + ret = PTR_ERR(dcnano->pll_clk); + drm_err(&dcnano->base, + "failed to get pll clock: %d\n", ret); + return ret; + } else if (!dcnano->pll_clk) { + drm_err(&dcnano->base, "no pll clock\n"); + return -ENODEV; + } + } + + return 0; +} + +static void dcnano_reset_all_debug_counters(struct dcnano_dev *dcnano) +{ + struct drm_device *drm = &dcnano->base; + + pm_runtime_get_sync(drm->dev); + dcnano_write(dcnano, DCNANO_DEBUGCOUNTERSELECT, RESET_ALL_CNTS); + pm_runtime_put_sync(drm->dev); +} + +int dcnano_crtc_init(struct dcnano_dev *dcnano) +{ + int ret; + + ret = dcnano_plane_init(dcnano); + if (ret) + return ret; + + drm_crtc_helper_add(&dcnano->crtc, &dcnano_crtc_helper_funcs); + ret = drm_crtc_init_with_planes(&dcnano->base, &dcnano->crtc, + &dcnano->primary, NULL, + &dcnano_crtc_funcs, NULL); + if (ret) { + drm_err(&dcnano->base, "failed to initialize CRTC: %d\n", ret); + return ret; + } + + ret = dcnano_get_pll_clock(dcnano); + if (ret) + return ret; + + dcnano_reset_all_debug_counters(dcnano); + + return 0; +} diff --git a/drivers/gpu/drm/imx/dcnano/dcnano-drv.c b/drivers/gpu/drm/imx/dcnano/dcnano-drv.c new file mode 100644 index 000000000000..755021b6f31d --- /dev/null +++ b/drivers/gpu/drm/imx/dcnano/dcnano-drv.c @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Copyright 2020,2021 NXP + */ + +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "dcnano-drv.h" +#include "dcnano-reg.h" + +#define DRIVER_NAME "imx-dcnano-drm" + +static int legacyfb_depth = 32; +module_param(legacyfb_depth, uint, 0444); + +DEFINE_DRM_GEM_CMA_FOPS(dcnano_driver_fops); + +static struct drm_driver dcnano_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + DRM_GEM_CMA_DRIVER_OPS, + .fops = &dcnano_driver_fops, + .name = "imx-dcnano", + .desc = "i.MX DCNANO DRM graphics", + .date = "20201221", + .major = 1, + .minor = 0, + .patchlevel = 0, +}; + +static int dcnano_reset(struct dcnano_dev *dcnano) +{ + struct drm_device *drm = &dcnano->base; + int ret; + + pm_runtime_get_sync(drm->dev); + + ret = reset_control_assert(dcnano->tied_resets); + if (ret) { + DRM_DEV_ERROR(drm->dev, + "failed to assert tied resets: %d\n", ret); + goto err; + } + + /* + * 10 microseconds are enough for the 32-cycle(slowest clock) + * assertion duration. + */ + usleep_range(10, 20); + + ret = reset_control_deassert(dcnano->tied_resets); + if (ret) { + DRM_DEV_ERROR(drm->dev, + "failed to deassert tied resets: %d\n", ret); + goto err; + } + + /* + * 40 microseconds are enough for the 128-cycle(slowest clock) + * de-assertion duration. + */ + usleep_range(40, 50); + +err: + pm_runtime_put_sync(drm->dev); + return ret; +} + +static int dcnano_irq_install(struct drm_device *dev, int irq) +{ + if (irq == IRQ_NOTCONNECTED) + return -ENOTCONN; + + return request_irq(irq, dcnano_irq_handler, 0, dev->driver->name, dev); +} + +static void dcnano_irq_uninstall(struct drm_device *dev) +{ + struct dcnano_dev *dcnano = to_dcnano_dev(dev); + + free_irq(dcnano->irq, dev); +} + +static int dcnano_check_chip_info(struct dcnano_dev *dcnano) +{ + struct drm_device *drm = &dcnano->base; + u32 val; + int ret = 0; + + pm_runtime_get_sync(drm->dev); + + val = dcnano_read(dcnano, DCNANO_DCCHIPREV); + if (val != DCCHIPREV) { + DRM_DEV_ERROR(drm->dev, "invalid chip revision(0x%08x)\n", val); + ret = -ENODEV; + goto err; + } + DRM_DEV_DEBUG(drm->dev, "chip revision is 0x%08x\n", val); + + val = dcnano_read(dcnano, DCNANO_DCCHIPDATE); + if (val != DCCHIPDATE) { + DRM_DEV_ERROR(drm->dev, "invalid chip date(0x%08x)\n", val); + ret = -ENODEV; + goto err; + } + DRM_DEV_DEBUG(drm->dev, "chip date is 0x%08x\n", val); + + val = dcnano_read(dcnano, DCNANO_DCCHIPPATCHREV); + if (val != DCCHIPPATCHREV) { + DRM_DEV_ERROR(drm->dev, + "invalid chip patch revision(0x%08x)\n", val); + ret = -ENODEV; + goto err; + } + DRM_DEV_DEBUG(drm->dev, "chip patch revision is 0x%08x\n", val); +err: + pm_runtime_put_sync(drm->dev); + return ret; +} + +static int dcnano_probe(struct platform_device *pdev) +{ + struct dcnano_dev *dcnano; + struct drm_device *drm; + int ret; + + if (!pdev->dev.of_node) + return -ENODEV; + + dcnano = devm_drm_dev_alloc(&pdev->dev, &dcnano_driver, + struct dcnano_dev, base); + if (IS_ERR(dcnano)) + return PTR_ERR(dcnano); + + drm = &dcnano->base; + dev_set_drvdata(&pdev->dev, dcnano); + + dcnano->mmio_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dcnano->mmio_base)) + return PTR_ERR(dcnano->mmio_base); + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + dcnano->irq = ret; + + dcnano->axi_clk = devm_clk_get(drm->dev, "axi"); + if (IS_ERR(dcnano->axi_clk)) { + ret = PTR_ERR(dcnano->axi_clk); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(drm->dev, + "failed to get axi clk: %d\n", ret); + return ret; + } + + dcnano->ahb_clk = devm_clk_get(drm->dev, "ahb"); + if (IS_ERR(dcnano->ahb_clk)) { + ret = PTR_ERR(dcnano->ahb_clk); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(drm->dev, + "failed to get ahb clk: %d\n", ret); + return ret; + } + + dcnano->pixel_clk = devm_clk_get(drm->dev, "pixel"); + if (IS_ERR(dcnano->pixel_clk)) { + ret = PTR_ERR(dcnano->pixel_clk); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(drm->dev, + "failed to get pixel clk: %d\n", ret); + return ret; + } + + ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32)); + if (ret) { + DRM_DEV_ERROR(drm->dev, + "failed to set dma mask and coherent: %d\n", ret); + return ret; + } + + pm_runtime_enable(drm->dev); + + dcnano->tied_resets = devm_reset_control_get(drm->dev, NULL); + if (IS_ERR(dcnano->tied_resets)) { + ret = PTR_ERR(dcnano->tied_resets); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(drm->dev, + "failed to get tied resets: %d\n", ret); + goto err_reset_get; + } + + ret = dcnano_reset(dcnano); + if (ret) + goto err_dcnano_reset; + + pm_runtime_get_sync(drm->dev); + ret = dcnano_irq_install(drm, dcnano->irq); + pm_runtime_put_sync(drm->dev); + + if (ret < 0) { + DRM_DEV_ERROR(drm->dev, + "failed to install IRQ handler: %d\n", ret); + goto err_irq_install; + } + + ret = dcnano_check_chip_info(dcnano); + if (ret) + goto err_check_chip_info; + + ret = dcnano_kms_prepare(dcnano); + if (ret) + goto err_kms_prepare; + + ret = drm_dev_register(drm, 0); + if (ret) { + DRM_DEV_ERROR(drm->dev, + "failed to register drm device: %d\n", ret); + goto err_register; + } + + if (legacyfb_depth != 16 && legacyfb_depth != 32) { + DRM_DEV_INFO(drm->dev, + "Invalid legacyfb_depth. Defaulting to 32bpp\n"); + legacyfb_depth = 32; + } + + drm_fbdev_generic_setup(drm, legacyfb_depth); + + return 0; + +err_register: + drm_kms_helper_poll_fini(drm); +err_kms_prepare: +err_check_chip_info: + pm_runtime_get_sync(drm->dev); + dcnano_irq_uninstall(drm); + pm_runtime_put_sync(drm->dev); +err_irq_install: +err_dcnano_reset: +err_reset_get: + pm_runtime_disable(drm->dev); + return ret; +} + +static int dcnano_remove(struct platform_device *pdev) +{ + struct dcnano_dev *dcnano = dev_get_drvdata(&pdev->dev); + struct drm_device *drm = &dcnano->base; + + drm_dev_unregister(drm); + + drm_kms_helper_poll_fini(drm); + + drm_atomic_helper_shutdown(drm); + + pm_runtime_get_sync(drm->dev); + dcnano_irq_uninstall(drm); + pm_runtime_put_sync(drm->dev); + + pm_runtime_disable(drm->dev); + + return 0; +} + +static int __maybe_unused dcnano_suspend(struct device *dev) +{ + struct dcnano_dev *dcnano = dev_get_drvdata(dev); + + return drm_mode_config_helper_suspend(&dcnano->base); +} + +static int __maybe_unused dcnano_resume(struct device *dev) +{ + struct dcnano_dev *dcnano = dev_get_drvdata(dev); + + return drm_mode_config_helper_resume(&dcnano->base); +} + +static int __maybe_unused dcnano_runtime_suspend(struct device *dev) +{ + struct dcnano_dev *dcnano = dev_get_drvdata(dev); + + drm_dbg(&dcnano->base, "runtime suspend\n"); + + clk_disable_unprepare(dcnano->pixel_clk); + clk_disable_unprepare(dcnano->ahb_clk); + clk_disable_unprepare(dcnano->axi_clk); + + return 0; +} + +static int __maybe_unused dcnano_runtime_resume(struct device *dev) +{ + struct dcnano_dev *dcnano = dev_get_drvdata(dev); + int ret; + + drm_dbg(&dcnano->base, "runtime resume\n"); + + ret = clk_prepare_enable(dcnano->axi_clk); + if (ret) { + DRM_DEV_ERROR(dev, "failed to enable axi clock: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(dcnano->ahb_clk); + if (ret) { + DRM_DEV_ERROR(dev, "failed to enable ahb clock: %d\n", ret); + clk_disable_unprepare(dcnano->axi_clk); + return ret; + } + + /* + * Pixel clock has to be enabled for like DCNANO in i.MX8ulp, + * otherwise registers cannot be accessed. + */ + ret = clk_prepare_enable(dcnano->pixel_clk); + if (ret) { + DRM_DEV_ERROR(dev, "failed to enable pixel clock: %d\n", ret); + clk_disable_unprepare(dcnano->axi_clk); + clk_disable_unprepare(dcnano->ahb_clk); + return ret; + } + + return 0; +} + +static const struct dev_pm_ops dcnano_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dcnano_suspend, dcnano_resume) + SET_RUNTIME_PM_OPS(dcnano_runtime_suspend, dcnano_runtime_resume, NULL) +}; + +static const struct of_device_id dcnano_dt_ids[] = { + { .compatible = "nxp,imx8ulp-dcnano", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dcnano_dt_ids); + +static struct platform_driver dcnano_platform_driver = { + .probe = dcnano_probe, + .remove = dcnano_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = dcnano_dt_ids, + .pm = &dcnano_pm_ops, + }, +}; +module_platform_driver(dcnano_platform_driver); + +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_DESCRIPTION("i.MX DCNANO DRM driver"); +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/imx/dcnano/dcnano-drv.h b/drivers/gpu/drm/imx/dcnano/dcnano-drv.h new file mode 100644 index 000000000000..90903990df98 --- /dev/null +++ b/drivers/gpu/drm/imx/dcnano/dcnano-drv.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +/* + * Copyright 2020,2021 NXP + */ + +#ifndef __DCNANO_DRV_H__ +#define __DCNANO_DRV_H__ + +#include <linux/clk.h> +#include <linux/irqreturn.h> +#include <linux/kernel.h> +#include <linux/reset.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_encoder.h> +#include <drm/drm_plane.h> +#include <drm/drm_vblank.h> + +enum dcnano_port { + DCNANO_DPI_PORT, + DCNANO_DBI_PORT, + DCNANO_PORT_NUM, +}; + +struct dcnano_dev { + struct drm_device base; + void __iomem *mmio_base; + + unsigned int irq; + + struct clk *axi_clk; + struct clk *ahb_clk; + struct clk *pixel_clk; + struct clk *pll_clk; + + struct reset_control *tied_resets; + + struct drm_crtc crtc; + struct drm_plane primary; + struct drm_encoder encoder; + + struct drm_pending_vblank_event *event; + + enum dcnano_port port; +}; + +static inline struct dcnano_dev *to_dcnano_dev(struct drm_device *drm) +{ + return container_of(drm, struct dcnano_dev, base); +} + +irqreturn_t dcnano_irq_handler(int irq, void *data); + +int dcnano_crtc_init(struct dcnano_dev *dcnano); + +int dcnano_plane_init(struct dcnano_dev *dcnano); + +int dcnano_kms_prepare(struct dcnano_dev *dcnano); + +#endif diff --git a/drivers/gpu/drm/imx/dcnano/dcnano-kms.c b/drivers/gpu/drm/imx/dcnano/dcnano-kms.c new file mode 100644 index 000000000000..95808b522cdd --- /dev/null +++ b/drivers/gpu/drm/imx/dcnano/dcnano-kms.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Copyright 2020,2021 NXP + */ + +#include <linux/of.h> +#include <linux/of_graph.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_bridge_connector.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/drm_vblank.h> + +#include "dcnano-drv.h" +#include "dcnano-reg.h" + +static int dcnano_kms_init(struct dcnano_dev *dcnano) +{ + struct drm_device *drm = &dcnano->base; + struct drm_panel *panel; + struct drm_bridge *bridge; + struct drm_connector *connector; + struct device_node *np = drm->dev->of_node; + struct device_node *port, *ep, *remote; + struct of_endpoint endpoint; + u32 port_id; + bool found_ep = false; + int ret; + + ret = dcnano_crtc_init(dcnano); + if (ret) + return ret; + + for (port_id = 0; port_id < DCNANO_PORT_NUM; port_id++) { + port = of_graph_get_port_by_id(np, port_id); + if (!port) { + drm_err(drm, "failed to get output port%u\n", port_id); + return -EINVAL; + } + + for_each_child_of_node(port, ep) { + remote = of_graph_get_remote_port_parent(ep); + if (!remote || !of_device_is_available(remote) || + !of_device_is_available(remote->parent)) { + of_node_put(remote); + continue; + } + + of_node_put(remote); + + ret = of_graph_parse_endpoint(ep, &endpoint); + if (ret) { + drm_err(drm, + "failed to parse endpoint of port%u: %d\n", + port_id, ret); + of_node_put(ep); + of_node_put(port); + return ret; + } + + ret = drm_of_find_panel_or_bridge(np, + port_id, endpoint.id, + &panel, &bridge); + if (ret) { + if (ret == -ENODEV) { + drm_dbg(drm, + "no panel or bridge on port%u ep%d\n", + port_id, endpoint.id); + continue; + } else if (ret != -EPROBE_DEFER) { + drm_err(drm, + "failed to find panel or bridge on port%u ep%d: %d\n", + port_id, endpoint.id, ret); + } + of_node_put(ep); + of_node_put(port); + return ret; + } + + found_ep = true; + break; + } + + of_node_put(port); + dcnano->port = port_id; + + if (found_ep) { + drm_dbg(drm, "found valid endpoint%d @ port%u\n", + endpoint.id, port_id); + break; + } + } + + if (!found_ep) { + drm_info(drm, "no valid endpoint\n"); + return 0; + } + + if (panel) { + bridge = devm_drm_panel_bridge_add(drm->dev, panel); + if (IS_ERR(bridge)) { + ret = PTR_ERR(bridge); + drm_err(drm, + "failed to add panel bridge on port%u ep%d: %d\n", + port_id, endpoint.id, ret); + goto err; + } + } + + dcnano->encoder.possible_crtcs = drm_crtc_mask(&dcnano->crtc); + ret = drm_simple_encoder_init(drm, &dcnano->encoder, + DRM_MODE_ENCODER_NONE); + if (ret) { + drm_err(drm, "failed to initialize encoder on port%u ep%d: %d\n", + port_id, endpoint.id, ret); + goto err; + } + + ret = drm_bridge_attach(&dcnano->encoder, bridge, NULL, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) { + drm_err(drm, + "failed to attach bridge to encoder on port%u ep%d: %d\n", + port_id, endpoint.id, ret); + goto err; + } + + connector = drm_bridge_connector_init(drm, &dcnano->encoder); + if (IS_ERR(connector)) { + ret = PTR_ERR(connector); + drm_err(drm, + "failed to initialize bridge connector on port%u ep%d: %d\n", + port_id, endpoint.id, ret); + goto err; + } + + ret = drm_connector_attach_encoder(connector, &dcnano->encoder); + if (ret) + drm_err(drm, + "failed to attach encoder to connector on port%u ep%d: %d\n", + port_id, endpoint.id, ret); +err: + return ret; +} + +static const struct drm_mode_config_funcs dcnano_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static const struct drm_mode_config_helper_funcs dcnano_mode_config_helpers = { + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, +}; + +int dcnano_kms_prepare(struct dcnano_dev *dcnano) +{ + struct drm_device *drm = &dcnano->base; + int ret; + + ret = drmm_mode_config_init(drm); + if (ret) + return ret; + + ret = dcnano_kms_init(dcnano); + if (ret) + return ret; + + drm->mode_config.min_width = 32; + drm->mode_config.min_height = 32; + drm->mode_config.max_width = 1280; + drm->mode_config.max_height = 1280; + drm->mode_config.funcs = &dcnano_mode_config_funcs; + drm->mode_config.helper_private = &dcnano_mode_config_helpers; + drm->max_vblank_count = DEBUGCOUNTERVALUE_MAX; + + ret = drm_vblank_init(drm, 1); + if (ret < 0) { + drm_err(drm, "failed to initialize vblank: %d\n", ret); + return ret; + } + + drm_mode_config_reset(drm); + + drm_kms_helper_poll_init(drm); + + return 0; +} diff --git a/drivers/gpu/drm/imx/dcnano/dcnano-plane.c b/drivers/gpu/drm/imx/dcnano/dcnano-plane.c new file mode 100644 index 000000000000..d1e6062e89e8 --- /dev/null +++ b/drivers/gpu/drm/imx/dcnano/dcnano-plane.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Copyright 2020,2021 NXP + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> +#include <drm/drm_print.h> +#include <drm/drm_rect.h> + +#include "dcnano-drv.h" +#include "dcnano-reg.h" + +#define DCNANO_FB_PITCH_ALIGN 128 /* in byte */ + +#define dcnano_plane_dbg(plane, fmt, ...) \ + drm_dbg_kms((plane)->dev, "[PLANE:%d:%s] " fmt, \ + (plane)->base.id, (plane)->name, ##__VA_ARGS__) + +/* primary plane formats */ +static const u32 dcnano_primary_plane_formats[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_XRGB8888, +}; + +static unsigned int +dcnano_primary_plane_format_count = ARRAY_SIZE(dcnano_primary_plane_formats); + +static inline struct dcnano_dev *plane_to_dcnano_dev(struct drm_plane *plane) +{ + return to_dcnano_dev(plane->dev); +} + +static inline dma_addr_t +drm_plane_state_to_baseaddr(struct drm_plane_state *state) +{ + struct drm_framebuffer *fb = state->fb; + struct drm_gem_cma_object *cma_obj; + unsigned int x = state->src.x1 >> 16; + unsigned int y = state->src.y1 >> 16; + + cma_obj = drm_fb_cma_get_gem_obj(fb, 0); + + return cma_obj->paddr + fb->offsets[0] + fb->pitches[0] * y + + fb->format->cpp[0] * x; +} + +/***************************/ +/* primary plane functions */ +/***************************/ + +static int dcnano_primary_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, + plane); + struct dcnano_dev *dcnano = plane_to_dcnano_dev(plane); + struct drm_crtc_state *crtc_state; + struct drm_framebuffer *fb = new_state->fb; + struct drm_framebuffer *old_fb = old_state->fb; + u32 src_w; + unsigned int pitch_no_padding; + int ret; + + /* ok to disable */ + if (!fb) + return 0; + + crtc_state = drm_atomic_get_new_crtc_state(state, &dcnano->crtc); + if (WARN_ON(!crtc_state)) + return -EINVAL; + + ret = drm_atomic_helper_check_plane_state(new_state, crtc_state, + DRM_PLANE_HELPER_NO_SCALING, + DRM_PLANE_HELPER_NO_SCALING, + false, true); + if (ret) + return ret; + + if (fb->pitches[0] > FBSTRIDE_MAX) { + dcnano_plane_dbg(plane, "fb pitches[0] 0x%08x is out of range\n", + fb->pitches[0]); + return -EINVAL; + } + + /* + * The primary plane's stride value in register has to be 128byte + * aligned, _but_ no dedicated padding is allowed. + */ + src_w = drm_rect_width(&new_state->src) >> 16; + pitch_no_padding = fb->format->cpp[0] * src_w; + if (fb->pitches[0] != pitch_no_padding) { + dcnano_plane_dbg(plane, + "fb pitches[0] 0x%08x should be no padding - 0x%08x\n", + fb->pitches[0], pitch_no_padding); + return -EINVAL; + } + + /* + * Force CRTC mode change if framebuffer stride or pixel format + * are changed. + */ + if (old_fb && + (fb->pitches[0] != old_fb->pitches[0] || + fb->format->format != old_fb->format->format)) + crtc_state->mode_changed = true; + + return 0; +} + +static void dcnano_primary_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, + plane); + struct dcnano_dev *dcnano = plane_to_dcnano_dev(plane); + struct drm_framebuffer *fb = new_state->fb; + dma_addr_t baseaddr; + + if (!fb) { + dcnano_plane_dbg(plane, "no fb\n"); + return; + } + + baseaddr = drm_plane_state_to_baseaddr(new_state); + + dcnano_plane_dbg(plane, "fb address %pad, pitch 0x%08x\n", + &baseaddr, fb->pitches[0]); + + dcnano_write(dcnano, DCNANO_FRAMEBUFFERADDRESS, baseaddr); + + dcnano_write(dcnano, DCNANO_FRAMEBUFFERSTRIDE, + ALIGN(fb->pitches[0], DCNANO_FB_PITCH_ALIGN)); +} + +static const struct drm_plane_helper_funcs dcnano_primary_plane_helper_funcs = { + .prepare_fb = drm_gem_plane_helper_prepare_fb, + .atomic_check = dcnano_primary_plane_atomic_check, + .atomic_update = dcnano_primary_plane_atomic_update, +}; + +static const struct drm_plane_funcs dcnano_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +int dcnano_plane_init(struct dcnano_dev *dcnano) +{ + int ret; + + /* primary plane */ + drm_plane_helper_add(&dcnano->primary, + &dcnano_primary_plane_helper_funcs); + ret = drm_universal_plane_init(&dcnano->base, &dcnano->primary, 0, + &dcnano_plane_funcs, + dcnano_primary_plane_formats, + dcnano_primary_plane_format_count, + NULL, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + drm_err(&dcnano->base, + "failed to initialize primary plane: %d\n", ret); + + return ret; +} diff --git a/drivers/gpu/drm/imx/dcnano/dcnano-reg.h b/drivers/gpu/drm/imx/dcnano/dcnano-reg.h new file mode 100644 index 000000000000..aa31adcc2935 --- /dev/null +++ b/drivers/gpu/drm/imx/dcnano/dcnano-reg.h @@ -0,0 +1,438 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +/* + * Copyright 2020,2021 NXP + */ + +#ifndef _DCNANO_REG_H_ +#define _DCNANO_REG_H_ + +#include <linux/bitfield.h> +#include <linux/io.h> +#include <linux/sizes.h> + +#define DCNANO_FRAMEBUFFERCONFIG 0x1240 +/* double buffered */ +#define FBCFG_FORMAT_MASK GENMASK(2, 0) +#define FBCFG_FORMAT_NONE FIELD_PREP(FBCFG_FORMAT_MASK, 0) +#define FBCFG_FORMAT_R4G4B4 FIELD_PREP(FBCFG_FORMAT_MASK, 1) +#define FBCFG_FORMAT_R5G5B5 FIELD_PREP(FBCFG_FORMAT_MASK, 2) +#define FBCFG_FORMAT_R5G6B5 FIELD_PREP(FBCFG_FORMAT_MASK, 3) +#define FBCFG_FORMAT_R8G8B8 FIELD_PREP(FBCFG_FORMAT_MASK, 4) +/* double buffered */ +#define FBCFG_MODE_LINEAR 0 +#define FBCFG_MODE_TILE4X4 BIT(4) +/* double buffered */ +#define FBCFG_OUTPUT_MASK BIT(8) +#define FBCFG_OUTPUT_DISABLE 0 +#define FBCFG_OUTPUT_ENABLE BIT(8) +/* double buffered */ +#define FBCFG_SWITCHPANEL_DISABLE 0 +#define FBCFG_SWITCHPANEL_ENABLE BIT(9) +/* double buffered */ +#define FBCFG_GAMMA_DISABLE 0 +#define FBCFG_GAMMA_ENABLE BIT(12) +#define FBCFG_VALID_WORKING 0 +#define FBCFG_VALID_PENDING BIT(16) +#define FBCFG_RESET_MASK BIT(20) +#define FBCFG_RESET_DISABLE 0 +#define FBCFG_RESET_ENABLE BIT(20) +#define FBCFG_UNDERFLOW_NO 0 +#define FBCFG_UNDERFLOW_YES BIT(24) +#define FBCFG_FLIP_INPROGRSS_NO 0 +#define FBCFG_FLIP_INPROGRSS_YES BIT(28) +#define FBCFG_BACK_PRES_DISABLE_NO 0 +#define FBCFG_BACK_PRES_DISABLE_YES BIT(29) + +/* double buffered */ +#define DCNANO_FRAMEBUFFERADDRESS 0x1260 +#define FBADDRESS_MASK GENMASK(31, 0) +#define FBADDRESS(x) FIELD_PREP(FBADDRESS_MASK, (x)) +#define FBADDRESS_TYPE_SYSTEM 0 +#define FBADDRESS_TYPE_VIRTUAL BIT(31) + +/* double buffered */ +#define DCNANO_FRAMEBUFFERSTRIDE 0x1280 +#define FBSTRIDE_MASK GENMASK(16, 0) +#define FBSTRIDE(x) FIELD_PREP(FBSTRIDE_MASK, (x)) +#define FBSTRIDE_MAX FIELD_MAX(FBSTRIDE_MASK) + +#define DCNANO_DISPLAYDITHERCONFIG 0x1360 +#define DITHERCFG_BLUESIZE_MASK GENMASK(3, 0) +#define DITHERCFG_BLUESIZE(x) FIELD_PREP(DITHERCFG_BLUESIZE_MASK, (x)) +#define DITHERCFG_GREENSIZE_MASK GENMASK(11, 8) +#define DITHERCFG_GREENSIZE(x) FIELD_PREP(DITHERCFG_GREENSIZE_MASK, (x)) +#define DITHERCFG_REDSIZE_MASK GENMASK(19, 16) +#define DITHERCFG_REDSIZE(x) FIELD_PREP(DITHERCFG_REDSIZE_MASK, (x)) +#define DITHERCFG_DISABLE 0 +/* double buffered */ +#define DITHERCFG_ENABLE BIT(31) + +#define DCNANO_DISPLAYDITHERTABLELOW 0x1380 +#define DITHERTLB_LOW_Y0X0_MASK GENMASK(3, 0) +#define DITHERTLB_LOW_Y0X0(x) FIELD_PREP(DITHERTLB_LOW_Y0X0_MASK, (x)) +#define DITHERTLB_LOW_Y0X1_MASK GENMASK(7, 4) +#define DITHERTLB_LOW_Y0X1(x) FIELD_PREP(DITHERTLB_LOW_Y0X1_MASK, (x)) +#define DITHERTLB_LOW_Y0X2_MASK GENMASK(11, 8) +#define DITHERTLB_LOW_Y0X2(x) FIELD_PREP(DITHERTLB_LOW_Y0X2_MASK, (x)) +#define DITHERTLB_LOW_Y0X3_MASK GENMASK(15, 12) +#define DITHERTLB_LOW_Y0X3(x) FIELD_PREP(DITHERTLB_LOW_Y0X3_MASK, (x)) +#define DITHERTLB_LOW_Y1X0_MASK GENMASK(19, 16) +#define DITHERTLB_LOW_Y1X0(x) FIELD_PREP(DITHERTLB_LOW_Y1X0_MASK, (x)) +#define DITHERTLB_LOW_Y1X1_MASK GENMASK(23, 20) +#define DITHERTLB_LOW_Y1X1(x) FIELD_PREP(DITHERTLB_LOW_Y1X1_MASK, (x)) +#define DITHERTLB_LOW_Y1X2_MASK GENMASK(27, 24) +#define DITHERTLB_LOW_Y1X2(x) FIELD_PREP(DITHERTLB_LOW_Y1X2_MASK, (x)) +#define DITHERTLB_LOW_Y1X3_MASK GENMASK(31, 28) +#define DITHERTLB_LOW_Y1X3(x) FIELD_PREP(DITHERTLB_LOW_Y1X3_MASK, (x)) + +#define DCNANO_DISPLAYDITHERTABLEHIGH 0x13a0 +#define DITHERTLB_HIGH_Y2X0_MASK GENMASK(3, 0) +#define DITHERTLB_HIGH_Y2X0(x) FIELD_PREP(DITHERTLB_HIGH_Y2X0_MASK, (x)) +#define DITHERTLB_HIGH_Y2X1_MASK GENMASK(7, 4) +#define DITHERTLB_HIGH_Y2X1(x) FIELD_PREP(DITHERTLB_HIGH_Y2X1_MASK, (x)) +#define DITHERTLB_HIGH_Y2X2_MASK GENMASK(11, 8) +#define DITHERTLB_HIGH_Y2X2(x) FIELD_PREP(DITHERTLB_HIGH_Y2X2_MASK, (x)) +#define DITHERTLB_HIGH_Y2X3_MASK GENMASK(15, 12) +#define DITHERTLB_HIGH_Y2X3(x) FIELD_PREP(DITHERTLB_HIGH_Y2X3_MASK, (x)) +#define DITHERTLB_HIGH_Y3X0_MASK GENMASK(19, 16) +#define DITHERTLB_HIGH_Y3X0(x) FIELD_PREP(DITHERTLB_HIGH_Y3X0_MASK, (x)) +#define DITHERTLB_HIGH_Y3X1_MASK GENMASK(23, 20) +#define DITHERTLB_HIGH_Y3X1(x) FIELD_PREP(DITHERTLB_HIGH_Y3X1_MASK, (x)) +#define DITHERTLB_HIGH_Y3X2_MASK GENMASK(27, 24) +#define DITHERTLB_HIGH_Y3X2(x) FIELD_PREP(DITHERTLB_HIGH_Y3X2_MASK, (x)) +#define DITHERTLB_HIGH_Y3X3_MASK GENMASK(31, 28) +#define DITHERTLB_HIGH_Y3X3(x) FIELD_PREP(DITHERTLB_HIGH_Y3X3_MASK, (x)) + +#define DCNANO_PANELCONFIG 0x13c0 +#define PANELCFG_DE_DISABLE 0 +#define PANELCFG_DE_ENABLE BIT(0) +#define PANELCFG_DE_POL_POSITIVE 0 +#define PANELCFG_DE_POL_NEGATIVE BIT(1) +/* double buffered? */ +#define PANELCFG_DATA_DISABLE 0 +#define PANELCFG_DATA_ENABLE BIT(4) +#define PANELCFG_DATA_POL_POSITIVE 0 +#define PANELCFG_DATA_POL_NEGATIVE BIT(5) +#define PANELCFG_CLOCK_DISABLE 0 +#define PANELCFG_CLOCK_ENABLE BIT(8) +#define PANELCFG_CLOCK_POL_POSITIVE 0 +#define PANELCFG_CLOCK_POL_NEGATIVE BIT(9) +#define PANELCFG_SEQUENCING_HARDWARE 0 +#define PANELCFG_SEQUENCING_SOFTWARE BIT(31) + +#define DCNANO_PANELTIMING 0x13e0 +#define PANELTIMING_POWER_ENABLE_MASK GENMASK(3, 0) +#define PANELTIMING_POWER_ENABLE(x) \ + FIELD_PREP(PANELTIMING_POWER_ENABLE_MASK, (x)) +#define PANELTIMING_BACKLIGHT_ENABLE_MASK GENMASK(7, 4) +#define PANELTIMING_BACKLIGHT_ENABLE(x) \ + FIELD_PREP(PANELTIMING_BACKLIGHT_ENABLE_MASK, (x)) +#define PANELTIMING_CLOCK_ENABLE_MASK GENMASK(11, 8) +#define PANELTIMING_CLOCK_ENABLE(x) \ + FIELD_PREP(PANELTIMING_CLOCK_ENABLE_MASK, (x)) +#define PANELTIMING_DATA_ENABLE_MASK GENMASK(15, 12) +#define PANELTIMING_DATA_ENABLE(x) \ + FIELD_PREP(PANELTIMING_DATA_ENABLE_MASK, (x)) +#define PANELTIMING_DATA_DISABLE_MASK GENMASK(19, 16) +#define PANELTIMING_DATA_DISABLE(x) \ + FIELD_PREP(PANELTIMING_DATA_DISABLE_MASK, (x)) +#define PANELTIMING_CLOCK_DISABLE_MASK GENMASK(23, 20) +#define PANELTIMING_CLOCK_DISABLE(x) \ + FIELD_PREP(PANELTIMING_CLOCK_DISABLE_MASK, (x)) +#define PANELTIMING_BACKLIGHT_DISABLE_MASK GENMASK(27, 24) +#define PANELTIMING_BACKLIGHT_DISABLE(x) \ + FIELD_PREP(PANELTIMING_BACKLIGHT_DISABLE_MASK, (x)) +#define PANELTIMING_POWER_DISABLE_MASK GENMASK(31, 28) +#define PANELTIMING_POWER_DISABLE(x) \ + FIELD_PREP(PANELTIMING_POWER_DISABLE_MASK, (x)) + +#define DCNANO_HDISPLAY 0x1400 +#define HDISPLAY_END_MASK GENMASK(12, 0) +#define HDISPLAY_END(x) FIELD_PREP(HDISPLAY_END_MASK, (x)) +#define HDISPLAY_TOTAL_MASK GENMASK(28, 16) +#define HDISPLAY_TOTAL(x) FIELD_PREP(HDISPLAY_TOTAL_MASK, (x)) + +#define DCNANO_HSYNC 0x1420 +#define HSYNC_START_MASK GENMASK(12, 0) +#define HSYNC_START(x) FIELD_PREP(HSYNC_START_MASK, (x)) +#define HSYNC_END_MASK GENMASK(28, 16) +#define HSYNC_END(x) FIELD_PREP(HSYNC_END_MASK, (x)) +/* double buffered? */ +#define HSYNC_PULSE_DISABLE 0 +#define HSYNC_PULSE_ENABLE BIT(30) +#define HSYNC_POL_MASK BIT(31) +#define HSYNC_POL_POSITIVE 0 +#define HSYNC_POL_NEGATIVE BIT(31) + +#define DCNANO_VDISPLAY 0x1480 +#define VDISPLAY_END_MASK GENMASK(11, 0) +#define VDISPLAY_END(x) FIELD_PREP(VDISPLAY_END_MASK, (x)) +#define VDISPLAY_TOTAL_MASK GENMASK(27, 16) +#define VDISPLAY_TOTAL(x) FIELD_PREP(VDISPLAY_TOTAL_MASK, (x)) + +#define DCNANO_VSYNC 0x14a0 +#define VSYNC_START_MASK GENMASK(11, 0) +#define VSYNC_START(x) FIELD_PREP(VSYNC_START_MASK, (x)) +#define VSYNC_END_MASK GENMASK(27, 16) +#define VSYNC_END(x) FIELD_PREP(VSYNC_END_MASK, (x)) +/* double buffered? */ +#define VSYNC_PULSE_DISABLE 0 +#define VSYNC_PULSE_ENABLE BIT(30) +#define VSYNC_POL_MASK BIT(31) +#define VSYNC_POL_POSITIVE 0 +#define VSYNC_POL_NEGATIVE BIT(31) + +#define DCNANO_DISPLAYCURRENTLOCATION 0x14c0 +#define CURRENTLOCATION_X_MASK GENMASK(15, 0) +#define CURRENTLOCATION_X(x) FIELD_PREP(CURRENTLOCATION_X_MASK, (x)) +#define CURRENTLOCATION_X_GET(x) FIELD_GET(CURRENTLOCATION_X_MASK, (x)) +#define CURRENTLOCATION_Y_MASK GENMASK(31, 16) +#define CURRENTLOCATION_Y(x) FIELD_PREP(CURRENTLOCATION_Y_MASK, (x)) +#define CURRENTLOCATION_Y_GET(x) FIELD_GET(CURRENTLOCATION_Y_MASK, (x)) + +#define DCNANO_GAMMAINDEX 0x14e0 +#define GAMMAINDEX_MASK GENMASK(7, 0) +#define GAMMAINDEX(x) FIELD_PREP(GAMMAINDEX_MASK, (x)) + +#define DCNANO_GAMMADATA 0x1500 +#define GAMMADATA_BLUE_MASK GENMASK(7, 0) +#define GAMMADATA_BLUE(x) FIELD_PREP(GAMMADATA_BLUE_MASK, (x)) +#define GAMMADATA_GREEN_MASK GENMASK(15, 8) +#define GAMMADATA_GREEN(x) FIELD_PREP(GAMMADATA_GREEN_MASK, (x)) +#define GAMMADATA_RED_MASK GENMASK(23, 16) +#define GAMMADATA_RED(x) FIELD_PREP(GAMMADATA_RED_MASK, (x)) + +#define DCNANO_CURSORCONFIG 0x1520 +/* double buffered */ +#define CURSORCFG_FORMAT_MASK GENMASK(1, 0) +#define CURSORCFG_FORMAT_NONE FIELD_PREP(CURSORCFG_FORMAT_MASK, 0) +#define CURSORCFG_FORMAT_MASKED FIELD_PREP(CURSORCFG_FORMAT_MASK, 1) +#define CURSORCFG_FORMAT_A8R8G8B8 FIELD_PREP(CURSORCFG_FORMAT_MASK, 2) +#define CURSORCFG_DISPLAY_MASK BIT(4) +#define CURSORCFG_DISPLAY0 0 +#define CURSORCFG_DISPLAY1 BIT(4) +/* double buffered */ +#define CURSORCFG_HOTSPOT_Y_MASK GENMASK(12, 8) +#define CURSORCFG_HOTSPOT_Y(x) FIELD_PREP(CURSORCFG_HOTSPOT_Y_MASK, 0) +/* double buffered */ +#define CURSORCFG_HOTSPOT_X_MASK GENMASK(20, 16) +#define CURSORCFG_HOTSPOT_X(x) FIELD_PREP(CURSORCFG_HOTSPOT_X_MASK, 0) +#define CURSORCFG_FLIP_INPROGRSS_NO 0 +#define CURSORCFG_FLIP_INPROGRSS_YES BIT(31) + +/* double buffered */ +#define DCNANO_CURSORADDRESS 0x1530 +#define CURSORADDRESS_MASK GENMASK(31, 0) +#define CURSORADDRESS(x) FIELD_PREP(CURSORADDRESS_MASK, (x)) +#define CURSORADDRESS_TYPE_SYSTEM 0 +#define CURSORADDRESS_TYPE_VIRTUAL BIT(31) + +/* double buffered */ +#define DCNANO_CURSORLOCATION 0x1540 +#define CURSORLOCATION_X_MASK GENMASK(12, 0) +#define CURSORLOCATION_X(x) FIELD_PREP(CURSORLOCATION_X_MASK, (x)) +#define CURSORLOCATION_X_MAX FIELD_MAX(CURSORLOCATION_X_MASK) +#define CURSORLOCATION_Y_MASK GENMASK(27, 16) +#define CURSORLOCATION_Y(x) FIELD_PREP(CURSORLOCATION_Y_MASK, (x)) +#define CURSORLOCATION_Y_MAX FIELD_MAX(CURSORLOCATION_Y_MASK) + +/* double buffered */ +#define DCNANO_CURSORBACKGROUND 0x1550 +/* double buffered */ +#define DCNANO_CURSORFOREGROUND 0x1560 +#define CURSOR_BLUE_MASK GENMASK(7, 0) +#define CURSOR_BLUE(x) FIELD_PREP(CURSOR_BLUE_MASK, (x)) +#define CURSOR_GREEN_MASK GENMASK(15, 8) +#define CURSOR_GREEN(x) FIELD_PREP(CURSOR_GREEN_MASK, (x)) +#define CURSOR_RED_MASK GENMASK(23, 16) +#define CURSOR_RED(x) FIELD_PREP(CURSOR_RED_MASK, (x)) + +#define DCNANO_DISPLAYINTR 0x1600 +#define DCNANO_DISPLAYINTRENABLE 0x1610 +#define DISPLAYINTR_DISP0 BIT(0) + +#define DCNANO_DBICONFIG 0x1620 +#define DBICFG_DBI_TYPE_MASK GENMASK(1, 0) +#define DBICFG_DBI_TYPE_A_FIXED_E FIELD_PREP(DBICFG_DBI_TYPE_MASK, 0) +#define DBICFG_DBI_TYPE_A_CLOCK_E FIELD_PREP(DBICFG_DBI_TYPE_MASK, 1) +#define DBICFG_DBI_TYPE_B FIELD_PREP(DBICFG_DBI_TYPE_MASK, 2) +#define DBICFG_DBI_TYPE_C FIELD_PREP(DBICFG_DBI_TYPE_MASK, 3) +#define DBICFG_DATA_FORMAT_MASK GENMASK(5, 2) + +/* 8bit data bus - D[7 : 0] */ +/* 8bpp */ +#define DBICFG_DATA_FORMAT_D8R3G3B2 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 0) +/* 12bpp */ +#define DBICFG_DATA_FORMAT_D8R4G4B4 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 1) +/* 16bpp */ +#define DBICFG_DATA_FORMAT_D8R5G6B5 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 2) +/* 18bpp */ +#define DBICFG_DATA_FORMAT_D8R6G6B6 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 3) +/* 24bpp */ +#define DBICFG_DATA_FORMAT_D8R8G8B8 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 4) + +/* 9bit data bus - D[8 : 0] */ +/* 18bpp */ +#define DBICFG_DATA_FORMAT_D9R6G6B6 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 5) + +/* 16bit data bus - D[15 : 0] */ +/* 8bpp */ +#define DBICFG_DATA_FORMAT_D16R3G3B2 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 6) +/* 12bpp */ +#define DBICFG_DATA_FORMAT_D16R4G4B4 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 7) +/* 16bpp */ +#define DBICFG_DATA_FORMAT_D16R5G6B5 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 8) +/* 18bpp */ +#define DBICFG_DATA_FORMAT_D16R6G6B6OP1 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 9) +#define DBICFG_DATA_FORMAT_D16R6G6B6OP2 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 10) +/* 24bpp */ +#define DBICFG_DATA_FORMAT_D16R8G8B8OP1 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 11) +#define DBICFG_DATA_FORMAT_D16R8G8B8OP2 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 12) + +#define DBICFG_BUS_OUTPUT_SEL_DPI 0 +#define DBICFG_BUS_OUTPUT_SEL_DBI BIT(6) +#define DBICFG_DBIX_POLARITY_DEFAULT 0 +#define DBICFG_DBIX_POLARITY_REVERSE BIT(7) +#define DBICFG_DBI_AC_TIME_UNIT_MASK GENMASK(11, 8) +#define DBICFG_DBI_AC_TIME_UNIT(x) \ + FIELD_PREP(DBICFG_DBI_AC_TIME_UNIT_MASK, (x)) +#define DBICFG_DBI_TYPEC_OPT_MASK GENMASK(13, 12) +#define DBICFG_DBI_TYPEC_OPT1 FIELD_PREP(DBICFG_DBI_TYPEC_OPT_MASK, 0) +#define DBICFG_DBI_TYPEC_OPT2 FIELD_PREP(DBICFG_DBI_TYPEC_OPT_MASK, 1) +#define DBICFG_DBI_TYPEC_OPT3 FIELD_PREP(DBICFG_DBI_TYPEC_OPT_MASK, 2) + +#define DCNANO_DBIIFRESET 0x1640 +#define DBIIF_LEVEL_NO_RESET 0 +#define DBIIF_LEVEL_RESET BIT(0) + +#define DCNANO_DBIWRCHAR1 0x1660 +#define DBIWR_PERIOD_MASK GENMASK(7, 0) +#define DBIWR_PERIOD(x) FIELD_PREP(DBIWR_PERIOD_MASK, (x)) +#define DBIWR_EOR_WR_ASSERT_MASK GENMASK(11, 8) +#define DBIWR_EOR_WR_ASSERT(x) FIELD_PREP(DBIWR_EOR_WR_ASSERT_MASK, (x)) +#define DBIWR_CS_ASSERT_MASK GENMASK(15, 12) +#define DBIWR_CS_ASSERT(x) FIELD_PREP(DBIWR_CS_ASSERT_MASK, (x)) + +#define DCNANO_DBIWRCHAR2 0x1680 +#define DBIWR_EOR_WR_DE_ASRT_MASK GENMASK(7, 0) +#define DBIWR_EOR_WR_DE_ASRT(x) \ + FIELD_PREP(DBIWR_EOR_WR_DE_ASRT_MASK, (x)) +#define DBIWR_CS_DE_ASRT_MASK GENMASK(15, 8) +#define DBIWR_CS_DE_ASRT(x) FIELD_PREP(DBIWR_CS_DE_ASRT_MASK, (x)) + +#define DCNANO_DBICMD 0x16a0 +#define DBICMD_WORD_MASK GENMASK(15, 0) +#define DBICMD_WORD(x) FIELD_PREP(DBICMD_WORD_MASK, (x)) +#define DBICMD_FLAG_MASK GENMASK(31, 30) +#define DBICMD_FLAG_ADDRESS FIELD_PREP(DBICMD_FLAG_MASK, 0) +#define DBICMD_FLAG_WRITE_MEM_START FIELD_PREP(DBICMD_FLAG_MASK, 1) +#define DBICMD_FLAG_PARAMETER_OR_DATA FIELD_PREP(DBICMD_FLAG_MASK, 2) +/* Read is unused. */ +#define DBICMD_FLAG_READ FIELD_PREP(DBICMD_FLAG_MASK, 3) + +#define DCNANO_DPICONFIG 0x16c0 +#define DPICFG_DATA_FORMAT_MASK GENMASK(2, 0) +#define DPICFG_DATA_FORMAT_D16CFG1 FIELD_PREP(DPICFG_DATA_FORMAT_MASK, 0) +#define DPICFG_DATA_FORMAT_D16CFG2 FIELD_PREP(DPICFG_DATA_FORMAT_MASK, 1) +#define DPICFG_DATA_FORMAT_D16CFG3 FIELD_PREP(DPICFG_DATA_FORMAT_MASK, 2) +#define DPICFG_DATA_FORMAT_D18CFG1 FIELD_PREP(DPICFG_DATA_FORMAT_MASK, 3) +#define DPICFG_DATA_FORMAT_D18CFG2 FIELD_PREP(DPICFG_DATA_FORMAT_MASK, 4) +#define DPICFG_DATA_FORMAT_D24 FIELD_PREP(DPICFG_DATA_FORMAT_MASK, 5) + +#define DCNANO_DCCHIPREV 0x16f0 +#define DCCHIPREV_MASK GENMASK(31, 0) +#define DCCHIPREV 0x00005543 + +#define DCNANO_DCCHIPDATE 0x1700 +#define DCCHIPDATE_MASK GENMASK(31, 0) +#define DCCHIPDATE 0x20180612 + +#define DCNANO_DCCHIPPATCHREV 0x1720 +#define DCCHIPPATCHREV_MASK GENMASK(31, 0) +#define DCCHIPPATCHREV 0x00000003 + +#define DCNANO_DCTILEINCFG 0x1740 +/* double buffered */ +#define DCTILEINCFG_TILE_FORMAT_MASK GENMASK(1, 0) +#define DCTILEINCFG_TILE_FORMAT_NONE \ + FIELD_PREP(DCTILEINCFG_TILE_FORMAT_MASK, 0) +#define DCTILEINCFG_TILE_FORMAT_ARGB8888 \ + FIELD_PREP(DCTILEINCFG_TILE_FORMAT_MASK, 1) +#define DCTILEINCFG_TILE_FORMAT_YUY2 \ + FIELD_PREP(DCTILEINCFG_TILE_FORMAT_MASK, 2) +#define DCTILEINCFG_TILE_FORMAT_NV12 \ + FIELD_PREP(DCTILEINCFG_TILE_FORMAT_MASK, 3) +#define DCTILEINCFG_YUV_STANDARD_MASK GENMASK(3, 2) +/* double buffered */ +#define DCTILEINCFG_YUV_BT601 \ + FIELD_PREP(DCTILEINCFG_YUV_STANDARD_MASK, 0) +#define DCTILEINCFG_YUV_BT709 \ + FIELD_PREP(DCTILEINCFG_YUV_STANDARD_MASK, 1) +/* double buffered */ +#define DCTILEINCFG_YUV2_RGB_EN_MASK BIT(4) +#define DCTILEINCFG_YUV2_RGB_ENABLE BIT(4) +#define DCTILEINCFG_YUV2_RGB_DISABLE 0 +#define DCTILEINCFG_CFG_MODE_EN BIT(5) +#define DCTILEINCFG_CFG_MODE_ENABLE BIT(5) +#define DCTILEINCFG_CFG_MODE_DISABLE 0 + +/* double buffered */ +#define DCNANO_DCTILEUVFRAMEBUFFERADR 0x1760 +#define DCTILEUVFB_ADDRESS_MASK GENMASK(31, 0) +#define DCTILEUVFB_ADDRESS(x) FIELD_PREP(DCTILEUVFB_ADDRESS_MASK, (x)) +#define DCTILEUVFB_ADDRESS_MAX FIELD_MAX(DCTILEUVFB_ADDRESS_MASK) + +/* double buffered */ +#define DCNANO_DCTILEUVFRAMEBUFFERSTR 0x1780 +#define DCTILEUVFB_STRIDE_MASK GENMASK(15, 0) +#define DCTILEUVFB_STRIDE(x) FIELD_PREP(DCTILEUVFB_STRIDE_MASK, (x)) +#define DCTILEUVFB_STRIDE_MAX FIELD_MAX(DCTILEUVFB_STRIDE_MASK) + +#define DCNANO_DCPRODUCTID 0x17b0 +#define DCPRODUCTID_MASK GENMASK(31, 0) +#define DCPRODUCTID 0x02000361 + +#define DCNANO_DCSTATUS 0x1800 +#define DCSTATUS_DBI_TYPEC_FIFO_FULL BIT(0) + +#define DCNANO_DEBUGCOUNTERSELECT 0x1820 +#define DEBUGCOUNTERSELECT_MASK GENMASK(7, 0) +#define TOTAL_AXI_RD_REQ_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 0) +#define TOTAL_AXI_RD_LAST_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 1) +#define TOTAL_AXI_REQ_BURST_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 2) +#define TOTAL_AXI_RD_BURST_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 3) +#define TOTAL_PIXEL_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 4) +#define TOTAL_FRAME_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 5) +#define TOTAL_INPUT_DBI_CMD_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 6) +#define TOTAL_OUTPUT_DBI_CMD_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 7) +#define DEBUG_SIGNALS0 FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 8) +#define RESET_ALL_CNTS FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 0xFF) + +#define DCNANO_DEBUGCOUNTERVALUE 0x1840 +#define DEBUGCOUNTERVALUE_MASK GENMASK(31, 0) +#define DEBUGCOUNTERVALUE(x) FIELD_PREP(DEBUGCOUNTERVALUE_MASK, (x)) +#define DEBUGCOUNTERVALUE_MAX FIELD_MAX(DEBUGCOUNTERVALUE_MASK) + +static inline u32 dcnano_read(struct dcnano_dev *dcnano, unsigned int reg) +{ + return readl(dcnano->mmio_base + reg); +} + +static inline void dcnano_write(struct dcnano_dev *dcnano, + unsigned int reg, u32 value) +{ + writel(value, dcnano->mmio_base + reg); +} + +static inline void dcnano_write_mask(struct dcnano_dev *dcnano, + unsigned int reg, u32 mask, u32 value) +{ + u32 tmp; + + tmp = dcnano_read(dcnano, reg); + tmp &= ~mask; + dcnano_write(dcnano, reg, tmp | value); +} + +#endif diff --git a/drivers/gpu/drm/imx/dcss/Kconfig b/drivers/gpu/drm/imx/dcss/Kconfig index 2b17a964ff05..2c5e159dab8f 100644 --- a/drivers/gpu/drm/imx/dcss/Kconfig +++ b/drivers/gpu/drm/imx/dcss/Kconfig @@ -1,5 +1,6 @@ config DRM_IMX_DCSS tristate "i.MX8MQ DCSS" + depends on DRM && OF select IMX_IRQSTEER select DRM_KMS_CMA_HELPER select VIDEOMODE_HELPERS diff --git a/drivers/gpu/drm/imx/dcss/Makefile b/drivers/gpu/drm/imx/dcss/Makefile index 8c7c8da42792..eb3a1860edd0 100644 --- a/drivers/gpu/drm/imx/dcss/Makefile +++ b/drivers/gpu/drm/imx/dcss/Makefile @@ -1,6 +1,7 @@ imx-dcss-objs := dcss-drv.o dcss-dev.o dcss-blkctl.o dcss-ctxld.o dcss-dtg.o \ dcss-ss.o dcss-dpr.o dcss-scaler.o dcss-kms.o dcss-crtc.o \ - dcss-plane.o + dcss-plane.o dcss-dec400d.o dcss-hdr10.o dcss-wrscl.o \ + dcss-rdsrc.o dcss-dtrc.o obj-$(CONFIG_DRM_IMX_DCSS) += imx-dcss.o diff --git a/drivers/gpu/drm/imx/dcss/dcss-crtc.c b/drivers/gpu/drm/imx/dcss/dcss-crtc.c index 31267c00782f..6260cff317f0 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-crtc.c +++ b/drivers/gpu/drm/imx/dcss/dcss-crtc.c @@ -3,19 +3,77 @@ * Copyright 2019 NXP. */ +#include <drm/bridge/cdns-mhdp.h> #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_edid.h> #include <drm/drm_vblank.h> + +#include <linux/hdmi.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include "dcss-dev.h" #include "dcss-kms.h" +static void dcss_drm_crtc_reset(struct drm_crtc *crtc) +{ + struct dcss_crtc_state *state; + + if (crtc->state) { + __drm_atomic_helper_crtc_destroy_state(crtc->state); + + state = to_dcss_crtc_state(crtc->state); + kfree(state); + crtc->state = NULL; + } + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) { + crtc->state = &state->base; + crtc->state->crtc = crtc; + } +} + +static struct drm_crtc_state * +dcss_drm_crtc_atomic_duplicate_state(struct drm_crtc *crtc) +{ + struct dcss_crtc_state *state, *copy; + + if (WARN_ON(!crtc->state)) + return NULL; + + copy = kzalloc(sizeof(*copy), GFP_KERNEL); + if (!copy) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, ©->base); + state = to_dcss_crtc_state(crtc->state); + copy->output_encoding = state->output_encoding; + copy->opipe_nl = state->opipe_nl; + copy->opipe_g = state->opipe_g; + copy->opipe_pr = state->opipe_pr; + + return ©->base; +} + +static void dcss_drm_crtc_atomic_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct dcss_crtc_state *dcss_crtc_state; + + if (state) { + __drm_atomic_helper_crtc_destroy_state(state); + dcss_crtc_state = to_dcss_crtc_state(state); + kfree(dcss_crtc_state); + } +} + static int dcss_enable_vblank(struct drm_crtc *crtc) { - struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc, - base); + struct dcss_crtc *dcss_crtc = to_dcss_crtc(crtc); struct dcss_dev *dcss = crtc->dev->dev_private; dcss_dtg_vblank_irq_enable(dcss->dtg, true); @@ -29,15 +87,15 @@ static int dcss_enable_vblank(struct drm_crtc *crtc) static void dcss_disable_vblank(struct drm_crtc *crtc) { - struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc, - base); + struct dcss_crtc *dcss_crtc = to_dcss_crtc(crtc); struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private; disable_irq_nosync(dcss_crtc->irq); dcss_dtg_vblank_irq_enable(dcss->dtg, false); - if (dcss_crtc->disable_ctxld_kick_irq) + if (!dcss_dtrc_is_running(dcss->dtrc) && + dcss_crtc->disable_ctxld_kick_irq) dcss_dtg_ctxld_kick_irq_enable(dcss->dtg, false); } @@ -45,9 +103,9 @@ static const struct drm_crtc_funcs dcss_crtc_funcs = { .set_config = drm_atomic_helper_set_config, .destroy = drm_crtc_cleanup, .page_flip = drm_atomic_helper_page_flip, - .reset = drm_atomic_helper_crtc_reset, - .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .reset = dcss_drm_crtc_reset, + .atomic_duplicate_state = dcss_drm_crtc_atomic_duplicate_state, + .atomic_destroy_state = dcss_drm_crtc_atomic_destroy_state, .enable_vblank = dcss_enable_vblank, .disable_vblank = dcss_disable_vblank, }; @@ -61,8 +119,7 @@ static void dcss_crtc_atomic_begin(struct drm_crtc *crtc, static void dcss_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) { - struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc, - base); + struct dcss_crtc *dcss_crtc = to_dcss_crtc(crtc); struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private; spin_lock_irq(&crtc->dev->event_lock); @@ -82,9 +139,10 @@ static void dcss_crtc_atomic_enable(struct drm_crtc *crtc, { struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc); - struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc, - base); + struct dcss_crtc *dcss_crtc = to_dcss_crtc(crtc); struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private; + struct dcss_crtc_state *dcss_crtc_state = + to_dcss_crtc_state(crtc->state); struct drm_display_mode *mode = &crtc->state->adjusted_mode; struct drm_display_mode *old_mode = &old_crtc_state->adjusted_mode; struct videomode vm; @@ -95,8 +153,8 @@ static void dcss_crtc_atomic_enable(struct drm_crtc *crtc, vm.pixelclock = mode->crtc_clock * 1000; - dcss_ss_subsam_set(dcss->ss); - dcss_dtg_css_set(dcss->dtg); + dcss_ss_subsam_set(dcss->ss, dcss_crtc_state->output_encoding); + dcss_dtg_css_set(dcss->dtg, dcss_crtc_state->output_encoding); if (!drm_mode_equal(mode, old_mode) || !old_crtc_state->active) { dcss_dtg_sync_set(dcss->dtg, &vm); @@ -118,8 +176,7 @@ static void dcss_crtc_atomic_disable(struct drm_crtc *crtc, { struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc); - struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc, - base); + struct dcss_crtc *dcss_crtc = to_dcss_crtc(crtc); struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private; struct drm_display_mode *mode = &crtc->state->adjusted_mode; struct drm_display_mode *old_mode = &old_crtc_state->adjusted_mode; @@ -158,11 +215,25 @@ static void dcss_crtc_atomic_disable(struct drm_crtc *crtc, pm_runtime_put_autosuspend(dcss->dev); } +static enum drm_mode_status dcss_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + /* + * From DCSS perspective, dissallow any mode higher than + * 3840x2160 or 2160x3840. + */ + if (mode->hdisplay * mode->vdisplay > 3840 * 2160) + return MODE_BAD; + + return MODE_OK; +} + static const struct drm_crtc_helper_funcs dcss_helper_funcs = { .atomic_begin = dcss_crtc_atomic_begin, .atomic_flush = dcss_crtc_atomic_flush, .atomic_enable = dcss_crtc_atomic_enable, .atomic_disable = dcss_crtc_atomic_disable, + .mode_valid = dcss_crtc_mode_valid, }; static irqreturn_t dcss_crtc_irq_handler(int irq, void *dev_id) @@ -181,6 +252,144 @@ static irqreturn_t dcss_crtc_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } +static void __dcss_crtc_setup_opipe_gamut(u32 colorspace, + const struct drm_display_mode *mode, + enum dcss_hdr10_gamut *g, + enum dcss_hdr10_nonlinearity *nl) +{ + u8 vic; + + switch (colorspace) { + case DRM_MODE_COLORIMETRY_BT709_YCC: + case DRM_MODE_COLORIMETRY_XVYCC_709: + *g = G_REC709; + *nl = NL_REC709; + return; + case DRM_MODE_COLORIMETRY_SMPTE_170M_YCC: + case DRM_MODE_COLORIMETRY_XVYCC_601: + case DRM_MODE_COLORIMETRY_SYCC_601: + case DRM_MODE_COLORIMETRY_OPYCC_601: + *g = G_REC601_NTSC; + *nl = NL_REC709; + return; + case DRM_MODE_COLORIMETRY_BT2020_CYCC: + case DRM_MODE_COLORIMETRY_BT2020_RGB: + case DRM_MODE_COLORIMETRY_BT2020_YCC: + *g = G_REC2020; + *nl = NL_REC2084; + return; + case DRM_MODE_COLORIMETRY_OPRGB: + *g = G_REC709; + *nl = NL_SRGB; + return; + default: + break; + } + + /* + * If we reached this point, it means the default colorimetry is used. + */ + + /* non-CEA mode, sRGB is used */ + vic = drm_match_cea_mode(mode); + if (vic == 0) { + *g = G_REC709; + *nl = NL_SRGB; + return; + } + + /* use REC709 otherwise, by default */ + *g = G_REC709; + *nl = NL_REC709; +} + +static void __dcss_crtc_setup_opipe(struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct dcss_crtc_state *dcss_crtc_state = to_dcss_crtc_state(crtc_state); + struct dcss_dev *dcss = crtc_state->crtc->dev->dev_private; + enum hdmi_quantization_range qr; + + qr = drm_default_rgb_quant_range(&crtc_state->adjusted_mode); + + __dcss_crtc_setup_opipe_gamut(conn_state->colorspace, + &crtc_state->adjusted_mode, + &dcss_crtc_state->opipe_g, + &dcss_crtc_state->opipe_nl); + + dcss_crtc_state->opipe_pr = qr == HDMI_QUANTIZATION_RANGE_FULL ? + PR_FULL : PR_LIMITED; + + dcss_crtc_state->output_encoding = DCSS_PIPE_OUTPUT_RGB; + + if (dcss->hdmi_output) { + struct cdns_mhdp_device *mhdp_dev = + container_of(conn_state->connector, + struct cdns_mhdp_device, + connector.base); + + switch (mhdp_dev->video_info.color_fmt) { + case YCBCR_4_2_2: + dcss_crtc_state->output_encoding = + DCSS_PIPE_OUTPUT_YUV422; + break; + case YCBCR_4_2_0: + dcss_crtc_state->output_encoding = + DCSS_PIPE_OUTPUT_YUV420; + break; + case YCBCR_4_4_4: + dcss_crtc_state->output_encoding = + DCSS_PIPE_OUTPUT_YUV444; + break; + default: + break; + } + } +} + +int dcss_crtc_setup_opipe(struct drm_device *dev, struct drm_atomic_state *state) +{ + struct dcss_dev *dcss = dev->dev_private; + struct dcss_kms_dev *dcss_kms = + container_of(dev, struct dcss_kms_dev, base); + struct drm_crtc_state *crtc_state; + struct drm_connector *conn; + struct drm_connector_state *conn_state; + int i, ret; + + crtc_state = drm_atomic_get_crtc_state(state, &dcss_kms->crtc.base); + if (WARN_ON(IS_ERR(crtc_state))) { + ret = PTR_ERR(crtc_state); + dev_dbg(dcss->dev, "failed to get CRTC state: %d\n", ret); + return ret; + } + + if (!dcss_drv_is_componentized(dcss->dev)) { + conn_state = drm_atomic_get_connector_state(state, + dcss_kms->connector); + if (IS_ERR(conn_state)) { + ret = PTR_ERR(conn_state); + dev_dbg(dcss->dev, + "failed to get connector state: %d\n", ret); + return ret; + } + + __dcss_crtc_setup_opipe(crtc_state, conn_state); + } else { + for_each_new_connector_in_state(state, conn, conn_state, i) { + if (!conn_state->best_encoder) + continue; + + if (!crtc_state->active) + continue; + + __dcss_crtc_setup_opipe(crtc_state, conn_state); + } + } + + return 0; +} + int dcss_crtc_init(struct dcss_crtc *crtc, struct drm_device *drm) { struct dcss_dev *dcss = drm->dev_private; @@ -188,7 +397,7 @@ int dcss_crtc_init(struct dcss_crtc *crtc, struct drm_device *drm) int ret; crtc->plane[0] = dcss_plane_init(drm, drm_crtc_mask(&crtc->base), - DRM_PLANE_TYPE_PRIMARY, 0); + DRM_PLANE_TYPE_PRIMARY, 2); if (IS_ERR(crtc->plane[0])) return PTR_ERR(crtc->plane[0]); @@ -202,6 +411,18 @@ int dcss_crtc_init(struct dcss_crtc *crtc, struct drm_device *drm) return ret; } + crtc->plane[1] = dcss_plane_init(drm, drm_crtc_mask(&crtc->base), + DRM_PLANE_TYPE_OVERLAY, 1); + if (IS_ERR(crtc->plane[1])) + crtc->plane[1] = NULL; + + crtc->plane[2] = dcss_plane_init(drm, drm_crtc_mask(&crtc->base), + DRM_PLANE_TYPE_OVERLAY, 0); + if (IS_ERR(crtc->plane[2])) + crtc->plane[2] = NULL; + + drm_plane_create_alpha_property(&crtc->plane[0]->base); + crtc->irq = platform_get_irq_byname(pdev, "vblank"); if (crtc->irq < 0) return crtc->irq; @@ -218,6 +439,26 @@ int dcss_crtc_init(struct dcss_crtc *crtc, struct drm_device *drm) return 0; } +void dcss_crtc_attach_color_mgmt_properties(struct dcss_crtc *crtc) +{ + int i; + + /* create color management properties only for video planes */ + for (i = 1; i < 3; i++) { + if (crtc->plane[i]->type == DRM_PLANE_TYPE_PRIMARY) + return; + + drm_plane_create_color_properties(&crtc->plane[i]->base, + BIT(DRM_COLOR_YCBCR_BT601) | + BIT(DRM_COLOR_YCBCR_BT709) | + BIT(DRM_COLOR_YCBCR_BT2020), + BIT(DRM_COLOR_YCBCR_FULL_RANGE) | + BIT(DRM_COLOR_YCBCR_LIMITED_RANGE), + DRM_COLOR_YCBCR_BT709, + DRM_COLOR_YCBCR_FULL_RANGE); + } +} + void dcss_crtc_deinit(struct dcss_crtc *crtc, struct drm_device *drm) { free_irq(crtc->irq, crtc); diff --git a/drivers/gpu/drm/imx/dcss/dcss-ctxld.c b/drivers/gpu/drm/imx/dcss/dcss-ctxld.c index 3a84cb3209c4..06668205094b 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-ctxld.c +++ b/drivers/gpu/drm/imx/dcss/dcss-ctxld.c @@ -267,6 +267,11 @@ static int dcss_ctxld_enable_locked(struct dcss_ctxld *ctxld) dcss_scaler_write_sclctrl(dcss->scaler); + if (dcss_dtrc_is_running(dcss->dtrc)) { + dcss_dtrc_switch_banks(dcss->dtrc); + ctxld->armed = true; + } + sb_hp_cnt = ctxld->ctx_size[curr_ctx][CTX_SB_HP]; sb_lp_cnt = ctxld->ctx_size[curr_ctx][CTX_SB_LP]; db_cnt = ctxld->ctx_size[curr_ctx][CTX_DB]; diff --git a/drivers/gpu/drm/imx/dcss/dcss-dec400d.c b/drivers/gpu/drm/imx/dcss/dcss-dec400d.c new file mode 100644 index 000000000000..11636d813802 --- /dev/null +++ b/drivers/gpu/drm/imx/dcss/dcss-dec400d.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 NXP. + */ + +#include <linux/device.h> +#include <linux/bitops.h> +#include <linux/io.h> +#include <drm/drm_fourcc.h> + +#include "dcss-dev.h" + +/* DEC400D registers offsets */ +#define DEC400D_READCONFIG_BASE 0x800 +#define DEC400D_READCONFIG(i) (DEC400D_READCONFIG_BASE + ((i) << 2)) +#define COMPRESSION_ENABLE_BIT BIT(0) +#define COMPRESSION_FORMAT_POS 3 +#define COMPRESSION_ALIGN_MODE_POS 16 +#define TILE_ALIGN_MODE_POS 22 +#define TILE_MODE_POS 25 +#define DEC400D_READBUFFERBASE0 0x900 +#define DEC400D_READCACHEBASE0 0x980 +#define DEC400D_CONTROL 0xB00 +#define DEC400D_CLEAR 0xB80 +#define DEC400D_READBUFFERBASE0 0x900 +#define DEC400D_READCACHEBASE0 0x980 +#define DEC400D_CONTROL 0xB00 +#define DISABLE_COMPRESSION_BIT BIT(1) +#define SHADOW_TRIGGER_BIT BIT(29) +#define DEC400_CFMT_ARGB8 0x0 +#define DEC400_CFMT_XRGB8 0x1 +#define DEC400_CFMT_AYUV 0x2 +#define DEC400_CFMT_UYVY 0x3 +#define DEC400_CFMT_YUY2 0x4 +#define DEC400_CFMT_YUV_ONLY 0x5 +#define DEC400_CFMT_UV_MIX 0x6 +#define DEC400_CFMT_ARGB4 0x7 +#define DEC400_CFMT_XRGB4 0x8 +#define DEC400_CFMT_A1R5G5B5 0x9 +#define DEC400_CFMT_X1R5G5B5 0xA +#define DEC400_CFMT_R5G6B5 0xB +#define DEC400_CFMT_Z24S8 0xC +#define DEC400_CFMT_Z24 0xD +#define DEC400_CFMT_Z16 0xE +#define DEC400_CFMT_A2R10G10B10 0xF +#define DEC400_CFMT_BAYER 0x10 +#define DEC400_CFMT_SIGNED_BAYER 0x11 + +struct dcss_dec400d { + struct device *dev; + void __iomem *base_reg; + u32 base_ofs; + struct dcss_ctxld *ctxld; + u32 ctx_id; + bool bypass; /* bypass or decompress */ +}; + +static void dcss_dec400d_write(struct dcss_dec400d *dec400d, + u32 value, + u32 offset) +{ + dcss_ctxld_write(dec400d->ctxld, dec400d->ctx_id, + value, dec400d->base_ofs + offset); +} + +int dcss_dec400d_init(struct dcss_dev *dcss, unsigned long dec400d_base) +{ + struct dcss_dec400d *dec400d; + int ret; + + dec400d = kzalloc(sizeof(*dec400d), GFP_KERNEL); + if (!dec400d) + return -ENOMEM; + + dcss->dec400d = dec400d; + dec400d->dev = dcss->dev; + dec400d->ctxld = dcss->ctxld; + + dec400d->base_reg = ioremap(dec400d_base, SZ_4K); + if (!dec400d->base_reg) { + dev_err(dcss->dev, "dec400d: unable to remap dec400d base\n"); + ret = -ENOMEM; + goto free_mem; + } + + dec400d->base_ofs = dec400d_base; + + dec400d->ctx_id = CTX_SB_HP; + + return 0; + +free_mem: + kfree(dcss->dec400d); + return ret; +} + +void dcss_dec400d_exit(struct dcss_dec400d *dec400d) +{ + if (dec400d->base_reg) + iounmap(dec400d->base_reg); + + kfree(dec400d); +} + +void dcss_dec400d_read_config(struct dcss_dec400d *dec400d, + u32 read_id, + bool compress_en, + u32 compress_format) +{ + u32 cformat = 0; + u32 read_config = 0x0; + + /* TODO: using 'read_id' 0 by default */ + if (read_id) { + WARN_ON(1); + return; + } + + if (!compress_en) + goto config; + + switch (compress_format) { + case _VIV_CFMT_ARGB8: + cformat = DEC400_CFMT_ARGB8; + break; + case _VIV_CFMT_XRGB8: + cformat = DEC400_CFMT_XRGB8; + break; + case _VIV_CFMT_AYUV: + cformat = DEC400_CFMT_AYUV; + break; + case _VIV_CFMT_UYVY: + cformat = DEC400_CFMT_UYVY; + break; + case _VIV_CFMT_YUY2: + cformat = DEC400_CFMT_YUY2; + break; + case _VIV_CFMT_YUV_ONLY: + cformat = DEC400_CFMT_YUV_ONLY; + break; + case _VIV_CFMT_UV_MIX: + cformat = DEC400_CFMT_UV_MIX; + break; + case _VIV_CFMT_ARGB4: + cformat = DEC400_CFMT_ARGB4; + break; + case _VIV_CFMT_XRGB4: + cformat = DEC400_CFMT_XRGB4; + break; + case _VIV_CFMT_A1R5G5B5: + cformat = DEC400_CFMT_A1R5G5B5; + break; + case _VIV_CFMT_X1R5G5B5: + cformat = DEC400_CFMT_X1R5G5B5; + break; + case _VIV_CFMT_R5G6B5: + cformat = DEC400_CFMT_R5G6B5; + break; + case _VIV_CFMT_Z24S8: + cformat = DEC400_CFMT_Z24S8; + break; + case _VIV_CFMT_Z24: + cformat = DEC400_CFMT_Z24; + break; + case _VIV_CFMT_Z16: + cformat = DEC400_CFMT_Z16; + break; + case _VIV_CFMT_A2R10G10B10: + cformat = DEC400_CFMT_A2R10G10B10; + break; + case _VIV_CFMT_BAYER: + cformat = DEC400_CFMT_BAYER; + break; + case _VIV_CFMT_SIGNED_BAYER: + cformat = DEC400_CFMT_SIGNED_BAYER; + break; + default: + /* TODO: not support yet */ + WARN_ON(1); + return; + } + + /* Dec compress format */ + read_config |= cformat << COMPRESSION_FORMAT_POS; + + /* ALIGN32_BYTE */ + read_config |= 0x2 << COMPRESSION_ALIGN_MODE_POS; + + /* TILE1_ALIGN */ + read_config |= 0x0 << TILE_ALIGN_MODE_POS; + + /* TILE8x4 */ + read_config |= 0x3 << TILE_MODE_POS; + + /* Compression Enable */ + read_config |= COMPRESSION_ENABLE_BIT; + +config: + dcss_dec400d_write(dec400d, read_config, DEC400D_READCONFIG(read_id)); +} + +void dcss_dec400d_bypass(struct dcss_dec400d *dec400d) +{ + u32 control; + + dcss_dec400d_read_config(dec400d, 0, false, 0); + + control = dcss_readl(dec400d->base_reg + DEC400D_CONTROL); + dev_dbg(dec400d->dev, "%s: dec400d control = %#x\n", __func__, control); + + control |= DISABLE_COMPRESSION_BIT; + dcss_dec400d_write(dec400d, control, DEC400D_CONTROL); + + /* Trigger shadow registers */ + control |= SHADOW_TRIGGER_BIT; + dcss_dec400d_write(dec400d, control, DEC400D_CONTROL); + + dec400d->bypass = true; +} + +void dcss_dec400d_shadow_trig(struct dcss_dec400d *dec400d) +{ + u32 control; + + /* do nothing */ + if (dec400d->bypass) + return; + + control = dcss_readl(dec400d->base_reg + DEC400D_CONTROL); + + /* Trigger shadow registers */ + control |= SHADOW_TRIGGER_BIT; + dcss_dec400d_write(dec400d, control, DEC400D_CONTROL); +} + +void dcss_dec400d_addr_set(struct dcss_dec400d *dec400d, u32 baddr, u32 caddr) +{ + /* set frame buffer base addr */ + dcss_dec400d_write(dec400d, baddr, DEC400D_READBUFFERBASE0); + + /* set tile status cache addr */ + dcss_dec400d_write(dec400d, caddr, DEC400D_READCACHEBASE0); + + dec400d->bypass = false; +} + +void dcss_dec400d_fast_clear_config(struct dcss_dec400d *dec400d, + u32 fc_value, + bool enable) +{ + dcss_dec400d_write(dec400d, fc_value, DEC400D_CLEAR); +} + +void dcss_dec400d_enable(struct dcss_dec400d *dec400d) +{ + u32 control; + + if (dec400d->bypass) + return; + + control = dcss_readl(dec400d->base_reg + DEC400D_CONTROL); + + /* enable compression */ + control &= ~(DISABLE_COMPRESSION_BIT); + dcss_dec400d_write(dec400d, control, DEC400D_CONTROL); + + /* Trigger shadow registers */ + control |= SHADOW_TRIGGER_BIT; + dcss_dec400d_write(dec400d, control, DEC400D_CONTROL); +} diff --git a/drivers/gpu/drm/imx/dcss/dcss-dev.c b/drivers/gpu/drm/imx/dcss/dcss-dev.c index 3f5750cc2673..a964cb6c9c58 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-dev.c +++ b/drivers/gpu/drm/imx/dcss/dcss-dev.c @@ -10,6 +10,7 @@ #include <linux/slab.h> #include <drm/drm_bridge_connector.h> #include <drm/drm_device.h> +#include <linux/busfreq-imx.h> #include <drm/drm_modeset_helper.h> #include "dcss-dev.h" @@ -17,6 +18,11 @@ static void dcss_clocks_enable(struct dcss_dev *dcss) { + if (dcss->hdmi_output) { + clk_prepare_enable(dcss->pll_phy_ref_clk); + clk_prepare_enable(dcss->pll_src_clk); + } + clk_prepare_enable(dcss->axi_clk); clk_prepare_enable(dcss->apb_clk); clk_prepare_enable(dcss->rtrm_clk); @@ -31,6 +37,11 @@ static void dcss_clocks_disable(struct dcss_dev *dcss) clk_disable_unprepare(dcss->rtrm_clk); clk_disable_unprepare(dcss->apb_clk); clk_disable_unprepare(dcss->axi_clk); + + if (dcss->hdmi_output) { + clk_disable_unprepare(dcss->pll_src_clk); + clk_disable_unprepare(dcss->pll_phy_ref_clk); + } } static void dcss_disable_dtg_and_ss_cb(void *data) @@ -83,22 +94,57 @@ static int dcss_submodules_init(struct dcss_dev *dcss) if (ret) goto ss_err; + ret = dcss_dtrc_init(dcss, base_addr + devtype->dtrc_ofs); + if (ret) + goto dtrc_err; + ret = dcss_dpr_init(dcss, base_addr + devtype->dpr_ofs); if (ret) goto dpr_err; + ret = dcss_wrscl_init(dcss, base_addr + devtype->wrscl_ofs); + if (ret) + goto wrscl_err; + + ret = dcss_rdsrc_init(dcss, base_addr + devtype->rdsrc_ofs); + if (ret) + goto rdsrc_err; + ret = dcss_scaler_init(dcss, base_addr + devtype->scaler_ofs); if (ret) goto scaler_err; + ret = dcss_dec400d_init(dcss, base_addr + devtype->dec400d_ofs); + if (ret) + goto dec400d_err; + + ret = dcss_hdr10_init(dcss, base_addr + devtype->hdr10_ofs); + if (ret) + goto hdr10_err; + dcss_clocks_disable(dcss); return 0; +hdr10_err: + dcss_dec400d_exit(dcss->dec400d); + +dec400d_err: + dcss_scaler_exit(dcss->scaler); + scaler_err: + dcss_rdsrc_exit(dcss->rdsrc); + +rdsrc_err: + dcss_wrscl_exit(dcss->wrscl); + +wrscl_err: dcss_dpr_exit(dcss->dpr); dpr_err: + dcss_dtrc_exit(dcss->dtrc); + +dtrc_err: dcss_ss_exit(dcss->ss); ss_err: @@ -118,8 +164,13 @@ ctxld_err: static void dcss_submodules_stop(struct dcss_dev *dcss) { dcss_clocks_enable(dcss); + dcss_hdr10_exit(dcss->hdr10); + dcss_dec400d_exit(dcss->dec400d); dcss_scaler_exit(dcss->scaler); + dcss_rdsrc_exit(dcss->rdsrc); + dcss_wrscl_exit(dcss->wrscl); dcss_dpr_exit(dcss->dpr); + dcss_dtrc_exit(dcss->dtrc); dcss_ss_exit(dcss->ss); dcss_dtg_exit(dcss->dtg); dcss_ctxld_exit(dcss->ctxld); @@ -133,17 +184,20 @@ static int dcss_clks_init(struct dcss_dev *dcss) struct { const char *id; struct clk **clk; + bool required; } clks[] = { - {"apb", &dcss->apb_clk}, - {"axi", &dcss->axi_clk}, - {"pix", &dcss->pix_clk}, - {"rtrm", &dcss->rtrm_clk}, - {"dtrc", &dcss->dtrc_clk}, + {"apb", &dcss->apb_clk, true}, + {"axi", &dcss->axi_clk, true}, + {"pix", &dcss->pix_clk, true}, + {"rtrm", &dcss->rtrm_clk, true}, + {"dtrc", &dcss->dtrc_clk, true}, + {"pll_src", &dcss->pll_src_clk, dcss->hdmi_output}, + {"pll_phy_ref", &dcss->pll_phy_ref_clk, dcss->hdmi_output}, }; for (i = 0; i < ARRAY_SIZE(clks); i++) { *clks[i].clk = devm_clk_get(dcss->dev, clks[i].id); - if (IS_ERR(*clks[i].clk)) { + if (IS_ERR(*clks[i].clk) && clks[i].required) { dev_err(dcss->dev, "failed to get %s clock\n", clks[i].id); return PTR_ERR(*clks[i].clk); @@ -257,7 +311,11 @@ int dcss_dev_suspend(struct device *dev) struct dcss_kms_dev *kms = container_of(ddev, struct dcss_kms_dev, base); int ret; - drm_bridge_connector_disable_hpd(kms->connector); + if (!dcss) + return 0; + + if (!dcss_drv_is_componentized(dev)) + drm_bridge_connector_disable_hpd(kms->connector); drm_mode_config_helper_suspend(ddev); @@ -270,6 +328,8 @@ int dcss_dev_suspend(struct device *dev) dcss_clocks_disable(dcss); + release_bus_freq(BUS_FREQ_HIGH); + return 0; } @@ -279,11 +339,16 @@ int dcss_dev_resume(struct device *dev) struct drm_device *ddev = dcss_drv_dev_to_drm(dev); struct dcss_kms_dev *kms = container_of(ddev, struct dcss_kms_dev, base); + if (!dcss) + return 0; + if (pm_runtime_suspended(dev)) { drm_mode_config_helper_resume(ddev); return 0; } + request_bus_freq(BUS_FREQ_HIGH); + dcss_clocks_enable(dcss); dcss_blkctl_cfg(dcss->blkctl); @@ -292,7 +357,8 @@ int dcss_dev_resume(struct device *dev) drm_mode_config_helper_resume(ddev); - drm_bridge_connector_enable_hpd(kms->connector); + if (!dcss_drv_is_componentized(dev)) + drm_bridge_connector_enable_hpd(kms->connector); return 0; } @@ -304,12 +370,17 @@ int dcss_dev_runtime_suspend(struct device *dev) struct dcss_dev *dcss = dcss_drv_dev_to_dcss(dev); int ret; + if (!dcss) + return 0; + ret = dcss_ctxld_suspend(dcss->ctxld); if (ret) return ret; dcss_clocks_disable(dcss); + release_bus_freq(BUS_FREQ_HIGH); + return 0; } @@ -317,6 +388,11 @@ int dcss_dev_runtime_resume(struct device *dev) { struct dcss_dev *dcss = dcss_drv_dev_to_dcss(dev); + if (!dcss) + return 0; + + request_bus_freq(BUS_FREQ_HIGH); + dcss_clocks_enable(dcss); dcss_blkctl_cfg(dcss->blkctl); diff --git a/drivers/gpu/drm/imx/dcss/dcss-dev.h b/drivers/gpu/drm/imx/dcss/dcss-dev.h index 1e582270c6ea..ad66ea1d16fa 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-dev.h +++ b/drivers/gpu/drm/imx/dcss/dcss-dev.h @@ -6,6 +6,7 @@ #ifndef __DCSS_PRV_H__ #define __DCSS_PRV_H__ +#include <drm/drm_atomic.h> #include <drm/drm_fourcc.h> #include <drm/drm_plane.h> #include <linux/io.h> @@ -58,6 +59,13 @@ enum dcss_ctxld_ctx_type { CTX_SB_LP, /* low-priority */ }; +enum dcss_pixel_pipe_output { + DCSS_PIPE_OUTPUT_RGB = 0, + DCSS_PIPE_OUTPUT_YUV444, + DCSS_PIPE_OUTPUT_YUV422, + DCSS_PIPE_OUTPUT_YUV420, +}; + struct dcss_dev { struct device *dev; const struct dcss_type_data *devtype; @@ -93,6 +101,7 @@ struct dcss_dev { struct dcss_dev *dcss_drv_dev_to_dcss(struct device *dev); struct drm_device *dcss_drv_dev_to_drm(struct device *dev); +bool dcss_drv_is_componentized(struct device *dev); struct dcss_dev *dcss_dev_create(struct device *dev, bool hdmi_output); void dcss_dev_destroy(struct dcss_dev *dcss); int dcss_dev_runtime_suspend(struct device *dev); @@ -122,6 +131,9 @@ int dcss_ctxld_enable(struct dcss_ctxld *ctxld); void dcss_ctxld_register_completion(struct dcss_ctxld *ctxld, struct completion *dis_completion); void dcss_ctxld_assert_locked(struct dcss_ctxld *ctxld); +void dcss_ctxld_register_dtrc_cb(struct dcss_ctxld *ctxld, + bool (*cb)(void *), + void *data); /* DPR */ int dcss_dpr_init(struct dcss_dev *dcss, unsigned long dpr_base); @@ -142,7 +154,8 @@ bool dcss_dtg_vblank_irq_valid(struct dcss_dtg *dtg); void dcss_dtg_vblank_irq_enable(struct dcss_dtg *dtg, bool en); void dcss_dtg_vblank_irq_clear(struct dcss_dtg *dtg); void dcss_dtg_sync_set(struct dcss_dtg *dtg, struct videomode *vm); -void dcss_dtg_css_set(struct dcss_dtg *dtg); +void dcss_dtg_css_set(struct dcss_dtg *dtg, + enum dcss_pixel_pipe_output output_encoding); void dcss_dtg_enable(struct dcss_dtg *dtg); void dcss_dtg_shutoff(struct dcss_dtg *dtg); bool dcss_dtg_is_enabled(struct dcss_dtg *dtg); @@ -159,7 +172,8 @@ int dcss_ss_init(struct dcss_dev *dcss, unsigned long subsam_base); void dcss_ss_exit(struct dcss_ss *ss); void dcss_ss_enable(struct dcss_ss *ss); void dcss_ss_shutoff(struct dcss_ss *ss); -void dcss_ss_subsam_set(struct dcss_ss *ss); +void dcss_ss_subsam_set(struct dcss_ss *ss, + enum dcss_pixel_pipe_output output_encoding); void dcss_ss_sync_set(struct dcss_ss *ss, struct videomode *vm, bool phsync, bool pvsync); @@ -177,4 +191,166 @@ int dcss_scaler_get_min_max_ratios(struct dcss_scaler *scl, int ch_num, int *min, int *max); void dcss_scaler_write_sclctrl(struct dcss_scaler *scl); +/* DEC400D */ + +#define VIV_VIDMEM_METADATA_MAGIC fourcc_code('v', 'i', 'v', 'm') + +/* Compressed format now was defined same as dec400d, should be general. */ +typedef enum _VIV_COMPRESS_FMT +{ + _VIV_CFMT_ARGB8 = 0, + _VIV_CFMT_XRGB8, + _VIV_CFMT_AYUV, + _VIV_CFMT_UYVY, + _VIV_CFMT_YUY2, + _VIV_CFMT_YUV_ONLY, + _VIV_CFMT_UV_MIX, + _VIV_CFMT_ARGB4, + _VIV_CFMT_XRGB4, + _VIV_CFMT_A1R5G5B5, + _VIV_CFMT_X1R5G5B5, + _VIV_CFMT_R5G6B5, + _VIV_CFMT_Z24S8, + _VIV_CFMT_Z24, + _VIV_CFMT_Z16, + _VIV_CFMT_A2R10G10B10, + _VIV_CFMT_BAYER, + _VIV_CFMT_SIGNED_BAYER, + _VIV_CFMT_VAA16, + _VIV_CFMT_S8, + + _VIV_CFMT_MAX, +} _VIV_COMPRESS_FMT; + +/* Metadata for cross-device fd share with additional (ts) info. */ +typedef struct _VIV_VIDMEM_METADATA +{ + uint32_t magic; + + int32_t ts_fd; + void * ts_dma_buf; + + uint32_t fc_enabled; + uint32_t fc_value; + uint32_t fc_value_upper; + + uint32_t compressed; + uint32_t compress_format; +} _VIV_VIDMEM_METADATA; + +int dcss_dec400d_init(struct dcss_dev *dcss, unsigned long dec400d_base); +void dcss_dec400d_exit(struct dcss_dec400d *dec400d); +void dcss_dec400d_bypass(struct dcss_dec400d *dec400d); +void dcss_dec400d_shadow_trig(struct dcss_dec400d *dec400d); +void dcss_dec400d_enable(struct dcss_dec400d *dec400d); +void dcss_dec400d_fast_clear_config(struct dcss_dec400d *dec400d, + u32 fc_value, + bool enable); +void dcss_dec400d_read_config(struct dcss_dec400d *dec400d, + u32 read_id, + bool compress_en, + u32 compress_format); +void dcss_dec400d_addr_set(struct dcss_dec400d *dec400d, u32 baddr, u32 caddr); + +/* HDR10 */ +enum dcss_hdr10_nonlinearity { + NL_REC2084, + NL_REC709, + NL_BT1886, + NL_2100HLG, + NL_SRGB, +}; + +enum dcss_hdr10_pixel_range { + PR_LIMITED, + PR_FULL, +}; + +enum dcss_hdr10_gamut { + G_REC2020, + G_REC709, + G_REC601_NTSC, + G_REC601_PAL, + G_ADOBE_ARGB, +}; + +struct dcss_hdr10_pipe_cfg { + bool is_yuv; + enum dcss_hdr10_nonlinearity nl; + enum dcss_hdr10_pixel_range pr; + enum dcss_hdr10_gamut g; +}; + +int dcss_hdr10_init(struct dcss_dev *dcss, unsigned long hdr10_base); +void dcss_hdr10_exit(struct dcss_hdr10 *hdr10); +bool dcss_hdr10_pipe_cfg_is_supported(struct dcss_hdr10 *hdr10, + struct dcss_hdr10_pipe_cfg *ipipe_cfg, + struct dcss_hdr10_pipe_cfg *opipe_cfg); +void dcss_hdr10_setup(struct dcss_hdr10 *hdr10, int ch_num, + struct dcss_hdr10_pipe_cfg *ipipe_cfg, + struct dcss_hdr10_pipe_cfg *opipe_cfg); + +/* enums common to both WRSCL and RDSRC */ +enum dcss_wrscl_rdsrc_psize { + PSIZE_64, + PSIZE_128, + PSIZE_256, + PSIZE_512, + PSIZE_1024, + PSIZE_2048, + PSIZE_4096, +}; + +enum dcss_wrscl_rdsrc_tsize { + TSIZE_64, + TSIZE_128, + TSIZE_256, + TSIZE_512, +}; + +enum dcss_wrscl_rdsrc_fifo_size { + FIFO_512, + FIFO_1024, + FIFO_2048, + FIFO_4096, +}; + +enum dcss_wrscl_rdsrc_bpp { + BPP_38, /* 38 bit unpacked components */ + BPP_32_UPCONVERT, + BPP_32_10BIT_OUTPUT, + BPP_20, /* 10-bit YUV422 */ + BPP_16, /* 8-bit YUV422 */ +}; + +/* WRSCL */ +int dcss_wrscl_init(struct dcss_dev *dcss, unsigned long wrscl_base); +void dcss_wrscl_exit(struct dcss_wrscl *wrscl); +u32 dcss_wrscl_setup(struct dcss_wrscl *wrscl, u32 pix_format, u32 pix_clk_hz, + u32 dst_xres, u32 dst_yres); +void dcss_wrscl_enable(struct dcss_wrscl *wrscl); +void dcss_wrscl_disable(struct dcss_wrscl *wrscl); + +/* RDSRC */ +int dcss_rdsrc_init(struct dcss_dev *dcss, unsigned long rdsrc_base); +void dcss_rdsrc_exit(struct dcss_rdsrc *rdsrc); +void dcss_rdsrc_setup(struct dcss_rdsrc *rdsrc, u32 pix_format, u32 dst_xres, + u32 dst_yres, u32 base_addr); +void dcss_rdsrc_enable(struct dcss_rdsrc *rdsrc); +void dcss_rdsrc_disable(struct dcss_rdsrc *rdsrc); + +/* DTRC */ +int dcss_dtrc_init(struct dcss_dev *dcss, unsigned long dtrc_base); +void dcss_dtrc_exit(struct dcss_dtrc *dtrc); +void dcss_dtrc_bypass(struct dcss_dtrc *dtrc, int ch_num); +void dcss_dtrc_set_format_mod(struct dcss_dtrc *dtrc, int ch_num, u64 modifier); +void dcss_dtrc_addr_set(struct dcss_dtrc *dtrc, int ch_num, + u32 p1_ba, u32 p2_ba, uint64_t dec_table_ofs); +bool dcss_dtrc_ch_running(struct dcss_dtrc *dtrc, int ch_num); +bool dcss_dtrc_is_running(struct dcss_dtrc *dtrc); +void dcss_dtrc_enable(struct dcss_dtrc *dtrc, int ch_num, bool enable); +void dcss_dtrc_set_res(struct dcss_dtrc *dtrc, int ch_num, + struct drm_plane_state *state, u32 *dtrc_w, u32 *dtrc_h); +void dcss_dtrc_switch_banks(struct dcss_dtrc *dtrc); + #endif /* __DCSS_PRV_H__ */ diff --git a/drivers/gpu/drm/imx/dcss/dcss-dpr.c b/drivers/gpu/drm/imx/dcss/dcss-dpr.c index df9dab949bf2..e7dc98f73d1a 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-dpr.c +++ b/drivers/gpu/drm/imx/dcss/dcss-dpr.c @@ -106,12 +106,17 @@ struct dcss_dpr_ch { bool sys_ctrl_chgd; + u32 pitch; + int ch_num; int irq; + + bool use_dtrc; }; struct dcss_dpr { struct device *dev; + struct dcss_dtrc *dtrc; struct dcss_ctxld *ctxld; u32 ctx_id; @@ -163,6 +168,7 @@ int dcss_dpr_init(struct dcss_dev *dcss, unsigned long dpr_base) dpr->dev = dcss->dev; dpr->ctxld = dcss->ctxld; dpr->ctx_id = CTX_SB_HP; + dpr->dtrc = dcss->dtrc; if (dcss_dpr_ch_init_all(dpr, dpr_base)) { int i; @@ -211,6 +217,9 @@ static u32 dcss_dpr_x_pix_wide_adjust(struct dcss_dpr_ch *ch, u32 pix_wide, pix_in_64byte = pix_in_64byte_map[ch->pix_size][ch->tile]; + if (pix_format == DRM_FORMAT_NV15) + pix_wide = pix_wide * 10 / 8; + div_64byte_mod = pix_wide % pix_in_64byte; offset = (div_64byte_mod == 0) ? 0 : (pix_in_64byte - div_64byte_mod); @@ -238,7 +247,8 @@ void dcss_dpr_set_res(struct dcss_dpr *dpr, int ch_num, u32 xres, u32 yres) u32 pix_x_wide, pix_y_high; if (pix_format == DRM_FORMAT_NV12 || - pix_format == DRM_FORMAT_NV21) + pix_format == DRM_FORMAT_NV21 || + pix_format == DRM_FORMAT_NV15) max_planes = 2; for (plane = 0; plane < max_planes; plane++) { @@ -247,12 +257,16 @@ void dcss_dpr_set_res(struct dcss_dpr *dpr, int ch_num, u32 xres, u32 yres) pix_x_wide = dcss_dpr_x_pix_wide_adjust(ch, xres, pix_format); pix_y_high = dcss_dpr_y_pix_high_adjust(ch, yres, pix_format); + if (plane == 0) + ch->pitch = pix_x_wide; + dcss_dpr_write(ch, pix_x_wide, DCSS_DPR_FRAME_1P_PIX_X_CTRL + plane * gap); dcss_dpr_write(ch, pix_y_high, DCSS_DPR_FRAME_1P_PIX_Y_CTRL + plane * gap); - dcss_dpr_write(ch, 2, DCSS_DPR_FRAME_1P_CTRL0 + plane * gap); + dcss_dpr_write(ch, ch->use_dtrc ? 7 : 2, + DCSS_DPR_FRAME_1P_CTRL0 + plane * gap); } } @@ -261,9 +275,19 @@ void dcss_dpr_addr_set(struct dcss_dpr *dpr, int ch_num, u32 luma_base_addr, { struct dcss_dpr_ch *ch = &dpr->ch[ch_num]; - dcss_dpr_write(ch, luma_base_addr, DCSS_DPR_FRAME_1P_BASE_ADDR); + if (ch->use_dtrc) { + luma_base_addr = 0x0; + chroma_base_addr = 0x10000000; + } - dcss_dpr_write(ch, chroma_base_addr, DCSS_DPR_FRAME_2P_BASE_ADDR); + if (!dcss_dtrc_ch_running(dpr->dtrc, ch_num)) { + dcss_dpr_write(ch, luma_base_addr, DCSS_DPR_FRAME_1P_BASE_ADDR); + dcss_dpr_write(ch, chroma_base_addr, + DCSS_DPR_FRAME_2P_BASE_ADDR); + } + + if (ch->use_dtrc) + pitch = ch->pitch; ch->frame_ctrl &= ~PITCH_MASK; ch->frame_ctrl |= (((u32)pitch << PITCH_POS) & PITCH_MASK); @@ -292,6 +316,7 @@ static void dcss_dpr_pix_size_set(struct dcss_dpr_ch *ch, switch (format->format) { case DRM_FORMAT_NV12: case DRM_FORMAT_NV21: + case DRM_FORMAT_NV15: val = PIX_SIZE_8; break; @@ -401,6 +426,7 @@ static void dcss_dpr_rtram_set(struct dcss_dpr_ch *ch, u32 pix_format) switch (pix_format) { case DRM_FORMAT_NV21: case DRM_FORMAT_NV12: + case DRM_FORMAT_NV15: ch->rtram_3buf_en = true; ch->rtram_4line_en = false; break; @@ -474,6 +500,8 @@ static void dcss_dpr_setup_components(struct dcss_dpr_ch *ch, static void dcss_dpr_tile_set(struct dcss_dpr_ch *ch, uint64_t modifier) { + struct device *dev = ch->dpr->dev; + switch (ch->ch_num) { case 0: switch (modifier) { @@ -484,16 +512,35 @@ static void dcss_dpr_tile_set(struct dcss_dpr_ch *ch, uint64_t modifier) ch->tile = TILE_GPU_STANDARD; break; case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED: + case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED_FC: ch->tile = TILE_GPU_SUPER; break; default: - WARN_ON(1); + dev_err(dev, "dpr: unsupported modifier(0x%016llx) for graphics path.\n", + modifier); break; } break; case 1: case 2: - ch->tile = TILE_LINEAR; + switch (modifier) { + case DRM_FORMAT_MOD_LINEAR: + case DRM_FORMAT_MOD_VSI_G1_TILED: + case DRM_FORMAT_MOD_VSI_G2_TILED: + case DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED: + ch->tile = TILE_LINEAR; + break; + case DRM_FORMAT_MOD_VIVANTE_TILED: + ch->tile = TILE_GPU_STANDARD; + break; + case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED: + ch->tile = TILE_GPU_SUPER; + break; + default: + dev_err(dev, "dpr: unsupported modifier(0x%016llx) for video path.\n", + modifier); + break; + } break; default: WARN_ON(1); @@ -510,6 +557,10 @@ void dcss_dpr_format_set(struct dcss_dpr *dpr, int ch_num, struct dcss_dpr_ch *ch = &dpr->ch[ch_num]; ch->format = *format; + ch->use_dtrc = ch_num && + (modifier == DRM_FORMAT_MOD_VSI_G1_TILED || + modifier == DRM_FORMAT_MOD_VSI_G2_TILED || + modifier == DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED); dcss_dpr_yuv_en(ch, format->is_yuv); diff --git a/drivers/gpu/drm/imx/dcss/dcss-drv.c b/drivers/gpu/drm/imx/dcss/dcss-drv.c index 8dc2f85c514b..dab15913d8c7 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-drv.c +++ b/drivers/gpu/drm/imx/dcss/dcss-drv.c @@ -6,6 +6,7 @@ #include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> +#include <linux/component.h> #include <drm/drm_of.h> #include "dcss-dev.h" @@ -14,6 +15,8 @@ struct dcss_drv { struct dcss_dev *dcss; struct dcss_kms_dev *kms; + + bool is_componentized; }; struct dcss_dev *dcss_drv_dev_to_dcss(struct device *dev) @@ -30,30 +33,25 @@ struct drm_device *dcss_drv_dev_to_drm(struct device *dev) return mdrv ? &mdrv->kms->base : NULL; } -static int dcss_drv_platform_probe(struct platform_device *pdev) +bool dcss_drv_is_componentized(struct device *dev) { - struct device *dev = &pdev->dev; - struct device_node *remote; - struct dcss_drv *mdrv; - int err = 0; - bool hdmi_output = true; - - if (!dev->of_node) - return -ENODEV; - - remote = of_graph_get_remote_node(dev->of_node, 0, 0); - if (!remote) - return -ENODEV; + struct dcss_drv *mdrv = dev_get_drvdata(dev); - hdmi_output = !of_device_is_compatible(remote, "fsl,imx8mq-nwl-dsi"); + return mdrv->is_componentized; +} - of_node_put(remote); +static int dcss_drv_init(struct device *dev, bool componentized) +{ + struct dcss_drv *mdrv; + int err = 0; mdrv = kzalloc(sizeof(*mdrv), GFP_KERNEL); if (!mdrv) return -ENOMEM; - mdrv->dcss = dcss_dev_create(dev, hdmi_output); + mdrv->is_componentized = componentized; + + mdrv->dcss = dcss_dev_create(dev, componentized); if (IS_ERR(mdrv->dcss)) { err = PTR_ERR(mdrv->dcss); goto err; @@ -61,7 +59,7 @@ static int dcss_drv_platform_probe(struct platform_device *pdev) dev_set_drvdata(dev, mdrv); - mdrv->kms = dcss_kms_attach(mdrv->dcss); + mdrv->kms = dcss_kms_attach(mdrv->dcss, componentized); if (IS_ERR(mdrv->kms)) { err = PTR_ERR(mdrv->kms); goto dcss_shutoff; @@ -79,19 +77,73 @@ err: return err; } -static int dcss_drv_platform_remove(struct platform_device *pdev) +static void dcss_drv_deinit(struct device *dev, bool componentized) { - struct dcss_drv *mdrv = dev_get_drvdata(&pdev->dev); + struct dcss_drv *mdrv = dev_get_drvdata(dev); if (!mdrv) - return 0; + return; - dcss_kms_detach(mdrv->kms); + dcss_kms_detach(mdrv->kms, componentized); dcss_dev_destroy(mdrv->dcss); - dev_set_drvdata(&pdev->dev, NULL); + dev_set_drvdata(dev, NULL); kfree(mdrv); +} + +static int dcss_drv_bind(struct device *dev) +{ + return dcss_drv_init(dev, true); +} + +static void dcss_drv_unbind(struct device *dev) +{ + return dcss_drv_deinit(dev, true); +} + +static const struct component_master_ops dcss_master_ops = { + .bind = dcss_drv_bind, + .unbind = dcss_drv_unbind, +}; + +static int compare_of(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +static int dcss_drv_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct component_match *match = NULL; + struct device_node *remote; + + if (!dev->of_node) + return -ENODEV; + + remote = of_graph_get_remote_node(dev->of_node, 0, 0); + if (!remote) + return -ENODEV; + + if (of_device_is_compatible(remote, "fsl,imx8mq-nwl-dsi")) { + of_node_put(remote); + return dcss_drv_init(dev, false); + } + + drm_of_component_match_add(dev, &match, compare_of, remote); + of_node_put(remote); + + return component_master_add_with_match(dev, &dcss_master_ops, match); +} + +static int dcss_drv_platform_remove(struct platform_device *pdev) +{ + struct dcss_drv *mdrv = dev_get_drvdata(&pdev->dev); + + if (mdrv->is_componentized) + component_master_del(&pdev->dev, &dcss_master_ops); + else + dcss_drv_deinit(&pdev->dev, false); return 0; } @@ -102,9 +154,14 @@ static struct dcss_type_data dcss_types[] = { .blkctl_ofs = 0x2F000, .ctxld_ofs = 0x23000, .dtg_ofs = 0x20000, + .rdsrc_ofs = 0x22000, + .wrscl_ofs = 0x21000, .scaler_ofs = 0x1C000, .ss_ofs = 0x1B000, .dpr_ofs = 0x18000, + .dec400d_ofs = 0x15000, + .hdr10_ofs = 0x00000, + .dtrc_ofs = 0x16000, }, }; diff --git a/drivers/gpu/drm/imx/dcss/dcss-dtg.c b/drivers/gpu/drm/imx/dcss/dcss-dtg.c index 30de00540f63..b5994d1929cd 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-dtg.c +++ b/drivers/gpu/drm/imx/dcss/dcss-dtg.c @@ -83,6 +83,7 @@ struct dcss_dtg { u32 ctx_id; bool in_use; + bool hdmi_output; u32 dis_ulc_x; u32 dis_ulc_y; @@ -159,6 +160,7 @@ int dcss_dtg_init(struct dcss_dev *dcss, unsigned long dtg_base) dcss->dtg = dtg; dtg->dev = dcss->dev; dtg->ctxld = dcss->ctxld; + dtg->hdmi_output = dcss->hdmi_output; dtg->base_reg = ioremap(dtg_base, SZ_4K); if (!dtg->base_reg) { @@ -221,6 +223,15 @@ void dcss_dtg_sync_set(struct dcss_dtg *dtg, struct videomode *vm) vm->vactive - 1; clk_disable_unprepare(dcss->pix_clk); + if (dcss->hdmi_output) { + int err; + + clk_disable_unprepare(dcss->pll_src_clk); + err = clk_set_parent(dcss->pll_src_clk, dcss->pll_phy_ref_clk); + if (err < 0) + dev_warn(dcss->dev, "clk_set_parent() returned %d", err); + clk_prepare_enable(dcss->pll_src_clk); + } clk_set_rate(dcss->pix_clk, vm->pixelclock); clk_prepare_enable(dcss->pix_clk); @@ -304,8 +315,14 @@ void dcss_dtg_plane_alpha_set(struct dcss_dtg *dtg, int ch_num, dtg->alpha = alpha; } -void dcss_dtg_css_set(struct dcss_dtg *dtg) +void dcss_dtg_css_set(struct dcss_dtg *dtg, + enum dcss_pixel_pipe_output output_encoding) { + dtg->control_status &= ~CSS_PIX_COMP_SWAP_MASK; + + if (output_encoding != DCSS_PIPE_OUTPUT_RGB) + return; + dtg->control_status |= (0x5 << CSS_PIX_COMP_SWAP_POS) & CSS_PIX_COMP_SWAP_MASK; } diff --git a/drivers/gpu/drm/imx/dcss/dcss-dtrc.c b/drivers/gpu/drm/imx/dcss/dcss-dtrc.c new file mode 100644 index 000000000000..47cc73f27c2a --- /dev/null +++ b/drivers/gpu/drm/imx/dcss/dcss-dtrc.c @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019, 2022 NXP. + */ + +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_rect.h> + +#include "dcss-dev.h" + +#define DTRC_F0_OFS 0x00 +#define DTRC_F1_OFS 0x60 + +#define DCSS_DTRC_DYDSADDR 0x00 +#define DCSS_DTRC_DCDSADDR 0x04 +#define DCSS_DTRC_DYTSADDR 0x08 +#define DCSS_DTRC_DCTSADDR 0x0C +#define DCSS_DTRC_SIZE 0x10 +#define FRAME_WIDTH_POS 0 +#define FRAME_WIDTH_MASK GENMASK(9, 0) +#define FRAME_HEIGHT_POS 16 +#define FRAME_HEIGHT_MASK GENMASK(25, 16) +#define DCSS_DTRC_SYSSA 0x14 +#define DCSS_DTRC_SYSEA 0x18 +#define DCSS_DTRC_SUVSSA 0x1C +#define DCSS_DTRC_SUVSEA 0x20 +#define DCSS_DTRC_CROPORIG 0x24 +#define DCSS_DTRC_CROPSIZE 0x28 +#define CROP_HEIGHT_POS 16 +#define CROP_HEIGHT_MASK GENMASK(28, 16) +#define CROP_WIDTH_POS 0 +#define CROP_WIDTH_MASK GENMASK(12, 0) +#define DCSS_DTRC_DCTL 0x2C +#define CROPPING_EN BIT(18) +#define COMPRESSION_DIS BIT(17) +#define PIX_DEPTH_8BIT_EN BIT(1) +#define CONFIG_READY BIT(0) +#define DCSS_DTRC_DYDSADDR_EXT 0x30 +#define DCSS_DTRC_DCDSADDR_EXT 0x34 +#define DCSS_DTRC_DYTSADDR_EXT 0x38 +#define DCSS_DTRC_DCTSADDR_EXT 0x3C +#define DCSS_DTRC_SYSSA_EXT 0x40 +#define DCSS_DTRC_SYSEA_EXT 0x44 +#define DCSS_DTRC_SUVSSA_EXT 0x48 +#define DCSS_DTRC_SUVSEA_EXT 0x4C + +#define DCSS_DTRC_INTEN 0xC0 +#define DCSS_DTRC_FDINTR 0xC4 +#define DCSS_DTRC_DTCTRL 0xC8 +#define CURRENT_FRAME BIT(31) +#define ADDRESS_ID_ENABLE BIT(30) +#define ENDIANNESS_10BIT BIT(29) +#define MERGE_ARID_ENABLE BIT(28) +#define NON_G1_2_SWAP_MODE_POS 24 +#define NON_G1_2_SWAP_MODE_MASK GENMASK(27, 24) +#define TABLE_DATA_SWAP_POS 20 +#define TABLE_DATA_SWAP_MASK GENMASK(23, 20) +#define TILED_SWAP_POS 16 +#define TILED_SWAP_MASK GENMASK(19, 16) +#define RASTER_SWAP_POS 12 +#define RASTER_SWAP_MASK GENMASK(15, 12) +#define BURST_LENGTH_POS 4 +#define BURST_LENGTH_MASK GENMASK(11, 4) +#define G1_TILED_DATA_EN BIT(3) +#define HOT_RESET BIT(2) +#define ARIDR_MODE_DETILE 0 +#define ARIDR_MODE_BYPASS 2 +#define DCSS_DTRC_ARIDR 0xCC +#define DCSS_DTRC_DTID2DDR 0xD0 +#define DCSS_DTRC_CONFIG 0xD4 +#define DCSS_DTRC_VER 0xD8 +#define DCSS_DTRC_PFCTRL 0xF0 +#define DCSS_DTRC_PFCR 0xF4 +#define DCSS_DTRC_TOCR 0xF8 + +struct dcss_dtrc_ch { + struct dcss_dtrc *dtrc; + + void __iomem *base_reg; + u32 base_ofs; + + u32 xres; + u32 yres; + u32 pix_format; + u64 format_modifier; + u32 y_dec_ofs; + u32 uv_dec_ofs; + + int curr_frame; + + u32 dctl; + + bool bypass; + bool running; + + int irq; + int ch_num; +}; + +struct dcss_dtrc { + struct device *dev; + + struct dcss_dtrc_ch ch[2]; + + u32 ctx_id; + struct dcss_ctxld *ctxld; +}; + +static irqreturn_t dcss_dtrc_irq_handler(int irq, void *data) +{ + struct dcss_dtrc_ch *ch = data; + u32 b0, b1, curr_bank; + + b0 = dcss_readl(ch->base_reg + DCSS_DTRC_DCTL) & 0x1; + b1 = dcss_readl(ch->base_reg + DTRC_F1_OFS + DCSS_DTRC_DCTL) & 0x1; + curr_bank = dcss_readl(ch->base_reg + DCSS_DTRC_DTCTRL) >> 31; + + dcss_update(1, 1, ch->base_reg + DCSS_DTRC_FDINTR); + + return IRQ_HANDLED; +} + +static int dcss_dtrc_irq_config(struct dcss_dtrc *dtrc, int ch_num) +{ + struct platform_device *pdev = to_platform_device(dtrc->dev); + struct dcss_dtrc_ch *ch = &dtrc->ch[ch_num]; + char irq_name[20]; + int ret; + + sprintf(irq_name, "dtrc_ch%d", ch_num + 1); + irq_name[8] = 0; + + ch->irq = platform_get_irq_byname(pdev, irq_name); + if (ch->irq < 0) { + dev_err(dtrc->dev, "dtrc: can't get DTRC irq\n"); + return ch->irq; + } + + ret = request_irq(ch->irq, dcss_dtrc_irq_handler, + 0, "dcss-dtrc", ch); + if (ret) { + ch->irq = 0; + dev_err(dtrc->dev, "dtrc: irq request failed.\n"); + return ret; + } + + dcss_writel(1, ch->base_reg + DCSS_DTRC_INTEN); + + return 0; +} + +static int dcss_dtrc_ch_init_all(struct dcss_dtrc *dtrc, u32 dtrc_base) +{ + struct dcss_dtrc_ch *ch; + int i, ret; + + for (i = 0; i < 2; i++) { + ch = &dtrc->ch[i]; + + ch->base_ofs = dtrc_base + i * 0x1000; + + ch->base_reg = ioremap(ch->base_ofs, SZ_4K); + if (!ch->base_reg) { + dev_err(dtrc->dev, "dtrc: unable to remap ch base\n"); + return -ENOMEM; + } + + ch->ch_num = i; + ch->dtrc = dtrc; + + ret = dcss_dtrc_irq_config(dtrc, i); + if (ret) + return ret; + } + + return 0; +} + +static void dcss_dtrc_write(struct dcss_dtrc_ch *ch, u32 val, u32 ofs) +{ + dcss_ctxld_write(ch->dtrc->ctxld, ch->dtrc->ctx_id, + val, ch->base_ofs + ofs); +} + +static void dcss_dtrc_write_irqsafe(struct dcss_dtrc_ch *ch, u32 val, u32 ofs) +{ + dcss_ctxld_write_irqsafe(ch->dtrc->ctxld, ch->dtrc->ctx_id, + val, ch->base_ofs + ofs); +} + +int dcss_dtrc_init(struct dcss_dev *dcss, unsigned long dtrc_base) +{ + struct dcss_dtrc *dtrc; + + dtrc = kzalloc(sizeof(*dtrc), GFP_KERNEL); + if (!dtrc) + return -ENOMEM; + + dcss->dtrc = dtrc; + dtrc->dev = dcss->dev; + dtrc->ctxld = dcss->ctxld; + dtrc->ctx_id = CTX_SB_HP; + + if (dcss_dtrc_ch_init_all(dtrc, dtrc_base)) { + struct dcss_dtrc_ch *ch; + int i; + + for (i = 0; i < 2; i++) { + ch = &dtrc->ch[i]; + + if (ch->irq) + free_irq(ch->irq, ch); + + if (ch->base_reg) + iounmap(ch->base_reg); + } + + kfree(dtrc); + + return -ENOMEM; + } + + return 0; +} + +void dcss_dtrc_exit(struct dcss_dtrc *dtrc) +{ + int ch_no; + + for (ch_no = 0; ch_no < 2; ch_no++) { + struct dcss_dtrc_ch *ch = &dtrc->ch[ch_no]; + + if (ch->base_reg) { + /* reset the module to default */ + dcss_writel(HOT_RESET, + ch->base_reg + DCSS_DTRC_DTCTRL); + iounmap(ch->base_reg); + } + + if (ch->irq) + free_irq(ch->irq, ch); + } + + kfree(dtrc); +} + +void dcss_dtrc_bypass(struct dcss_dtrc *dtrc, int ch_num) +{ + struct dcss_dtrc_ch *ch; + + if (ch_num == 0) + return; + + ch = &dtrc->ch[ch_num - 1]; + + if (ch->bypass) + return; + + dcss_dtrc_write(ch, ARIDR_MODE_BYPASS, DCSS_DTRC_DTCTRL); + dcss_dtrc_write(ch, 0, DCSS_DTRC_DYTSADDR); + dcss_dtrc_write(ch, 0, DCSS_DTRC_DCTSADDR); + dcss_dtrc_write(ch, 0x0f0e0100, DCSS_DTRC_ARIDR); + dcss_dtrc_write(ch, 0x0f0e, DCSS_DTRC_DTID2DDR); + + ch->running = false; + ch->bypass = true; +} + +void dcss_dtrc_addr_set(struct dcss_dtrc *dtrc, int ch_num, + u32 p1_ba, u32 p2_ba, uint64_t dec_table_ofs) +{ + struct dcss_dtrc_ch *ch; + + if (ch_num == 0) + return; + + ch = &dtrc->ch[ch_num - 1]; + + dcss_dtrc_write(ch, p1_ba, DCSS_DTRC_DYDSADDR); + dcss_dtrc_write(ch, p2_ba, DCSS_DTRC_DCDSADDR); + + dcss_dtrc_write(ch, p1_ba, DTRC_F1_OFS + DCSS_DTRC_DYDSADDR); + dcss_dtrc_write(ch, p2_ba, DTRC_F1_OFS + DCSS_DTRC_DCDSADDR); + + if (ch->format_modifier == DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED) { + ch->y_dec_ofs = dec_table_ofs & 0xFFFFFFFF; + ch->uv_dec_ofs = dec_table_ofs >> 32; + + dcss_dtrc_write(ch, p1_ba + ch->y_dec_ofs, + DCSS_DTRC_DYTSADDR); + dcss_dtrc_write(ch, p1_ba + ch->uv_dec_ofs, + DCSS_DTRC_DCTSADDR); + dcss_dtrc_write(ch, p1_ba + ch->y_dec_ofs, + DTRC_F1_OFS + DCSS_DTRC_DYTSADDR); + dcss_dtrc_write(ch, p1_ba + ch->uv_dec_ofs, + DTRC_F1_OFS + DCSS_DTRC_DCTSADDR); + } + + ch->bypass = false; +} + +void dcss_dtrc_set_res(struct dcss_dtrc *dtrc, int ch_num, + struct drm_plane_state *state, u32 *dtrc_w, u32 *dtrc_h) +{ + struct drm_framebuffer *fb = state->fb; + u32 pixel_format = fb->format->format; + struct dcss_dtrc_ch *ch; + u32 frame_height, frame_width; + u32 crop_w, crop_h, crop_orig_w, crop_orig_h; + int bank; + u32 old_xres, old_yres, xres, yres; + u32 x1, y1, x2, y2; + u32 pix_depth; + u16 width_align = 0; + + if (ch_num == 0) + return; + + ch = &dtrc->ch[ch_num - 1]; + + bank = dcss_readl(ch->base_reg + DCSS_DTRC_DTCTRL) >> 31; + + ch->pix_format = pixel_format; + ch->format_modifier = fb->modifier; + + pix_depth = ch->pix_format == DRM_FORMAT_NV15 ? 10 : 8; + + old_xres = state->src_w >> 16; + old_yres = state->src_h >> 16; + + x1 = (state->src.x1 >> 16) & ~1; + y1 = (state->src.y1 >> 16) & ~1; + x2 = state->src.x2 >> 16; + y2 = state->src.y2 >> 16; + + xres = x2 - x1; + yres = y2 - y1; + + frame_height = ((old_yres >> 3) << FRAME_HEIGHT_POS) & FRAME_HEIGHT_MASK; + frame_width = ((old_xres >> 3) << FRAME_WIDTH_POS) & FRAME_WIDTH_MASK; + + dcss_dtrc_write(ch, frame_height | frame_width, + DTRC_F1_OFS * bank + DCSS_DTRC_SIZE); + + dcss_dtrc_write(ch, frame_height | frame_width, + DTRC_F1_OFS * (bank ^ 1) + DCSS_DTRC_SIZE); + + /* + * Image original size is aligned: + * - 128 pixels for width (8-bit) or 256 (10-bit); + * - 8 lines for height; + */ + width_align = ch->pix_format == DRM_FORMAT_NV15 ? 0xff : 0x7f; + + if (xres == old_xres && !(xres & width_align) && + yres == old_yres && !(yres & 0xf)) { + ch->dctl &= ~CROPPING_EN; + goto exit; + } + + /* align the image size: down align for compressed formats */ + if (ch->format_modifier == DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED && x1) + xres = xres & ~width_align; + else + xres = (xres + width_align) & ~width_align; + + if (ch->format_modifier == DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED && y1) + yres = yres & ~0xf; + else + yres = (yres + 0xf) & ~0xf; + + crop_orig_w = (x1 << CROP_WIDTH_POS) & CROP_WIDTH_MASK; + crop_orig_h = (y1 << CROP_HEIGHT_POS) & CROP_HEIGHT_MASK; + + dcss_dtrc_write(ch, crop_orig_w | crop_orig_h, + DCSS_DTRC_CROPORIG); + dcss_dtrc_write(ch, crop_orig_w | crop_orig_h, + DTRC_F1_OFS + DCSS_DTRC_CROPORIG); + + crop_w = (xres << CROP_WIDTH_POS) & CROP_WIDTH_MASK; + crop_h = (yres << CROP_HEIGHT_POS) & CROP_HEIGHT_MASK; + + dcss_dtrc_write(ch, crop_w | crop_h, + DTRC_F1_OFS * bank + DCSS_DTRC_CROPSIZE); + dcss_dtrc_write(ch, crop_w | crop_h, + DTRC_F1_OFS * (bank ^ 1) + DCSS_DTRC_CROPSIZE); + + ch->dctl |= CROPPING_EN; + +exit: + dcss_dtrc_write(ch, xres * yres * pix_depth / 8, + DCSS_DTRC_SYSEA); + dcss_dtrc_write(ch, xres * yres * pix_depth / 8, + DTRC_F1_OFS + DCSS_DTRC_SYSEA); + + dcss_dtrc_write(ch, 0x10000000 + xres * yres * pix_depth / 8 / 2, + DCSS_DTRC_SUVSEA); + dcss_dtrc_write(ch, 0x10000000 + xres * yres * pix_depth / 8 / 2, + DTRC_F1_OFS + DCSS_DTRC_SUVSEA); + + *dtrc_w = xres; + *dtrc_h = yres; + + if (ch->running) + return; + + dcss_dtrc_write(ch, 0x0, DCSS_DTRC_SYSSA); + dcss_dtrc_write(ch, 0x0, DTRC_F1_OFS + DCSS_DTRC_SYSSA); + + dcss_dtrc_write(ch, 0x10000000, DCSS_DTRC_SUVSSA); + dcss_dtrc_write(ch, 0x10000000, DTRC_F1_OFS + DCSS_DTRC_SUVSSA); +} + +void dcss_dtrc_enable(struct dcss_dtrc *dtrc, int ch_num, bool enable) +{ + struct dcss_dtrc_ch *ch; + int curr_frame; + u32 fdctl, dtctrl; + + if (ch_num == 0) + return; + + ch = &dtrc->ch[ch_num - 1]; + + if (ch->bypass) + return; + + if (!enable) { + ch->running = false; + return; + } + + if (ch->running) + return; + + dcss_update(HOT_RESET, HOT_RESET, ch->base_reg + DCSS_DTRC_DTCTRL); + while (dcss_readl(ch->base_reg + DCSS_DTRC_DTCTRL) & HOT_RESET) + usleep_range(100, 200); + + dcss_dtrc_write(ch, 0x0f0e0100, + DCSS_DTRC_ARIDR); + dcss_dtrc_write(ch, 0x0f0e, + DCSS_DTRC_DTID2DDR); + + dtctrl = ADDRESS_ID_ENABLE | MERGE_ARID_ENABLE | + ((0xF << TABLE_DATA_SWAP_POS) & TABLE_DATA_SWAP_MASK) | + ((0x10 << BURST_LENGTH_POS) & BURST_LENGTH_MASK); + + if (ch->format_modifier == DRM_FORMAT_MOD_VSI_G1_TILED) + dtctrl |= G1_TILED_DATA_EN; + + dcss_dtrc_write(ch, dtctrl, DCSS_DTRC_DTCTRL); + + curr_frame = dcss_readl(ch->base_reg + DCSS_DTRC_DTCTRL) >> 31; + + fdctl = ch->dctl & ~(PIX_DEPTH_8BIT_EN | COMPRESSION_DIS); + + fdctl |= ch->pix_format == DRM_FORMAT_NV15 ? 0 : PIX_DEPTH_8BIT_EN; + + if (ch->format_modifier != DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED) + fdctl |= COMPRESSION_DIS; + + dcss_dtrc_write(ch, fdctl, + (curr_frame ^ 1) * DTRC_F1_OFS + DCSS_DTRC_DCTL); + dcss_dtrc_write(ch, fdctl | CONFIG_READY, + curr_frame * DTRC_F1_OFS + DCSS_DTRC_DCTL); + + ch->curr_frame = curr_frame; + ch->dctl = fdctl; + ch->running = true; +} + +bool dcss_dtrc_ch_running(struct dcss_dtrc *dtrc, int ch_num) +{ + struct dcss_dtrc_ch *ch; + + if (ch_num == 0) + return false; + + ch = &dtrc->ch[ch_num - 1]; + + return ch->running; +} + +bool dcss_dtrc_is_running(struct dcss_dtrc *dtrc) +{ + return dtrc->ch[0].running || dtrc->ch[1].running; +} + +static void dcss_dtrc_ch_switch_banks(struct dcss_dtrc *dtrc, int dtrc_ch) +{ + struct dcss_dtrc_ch *ch = &dtrc->ch[dtrc_ch]; + u32 b0, b1; + + if (!ch->running) + return; + + b0 = dcss_readl(ch->base_reg + DCSS_DTRC_DCTL) & 0x1; + b1 = dcss_readl(ch->base_reg + DTRC_F1_OFS + DCSS_DTRC_DCTL) & 0x1; + + ch->curr_frame = dcss_readl(ch->base_reg + DCSS_DTRC_DTCTRL) >> 31; + + dcss_dtrc_write_irqsafe(ch, ch->dctl | CONFIG_READY, + (ch->curr_frame ^ 1) * DTRC_F1_OFS + DCSS_DTRC_DCTL); +} + +void dcss_dtrc_switch_banks(struct dcss_dtrc *dtrc) +{ + dcss_dtrc_ch_switch_banks(dtrc, 0); + dcss_dtrc_ch_switch_banks(dtrc, 1); +} diff --git a/drivers/gpu/drm/imx/dcss/dcss-hdr10-tables.h b/drivers/gpu/drm/imx/dcss/dcss-hdr10-tables.h new file mode 100644 index 000000000000..6baa886bb358 --- /dev/null +++ b/drivers/gpu/drm/imx/dcss/dcss-hdr10-tables.h @@ -0,0 +1,3018 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2018-2020 NXP. + */ + + +#ifndef __DCSS_HDR10_TABLES_H__ +#define __DCSS_HDR10_TABLES_H__ + +static const u32 dcss_cscas[20][29] = { + {0x8000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0}, + {0x3, 0x4000, 0x0, 0x5c45, 0x4000, 0xfffff5b6, 0xffffdc41, 0x4000, + 0x75b9, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40, + 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x36c, 0x36c, 0x36c}, + {0x3, 0x36ce, 0x0, 0x50d1, 0x36ce, 0xfffff6fd, 0xffffe0b1, 0x36ce, + 0x671c, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00, + 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x36c, 0x36c, + 0x36c}, + {0x3, 0x4000, 0x0, 0x628a, 0x4000, 0xfffff449, 0xffffe2b7, 0x4000, + 0x741c, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40, + 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x36c, 0x36c, 0x36c}, + {0x3, 0x4000, 0x0, 0x57ba, 0x4000, 0xffffea79, 0xffffd352, 0x4000, + 0x6ee1, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40, + 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x36c, 0x36c, 0x36c}, + {0x3, 0x36ce, 0x0, 0x564e, 0x36ce, 0xfffff5bd, 0xffffe65a, 0x36ce, + 0x65b2, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00, + 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x36c, 0x36c, + 0x36c}, + {0x3, 0x36ce, 0x0, 0x4cd6, 0x36ce, 0xffffed25, 0xffffd8de, 0x36ce, + 0x611d, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00, + 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x36c, 0x36c, + 0x36c}, + {0x1, 0x4abe, 0x0, 0x0, 0x0, 0x4abe, 0x0, 0x0, 0x0, 0x4abe, 0xffffffc0, + 0xffffffc0, 0xffffffc0, 0x0, 0x0, 0x0, 0x36c, 0x36c, 0x36c, 0xe, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff, 0x3ff, 0x3ff}, + {0x3, 0x255f, 0x0, 0x35e1, 0x255f, 0xfffff9fe, 0xffffeb21, 0x255f, + 0x44bd, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40, + 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3ff, 0x3ff, 0x3ff}, + {0x3, 0x4000, 0x0, 0x5e60, 0x4000, 0xfffff579, 0xffffdb70, 0x4000, + 0x7869, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00, + 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff, 0x3ff, + 0x3ff}, + {0x3, 0x255f, 0x0, 0x398a, 0x255f, 0xfffff929, 0xffffeee7, 0x255f, + 0x43cc, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40, + 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3ff, 0x3ff, 0x3ff}, + {0x3, 0x255f, 0x0, 0x333a, 0x255f, 0xfffff36f, 0xffffe5ea, 0x255f, + 0x40be, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40, + 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3ff, 0x3ff, 0x3ff}, + {0x3, 0x4000, 0x0, 0x64ca, 0x4000, 0xfffff404, 0xffffe20c, 0x4000, + 0x76c3, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00, + 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff, 0x3ff, + 0x3ff}, + {0x3, 0x4000, 0x0, 0x59bb, 0x4000, 0xffffe9fb, 0xffffd24d, 0x4000, + 0x7169, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00, + 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff, 0x3ff, + 0x3ff}, + {0x3, 0x4176, 0x0, 0x5e60, 0x4176, 0xfffff579, 0xffffdb70, 0x4176, + 0x7869, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40, + 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x380, 0x380, 0x380}, + {0x3, 0x380e, 0x0, 0x52a9, 0x380e, 0xfffff6c8, 0xffffdffa, 0x380e, + 0x6977, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00, + 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x380, 0x380, + 0x380}, + {0x3, 0x4176, 0x0, 0x64ca, 0x4176, 0xfffff404, 0xffffe20c, 0x4176, + 0x76c3, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40, + 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x380, 0x380, 0x380}, + {0x3, 0x4176, 0x0, 0x59bb, 0x4176, 0xffffe9fb, 0xffffd24d, 0x4176, + 0x7169, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40, + 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x380, 0x380, 0x380}, + {0x3, 0x380e, 0x0, 0x5847, 0x380e, 0xfffff581, 0xffffe5c4, 0x380e, + 0x6804, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00, + 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x380, 0x380, + 0x380}, + {0x3, 0x380e, 0x0, 0x4e97, 0x380e, 0xffffecb7, 0xffffd7fa, 0x380e, + 0x6355, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00, + 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x380, 0x380, + 0x380}, +}; + +static const u16 dcss_iluts[11][1026] = { + {0x3, 0x0, 0x200, 0x600, 0x840, 0x9c0, 0xac0, 0xbe0, 0xc90, 0xd40, + 0xe08, 0xe78, 0xef0, 0xf78, 0x1008, 0x1054, 0x10ac, 0x1108, 0x116c, + 0x11d4, 0x1222, 0x125c, 0x129c, 0x12de, 0x1324, 0x136e, 0x13ba, 0x1406, + 0x1430, 0x145d, 0x148c, 0x14bc, 0x14ef, 0x1524, 0x155b, 0x1595, 0x15d1, + 0x1607, 0x1627, 0x1649, 0x166b, 0x168f, 0x16b5, 0x16db, 0x1703, 0x172c, + 0x1757, 0x1783, 0x17b1, 0x17e0, 0x1808, 0x1821, 0x183a, 0x1855, 0x1870, + 0x188c, 0x18a9, 0x18c7, 0x18e6, 0x1905, 0x1926, 0x1947, 0x1969, 0x198c, + 0x19b1, 0x19d6, 0x19fc, 0x1a11, 0x1a25, 0x1a3a, 0x1a4f, 0x1a64, 0x1a7a, + 0x1a91, 0x1aa8, 0x1ac0, 0x1ad8, 0x1af1, 0x1b0b, 0x1b25, 0x1b3f, 0x1b5b, + 0x1b77, 0x1b93, 0x1bb0, 0x1bce, 0x1bed, 0x1c06, 0x1c16, 0x1c26, 0x1c37, + 0x1c48, 0x1c59, 0x1c6b, 0x1c7d, 0x1c8f, 0x1ca2, 0x1cb6, 0x1cc9, 0x1cdd, + 0x1cf2, 0x1d07, 0x1d1c, 0x1d32, 0x1d48, 0x1d5f, 0x1d76, 0x1d8d, 0x1da5, + 0x1dbd, 0x1dd6, 0x1df0, 0x1e04, 0x1e12, 0x1e1f, 0x1e2d, 0x1e3b, 0x1e49, + 0x1e57, 0x1e66, 0x1e75, 0x1e84, 0x1e94, 0x1ea4, 0x1eb4, 0x1ec4, 0x1ed5, + 0x1ee6, 0x1ef7, 0x1f08, 0x1f1a, 0x1f2c, 0x1f3f, 0x1f52, 0x1f65, 0x1f78, + 0x1f8c, 0x1fa0, 0x1fb5, 0x1fca, 0x1fdf, 0x1ff4, 0x2005, 0x2010, 0x201b, + 0x2027, 0x2032, 0x203e, 0x204a, 0x2057, 0x2063, 0x2070, 0x207d, 0x208a, + 0x2097, 0x20a4, 0x20b2, 0x20c0, 0x20ce, 0x20dd, 0x20eb, 0x20fa, 0x2109, + 0x2118, 0x2128, 0x2138, 0x2148, 0x2158, 0x2168, 0x2179, 0x218a, 0x219b, + 0x21ad, 0x21bf, 0x21d1, 0x21e3, 0x21f6, 0x2204, 0x220e, 0x2217, 0x2221, + 0x222b, 0x2235, 0x2240, 0x224a, 0x2255, 0x225f, 0x226a, 0x2275, 0x2281, + 0x228c, 0x2298, 0x22a3, 0x22af, 0x22bb, 0x22c8, 0x22d4, 0x22e1, 0x22ed, + 0x22fa, 0x2307, 0x2315, 0x2322, 0x2330, 0x233e, 0x234c, 0x235a, 0x2368, + 0x2377, 0x2386, 0x2395, 0x23a4, 0x23b4, 0x23c3, 0x23d3, 0x23e3, 0x23f4, + 0x2402, 0x240a, 0x2413, 0x241b, 0x2424, 0x242d, 0x2436, 0x243f, 0x2448, + 0x2452, 0x245b, 0x2465, 0x246e, 0x2478, 0x2482, 0x248c, 0x2496, 0x24a1, + 0x24ab, 0x24b6, 0x24c1, 0x24cc, 0x24d7, 0x24e2, 0x24ed, 0x24f9, 0x2504, + 0x2510, 0x251c, 0x2528, 0x2534, 0x2541, 0x254d, 0x255a, 0x2567, 0x2574, + 0x2581, 0x258f, 0x259c, 0x25aa, 0x25b8, 0x25c6, 0x25d4, 0x25e3, 0x25f1, + 0x2600, 0x2607, 0x260f, 0x2616, 0x261e, 0x2626, 0x262e, 0x2636, 0x263e, + 0x2647, 0x264f, 0x2657, 0x2660, 0x2669, 0x2671, 0x267a, 0x2683, 0x268c, + 0x2696, 0x269f, 0x26a8, 0x26b2, 0x26bc, 0x26c5, 0x26cf, 0x26d9, 0x26e4, + 0x26ee, 0x26f8, 0x2703, 0x270d, 0x2718, 0x2723, 0x272e, 0x2739, 0x2745, + 0x2750, 0x275c, 0x2767, 0x2773, 0x277f, 0x278b, 0x2798, 0x27a4, 0x27b1, + 0x27bd, 0x27ca, 0x27d7, 0x27e4, 0x27f2, 0x27ff, 0x2806, 0x280d, 0x2814, + 0x281b, 0x2822, 0x2829, 0x2831, 0x2838, 0x2840, 0x2847, 0x284f, 0x2857, + 0x285e, 0x2866, 0x286e, 0x2877, 0x287f, 0x2887, 0x288f, 0x2898, 0x28a1, + 0x28a9, 0x28b2, 0x28bb, 0x28c4, 0x28cd, 0x28d6, 0x28df, 0x28e9, 0x28f2, + 0x28fc, 0x2906, 0x2910, 0x291a, 0x2924, 0x292e, 0x2938, 0x2942, 0x294d, + 0x2958, 0x2962, 0x296d, 0x2978, 0x2983, 0x298f, 0x299a, 0x29a6, 0x29b1, + 0x29bd, 0x29c9, 0x29d5, 0x29e1, 0x29ee, 0x29fa, 0x2a03, 0x2a09, 0x2a10, + 0x2a16, 0x2a1d, 0x2a24, 0x2a2a, 0x2a31, 0x2a38, 0x2a3f, 0x2a46, 0x2a4d, + 0x2a54, 0x2a5c, 0x2a63, 0x2a6a, 0x2a72, 0x2a79, 0x2a81, 0x2a89, 0x2a91, + 0x2a99, 0x2aa1, 0x2aa9, 0x2ab1, 0x2ab9, 0x2ac2, 0x2aca, 0x2ad3, 0x2adb, + 0x2ae4, 0x2aed, 0x2af6, 0x2aff, 0x2b08, 0x2b11, 0x2b1b, 0x2b24, 0x2b2e, + 0x2b37, 0x2b41, 0x2b4b, 0x2b55, 0x2b5f, 0x2b69, 0x2b73, 0x2b7e, 0x2b88, + 0x2b93, 0x2b9e, 0x2ba9, 0x2bb3, 0x2bbf, 0x2bca, 0x2bd5, 0x2be1, 0x2bec, + 0x2bf8, 0x2c02, 0x2c08, 0x2c0e, 0x2c14, 0x2c1a, 0x2c20, 0x2c26, 0x2c2d, + 0x2c33, 0x2c3a, 0x2c40, 0x2c47, 0x2c4e, 0x2c55, 0x2c5b, 0x2c62, 0x2c69, + 0x2c71, 0x2c78, 0x2c7f, 0x2c86, 0x2c8e, 0x2c95, 0x2c9d, 0x2ca4, 0x2cac, + 0x2cb4, 0x2cbc, 0x2cc4, 0x2ccc, 0x2cd4, 0x2cdc, 0x2ce4, 0x2ced, 0x2cf5, + 0x2cfe, 0x2d07, 0x2d0f, 0x2d18, 0x2d21, 0x2d2a, 0x2d33, 0x2d3d, 0x2d46, + 0x2d4f, 0x2d59, 0x2d63, 0x2d6c, 0x2d76, 0x2d80, 0x2d8a, 0x2d94, 0x2d9f, + 0x2da9, 0x2db3, 0x2dbe, 0x2dc9, 0x2dd4, 0x2dde, 0x2dea, 0x2df5, 0x2e00, + 0x2e05, 0x2e0b, 0x2e11, 0x2e17, 0x2e1d, 0x2e23, 0x2e29, 0x2e2f, 0x2e35, + 0x2e3b, 0x2e42, 0x2e48, 0x2e4f, 0x2e55, 0x2e5c, 0x2e62, 0x2e69, 0x2e70, + 0x2e77, 0x2e7e, 0x2e85, 0x2e8c, 0x2e93, 0x2e9a, 0x2ea2, 0x2ea9, 0x2eb1, + 0x2eb8, 0x2ec0, 0x2ec7, 0x2ecf, 0x2ed7, 0x2edf, 0x2ee7, 0x2eef, 0x2ef7, + 0x2f00, 0x2f08, 0x2f11, 0x2f19, 0x2f22, 0x2f2a, 0x2f33, 0x2f3c, 0x2f45, + 0x2f4e, 0x2f58, 0x2f61, 0x2f6a, 0x2f74, 0x2f7d, 0x2f87, 0x2f91, 0x2f9b, + 0x2fa5, 0x2faf, 0x2fb9, 0x2fc3, 0x2fce, 0x2fd8, 0x2fe3, 0x2fee, 0x2ff8, + 0x3001, 0x3007, 0x300d, 0x3012, 0x3018, 0x301e, 0x3024, 0x3029, 0x302f, + 0x3035, 0x303c, 0x3042, 0x3048, 0x304e, 0x3055, 0x305b, 0x3061, 0x3068, + 0x306f, 0x3075, 0x307c, 0x3083, 0x308a, 0x3091, 0x3098, 0x309f, 0x30a6, + 0x30ad, 0x30b4, 0x30bc, 0x30c3, 0x30cb, 0x30d3, 0x30da, 0x30e2, 0x30ea, + 0x30f2, 0x30fa, 0x3102, 0x310a, 0x3113, 0x311b, 0x3123, 0x312c, 0x3135, + 0x313d, 0x3146, 0x314f, 0x3158, 0x3161, 0x316a, 0x3174, 0x317d, 0x3187, + 0x3190, 0x319a, 0x31a4, 0x31ad, 0x31b7, 0x31c2, 0x31cc, 0x31d6, 0x31e0, + 0x31eb, 0x31f6, 0x3200, 0x3205, 0x320b, 0x3210, 0x3216, 0x321c, 0x3221, + 0x3227, 0x322d, 0x3233, 0x3239, 0x323f, 0x3245, 0x324b, 0x3251, 0x3258, + 0x325e, 0x3264, 0x326b, 0x3271, 0x3278, 0x327f, 0x3285, 0x328c, 0x3293, + 0x329a, 0x32a1, 0x32a8, 0x32af, 0x32b6, 0x32be, 0x32c5, 0x32cd, 0x32d4, + 0x32dc, 0x32e4, 0x32eb, 0x32f3, 0x32fb, 0x3303, 0x330b, 0x3314, 0x331c, + 0x3324, 0x332d, 0x3335, 0x333e, 0x3347, 0x3350, 0x3358, 0x3361, 0x336b, + 0x3374, 0x337d, 0x3387, 0x3390, 0x339a, 0x33a3, 0x33ad, 0x33b7, 0x33c1, + 0x33cb, 0x33d5, 0x33e0, 0x33ea, 0x33f5, 0x33ff, 0x3405, 0x340a, 0x3410, + 0x3415, 0x341b, 0x3421, 0x3426, 0x342c, 0x3432, 0x3438, 0x343e, 0x3444, + 0x344a, 0x3450, 0x3456, 0x345d, 0x3463, 0x346a, 0x3470, 0x3477, 0x347d, + 0x3484, 0x348b, 0x3492, 0x3498, 0x349f, 0x34a7, 0x34ae, 0x34b5, 0x34bc, + 0x34c4, 0x34cb, 0x34d3, 0x34da, 0x34e2, 0x34ea, 0x34f1, 0x34f9, 0x3501, + 0x350a, 0x3512, 0x351a, 0x3522, 0x352b, 0x3533, 0x353c, 0x3545, 0x354e, + 0x3557, 0x3560, 0x3569, 0x3572, 0x357b, 0x3585, 0x358e, 0x3598, 0x35a1, + 0x35ab, 0x35b5, 0x35bf, 0x35c9, 0x35d3, 0x35de, 0x35e8, 0x35f3, 0x35fe, + 0x3604, 0x3609, 0x360f, 0x3614, 0x361a, 0x3620, 0x3626, 0x362b, 0x3631, + 0x3637, 0x363d, 0x3643, 0x3649, 0x3650, 0x3656, 0x365c, 0x3663, 0x3669, + 0x3670, 0x3676, 0x367d, 0x3684, 0x368a, 0x3691, 0x3698, 0x369f, 0x36a7, + 0x36ae, 0x36b5, 0x36bc, 0x36c4, 0x36cb, 0x36d3, 0x36db, 0x36e2, 0x36ea, + 0x36f2, 0x36fa, 0x3702, 0x370b, 0x3713, 0x371b, 0x3724, 0x372c, 0x3735, + 0x373e, 0x3747, 0x374f, 0x3758, 0x3762, 0x376b, 0x3774, 0x377e, 0x3787, + 0x3791, 0x379b, 0x37a4, 0x37ae, 0x37b9, 0x37c3, 0x37cd, 0x37d7, 0x37e2, + 0x37ed, 0x37f7, 0x3801, 0x3806, 0x380c, 0x3812, 0x3817, 0x381d, 0x3823, + 0x3829, 0x382f, 0x3835, 0x383b, 0x3841, 0x3847, 0x384d, 0x3854, 0x385a, + 0x3861, 0x3867, 0x386e, 0x3874, 0x387b, 0x3882, 0x3889, 0x3890, 0x3897, + 0x389e, 0x38a6, 0x38ad, 0x38b4, 0x38bc, 0x38c3, 0x38cb, 0x38d3, 0x38db, + 0x38e3, 0x38eb, 0x38f3, 0x38fb, 0x3903, 0x390b, 0x3914, 0x391c, 0x3925, + 0x392e, 0x3937, 0x3940, 0x3949, 0x3952, 0x395b, 0x3965, 0x396e, 0x3978, + 0x3981, 0x398b, 0x3995, 0x399f, 0x39a9, 0x39b3, 0x39be, 0x39c8, 0x39d3, + 0x39de, 0x39e8, 0x39f3, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x3}, + {0x3, 0x0, 0x200, 0x500, 0x700, 0x8c0, 0xa20, 0xae0, 0xbe0, 0xc70, + 0xd00, 0xdb0, 0xe30, 0xe98, 0xf00, 0xf78, 0xff8, 0x103c, 0x1084, + 0x10d0, 0x1120, 0x1178, 0x11d0, 0x1218, 0x124a, 0x127e, 0x12b6, 0x12f0, + 0x132c, 0x136c, 0x13ae, 0x13f2, 0x141d, 0x1442, 0x1469, 0x1491, 0x14bb, + 0x14e6, 0x1513, 0x1542, 0x1572, 0x15a4, 0x15d7, 0x1606, 0x1621, 0x163e, + 0x165b, 0x1679, 0x1698, 0x16b9, 0x16da, 0x16fc, 0x171f, 0x1743, 0x1768, + 0x178e, 0x17b5, 0x17dd, 0x1803, 0x1818, 0x182e, 0x1844, 0x185b, 0x1873, + 0x188b, 0x18a4, 0x18bd, 0x18d7, 0x18f2, 0x190d, 0x1929, 0x1945, 0x1962, + 0x1980, 0x199f, 0x19be, 0x19de, 0x19ff, 0x1a10, 0x1a21, 0x1a32, 0x1a44, + 0x1a56, 0x1a69, 0x1a7c, 0x1a8f, 0x1aa3, 0x1ab7, 0x1acc, 0x1ae1, 0x1af6, + 0x1b0c, 0x1b23, 0x1b39, 0x1b51, 0x1b68, 0x1b81, 0x1b99, 0x1bb2, 0x1bcc, + 0x1be6, 0x1c00, 0x1c0e, 0x1c1b, 0x1c29, 0x1c38, 0x1c46, 0x1c55, 0x1c64, + 0x1c74, 0x1c83, 0x1c93, 0x1ca3, 0x1cb4, 0x1cc5, 0x1cd6, 0x1ce7, 0x1cf9, + 0x1d0b, 0x1d1d, 0x1d30, 0x1d42, 0x1d56, 0x1d69, 0x1d7d, 0x1d91, 0x1da6, + 0x1dbb, 0x1dd0, 0x1de6, 0x1dfc, 0x1e09, 0x1e14, 0x1e20, 0x1e2b, 0x1e37, + 0x1e43, 0x1e4f, 0x1e5c, 0x1e69, 0x1e75, 0x1e82, 0x1e90, 0x1e9d, 0x1eab, + 0x1eb9, 0x1ec7, 0x1ed5, 0x1ee3, 0x1ef2, 0x1f01, 0x1f10, 0x1f20, 0x1f2f, + 0x1f3f, 0x1f4f, 0x1f60, 0x1f70, 0x1f81, 0x1f92, 0x1fa3, 0x1fb5, 0x1fc7, + 0x1fd9, 0x1feb, 0x1ffe, 0x2008, 0x2012, 0x201b, 0x2025, 0x202f, 0x2039, + 0x2043, 0x204e, 0x2058, 0x2063, 0x206e, 0x2079, 0x2084, 0x208f, 0x209b, + 0x20a6, 0x20b2, 0x20be, 0x20ca, 0x20d6, 0x20e3, 0x20ef, 0x20fc, 0x2109, + 0x2116, 0x2123, 0x2131, 0x213e, 0x214c, 0x215a, 0x2168, 0x2176, 0x2185, + 0x2194, 0x21a2, 0x21b2, 0x21c1, 0x21d0, 0x21e0, 0x21f0, 0x2200, 0x2208, + 0x2210, 0x2218, 0x2221, 0x2229, 0x2232, 0x223b, 0x2244, 0x224d, 0x2256, + 0x225f, 0x2268, 0x2272, 0x227b, 0x2285, 0x228f, 0x2299, 0x22a3, 0x22ad, + 0x22b7, 0x22c2, 0x22cc, 0x22d7, 0x22e2, 0x22ed, 0x22f8, 0x2303, 0x230e, + 0x231a, 0x2325, 0x2331, 0x233d, 0x2349, 0x2355, 0x2361, 0x236e, 0x237a, + 0x2387, 0x2394, 0x23a1, 0x23ae, 0x23bc, 0x23c9, 0x23d7, 0x23e5, 0x23f3, + 0x2400, 0x2407, 0x240f, 0x2416, 0x241d, 0x2425, 0x242c, 0x2434, 0x243c, + 0x2444, 0x244c, 0x2454, 0x245c, 0x2464, 0x246c, 0x2475, 0x247d, 0x2486, + 0x248e, 0x2497, 0x24a0, 0x24a9, 0x24b2, 0x24bb, 0x24c4, 0x24ce, 0x24d7, + 0x24e1, 0x24eb, 0x24f4, 0x24fe, 0x2508, 0x2512, 0x251d, 0x2527, 0x2531, + 0x253c, 0x2547, 0x2552, 0x255d, 0x2568, 0x2573, 0x257e, 0x2589, 0x2595, + 0x25a1, 0x25ac, 0x25b8, 0x25c4, 0x25d1, 0x25dd, 0x25e9, 0x25f6, 0x2601, + 0x2607, 0x260e, 0x2614, 0x261b, 0x2622, 0x2629, 0x262f, 0x2636, 0x263d, + 0x2644, 0x264c, 0x2653, 0x265a, 0x2661, 0x2669, 0x2670, 0x2678, 0x2680, + 0x2687, 0x268f, 0x2697, 0x269f, 0x26a7, 0x26af, 0x26b8, 0x26c0, 0x26c8, + 0x26d1, 0x26d9, 0x26e2, 0x26eb, 0x26f4, 0x26fd, 0x2706, 0x270f, 0x2718, + 0x2722, 0x272b, 0x2734, 0x273e, 0x2748, 0x2752, 0x275c, 0x2766, 0x2770, + 0x277a, 0x2784, 0x278f, 0x2799, 0x27a4, 0x27af, 0x27b9, 0x27c4, 0x27d0, + 0x27db, 0x27e6, 0x27f1, 0x27fd, 0x2804, 0x280a, 0x2810, 0x2816, 0x281c, + 0x2822, 0x2828, 0x282f, 0x2835, 0x283b, 0x2842, 0x2848, 0x284f, 0x2855, + 0x285c, 0x2863, 0x286a, 0x2870, 0x2877, 0x287e, 0x2886, 0x288d, 0x2894, + 0x289b, 0x28a3, 0x28aa, 0x28b2, 0x28b9, 0x28c1, 0x28c9, 0x28d0, 0x28d8, + 0x28e0, 0x28e8, 0x28f1, 0x28f9, 0x2901, 0x2909, 0x2912, 0x291a, 0x2923, + 0x292c, 0x2935, 0x293d, 0x2946, 0x294f, 0x2959, 0x2962, 0x296b, 0x2975, + 0x297e, 0x2988, 0x2991, 0x299b, 0x29a5, 0x29af, 0x29b9, 0x29c3, 0x29cd, + 0x29d8, 0x29e2, 0x29ed, 0x29f7, 0x2a01, 0x2a06, 0x2a0c, 0x2a11, 0x2a17, + 0x2a1c, 0x2a22, 0x2a28, 0x2a2e, 0x2a33, 0x2a39, 0x2a3f, 0x2a45, 0x2a4b, + 0x2a52, 0x2a58, 0x2a5e, 0x2a64, 0x2a6b, 0x2a71, 0x2a78, 0x2a7e, 0x2a85, + 0x2a8b, 0x2a92, 0x2a99, 0x2aa0, 0x2aa7, 0x2aae, 0x2ab5, 0x2abc, 0x2ac3, + 0x2aca, 0x2ad2, 0x2ad9, 0x2ae1, 0x2ae8, 0x2af0, 0x2af7, 0x2aff, 0x2b07, + 0x2b0f, 0x2b17, 0x2b1f, 0x2b27, 0x2b2f, 0x2b38, 0x2b40, 0x2b48, 0x2b51, + 0x2b59, 0x2b62, 0x2b6b, 0x2b74, 0x2b7d, 0x2b86, 0x2b8f, 0x2b98, 0x2ba1, + 0x2baa, 0x2bb4, 0x2bbd, 0x2bc7, 0x2bd0, 0x2bda, 0x2be4, 0x2bee, 0x2bf8, + 0x2c01, 0x2c06, 0x2c0b, 0x2c10, 0x2c16, 0x2c1b, 0x2c20, 0x2c26, 0x2c2b, + 0x2c31, 0x2c36, 0x2c3c, 0x2c41, 0x2c47, 0x2c4d, 0x2c53, 0x2c59, 0x2c5e, + 0x2c64, 0x2c6a, 0x2c70, 0x2c77, 0x2c7d, 0x2c83, 0x2c89, 0x2c90, 0x2c96, + 0x2c9d, 0x2ca3, 0x2caa, 0x2cb0, 0x2cb7, 0x2cbe, 0x2cc5, 0x2ccc, 0x2cd3, + 0x2cda, 0x2ce1, 0x2ce8, 0x2cef, 0x2cf6, 0x2cfe, 0x2d05, 0x2d0d, 0x2d14, + 0x2d1c, 0x2d24, 0x2d2b, 0x2d33, 0x2d3b, 0x2d43, 0x2d4b, 0x2d53, 0x2d5b, + 0x2d64, 0x2d6c, 0x2d74, 0x2d7d, 0x2d85, 0x2d8e, 0x2d97, 0x2da0, 0x2da9, + 0x2db2, 0x2dbb, 0x2dc4, 0x2dcd, 0x2dd6, 0x2de0, 0x2de9, 0x2df3, 0x2dfc, + 0x2e03, 0x2e08, 0x2e0d, 0x2e12, 0x2e17, 0x2e1c, 0x2e21, 0x2e26, 0x2e2b, + 0x2e30, 0x2e36, 0x2e3b, 0x2e41, 0x2e46, 0x2e4c, 0x2e51, 0x2e57, 0x2e5c, + 0x2e62, 0x2e68, 0x2e6e, 0x2e73, 0x2e79, 0x2e7f, 0x2e85, 0x2e8b, 0x2e92, + 0x2e98, 0x2e9e, 0x2ea4, 0x2eab, 0x2eb1, 0x2eb8, 0x2ebe, 0x2ec5, 0x2ecb, + 0x2ed2, 0x2ed9, 0x2ee0, 0x2ee6, 0x2eed, 0x2ef4, 0x2efb, 0x2f03, 0x2f0a, + 0x2f11, 0x2f18, 0x2f20, 0x2f27, 0x2f2f, 0x2f36, 0x2f3e, 0x2f46, 0x2f4e, + 0x2f55, 0x2f5d, 0x2f65, 0x2f6d, 0x2f76, 0x2f7e, 0x2f86, 0x2f8e, 0x2f97, + 0x2f9f, 0x2fa8, 0x2fb1, 0x2fb9, 0x2fc2, 0x2fcb, 0x2fd4, 0x2fdd, 0x2fe6, + 0x2ff0, 0x2ff9, 0x3001, 0x3006, 0x300a, 0x300f, 0x3014, 0x3019, 0x301e, + 0x3023, 0x3028, 0x302d, 0x3032, 0x3037, 0x303d, 0x3042, 0x3047, 0x304d, + 0x3052, 0x3057, 0x305d, 0x3062, 0x3068, 0x306e, 0x3073, 0x3079, 0x307f, + 0x3085, 0x308b, 0x3091, 0x3097, 0x309d, 0x30a3, 0x30a9, 0x30af, 0x30b6, + 0x30bc, 0x30c2, 0x30c9, 0x30cf, 0x30d6, 0x30dd, 0x30e3, 0x30ea, 0x30f1, + 0x30f8, 0x30ff, 0x3106, 0x310d, 0x3114, 0x311b, 0x3122, 0x3129, 0x3131, + 0x3138, 0x3140, 0x3147, 0x314f, 0x3157, 0x315e, 0x3166, 0x316e, 0x3176, + 0x317e, 0x3186, 0x318f, 0x3197, 0x319f, 0x31a8, 0x31b0, 0x31b9, 0x31c1, + 0x31ca, 0x31d3, 0x31dc, 0x31e5, 0x31ee, 0x31f7, 0x3200, 0x3204, 0x3209, + 0x320e, 0x3213, 0x3217, 0x321c, 0x3221, 0x3226, 0x322b, 0x3230, 0x3235, + 0x323a, 0x323f, 0x3245, 0x324a, 0x324f, 0x3255, 0x325a, 0x325f, 0x3265, + 0x326a, 0x3270, 0x3276, 0x327b, 0x3281, 0x3287, 0x328d, 0x3293, 0x3299, + 0x329f, 0x32a5, 0x32ab, 0x32b1, 0x32b7, 0x32bd, 0x32c4, 0x32ca, 0x32d1, + 0x32d7, 0x32de, 0x32e4, 0x32eb, 0x32f2, 0x32f8, 0x32ff, 0x3306, 0x330d, + 0x3314, 0x331b, 0x3322, 0x332a, 0x3331, 0x3338, 0x3340, 0x3347, 0x334f, + 0x3357, 0x335e, 0x3366, 0x336e, 0x3376, 0x337e, 0x3386, 0x338e, 0x3396, + 0x339e, 0x33a7, 0x33af, 0x33b8, 0x33c0, 0x33c9, 0x33d2, 0x33da, 0x33e3, + 0x33ec, 0x33f5, 0x33fe, 0x3404, 0x3408, 0x340d, 0x3412, 0x3416, 0x341b, + 0x3420, 0x3425, 0x342a, 0x342f, 0x3434, 0x3439, 0x343e, 0x3443, 0x3448, + 0x344e, 0x3453, 0x3458, 0x345e, 0x3463, 0x3469, 0x346e, 0x3474, 0x347a, + 0x347f, 0x3485, 0x348b, 0x3491, 0x3497, 0x349d, 0x34a3, 0x34a9, 0x34af, + 0x34b5, 0x34bb, 0x34c2, 0x34c8, 0x34ce, 0x34d5, 0x34db, 0x34e2, 0x34e9, + 0x34ef, 0x34f6, 0x34fd, 0x3504, 0x350b, 0x3512, 0x3519, 0x3520, 0x3527, + 0x352f, 0x3536, 0x353d, 0x3545, 0x354c, 0x3554, 0x355c, 0x3563, 0x356b, + 0x3573, 0x357b, 0x3583, 0x358b, 0x3593, 0x359c, 0x35a4, 0x35ad, 0x35b5, + 0x35be, 0x35c6, 0x35cf, 0x35d8, 0x35e1, 0x35ea, 0x35f3, 0x35fc, 0x3602, + 0x3607, 0x360c, 0x3610, 0x3615, 0x361a, 0x361f, 0x3624, 0x3629, 0x362e, + 0x3633, 0x3638, 0x363d, 0x3642, 0x3647, 0x364d, 0x3652, 0x3657, 0x365d, + 0x3662, 0x3668, 0x366d, 0x3673, 0x3679, 0x367f, 0x3684, 0x368a, 0x3690, + 0x3696, 0x369c, 0x36a2, 0x36a8, 0x36ae, 0x36b5, 0x36bb, 0x36c1, 0x36c8, + 0x36ce, 0x36d5, 0x36db, 0x36e2, 0x36e9, 0x36f0, 0x36f6, 0x36fd, 0x3704, + 0x370b, 0x3712, 0x371a, 0x3721, 0x3728, 0x372f, 0x3737, 0x373e, 0x3746, + 0x374e, 0x3755, 0x375d, 0x3765, 0x376d, 0x3775, 0x377d, 0x3785, 0x378d, + 0x3796, 0x379e, 0x37a7, 0x37af, 0x37b8, 0x37c1, 0x37c9, 0x37d2, 0x37db, + 0x37e4, 0x37ed, 0x37f7, 0x3800, 0x3804, 0x3809, 0x380e, 0x3813, 0x3818, + 0x381d, 0x3821, 0x3826, 0x382c, 0x3831, 0x3836, 0x383b, 0x3840, 0x3846, + 0x384b, 0x3850, 0x3856, 0x385b, 0x3861, 0x3866, 0x386c, 0x3872, 0x3878, + 0x387d, 0x3883, 0x3889, 0x388f, 0x3895, 0x389b, 0x38a2, 0x38a8, 0x38ae, + 0x38b5, 0x38bb, 0x38c1, 0x38c8, 0x38cf, 0x38d5, 0x38dc, 0x38e3, 0x38ea, + 0x38f1, 0x38f8, 0x38ff, 0x3906, 0x390d, 0x3914, 0x391b, 0x3923, 0x392a, + 0x3932, 0x3939, 0x3941, 0x3949, 0x3951, 0x3959, 0x3961, 0x3969, 0x3971, + 0x3979, 0x3981, 0x398a, 0x3992, 0x399b, 0x39a3, 0x39ac, 0x39b5, 0x39be, + 0x39c7, 0x39d0, 0x39d9, 0x39e2, 0x39ec, 0x39f5, 0x39ff, 0x3}, + {0x3, 0x0, 0x4, 0x8, 0xc, 0x10, 0x14, 0x18, 0x1d, 0x21, 0x25, 0x29, + 0x2d, 0x31, 0x36, 0x3a, 0x3e, 0x42, 0x46, 0x4a, 0x4e, 0x53, 0x57, 0x5b, + 0x5f, 0x63, 0x67, 0x6c, 0x70, 0x74, 0x78, 0x7c, 0x80, 0x84, 0x89, 0x8d, + 0x91, 0x95, 0x99, 0x9d, 0xa2, 0xa6, 0xaa, 0xae, 0xb2, 0xb6, 0xbb, 0xbf, + 0xc3, 0xc7, 0xcb, 0xcf, 0xd3, 0xd8, 0xdc, 0xe0, 0xe4, 0xe8, 0xec, 0xf1, + 0xf5, 0xf9, 0xfd, 0x101, 0x105, 0x109, 0x10e, 0x112, 0x116, 0x11a, + 0x11e, 0x122, 0x126, 0x12a, 0x12e, 0x132, 0x137, 0x13b, 0x13f, 0x143, + 0x148, 0x14c, 0x151, 0x155, 0x15a, 0x15e, 0x163, 0x167, 0x16c, 0x171, + 0x176, 0x17a, 0x17f, 0x184, 0x189, 0x18e, 0x193, 0x197, 0x19c, 0x1a1, + 0x1a6, 0x1ac, 0x1b1, 0x1b6, 0x1bb, 0x1c0, 0x1c5, 0x1cb, 0x1d0, 0x1d5, + 0x1db, 0x1e0, 0x1e6, 0x1eb, 0x1f1, 0x1f6, 0x1fc, 0x201, 0x207, 0x20d, + 0x212, 0x218, 0x21e, 0x224, 0x22a, 0x230, 0x235, 0x23b, 0x241, 0x247, + 0x24d, 0x254, 0x25a, 0x260, 0x266, 0x26c, 0x273, 0x279, 0x27f, 0x286, + 0x28c, 0x292, 0x299, 0x29f, 0x2a6, 0x2ad, 0x2b3, 0x2ba, 0x2c0, 0x2c7, + 0x2ce, 0x2d5, 0x2dc, 0x2e2, 0x2e9, 0x2f0, 0x2f7, 0x2fe, 0x305, 0x30c, + 0x313, 0x31b, 0x322, 0x329, 0x330, 0x338, 0x33f, 0x346, 0x34e, 0x355, + 0x35c, 0x364, 0x36c, 0x373, 0x37b, 0x382, 0x38a, 0x392, 0x399, 0x3a1, + 0x3a9, 0x3b1, 0x3b9, 0x3c1, 0x3c9, 0x3d1, 0x3d9, 0x3e1, 0x3e9, 0x3f1, + 0x3f9, 0x402, 0x40a, 0x412, 0x41a, 0x423, 0x42b, 0x434, 0x43c, 0x445, + 0x44d, 0x456, 0x45e, 0x467, 0x470, 0x478, 0x481, 0x48a, 0x493, 0x49c, + 0x4a5, 0x4ae, 0x4b7, 0x4c0, 0x4c9, 0x4d2, 0x4db, 0x4e4, 0x4ed, 0x4f7, + 0x500, 0x509, 0x513, 0x51c, 0x525, 0x52f, 0x538, 0x542, 0x54c, 0x555, + 0x55f, 0x569, 0x572, 0x57c, 0x586, 0x590, 0x59a, 0x5a4, 0x5ad, 0x5b7, + 0x5c2, 0x5cc, 0x5d6, 0x5e0, 0x5ea, 0x5f4, 0x5ff, 0x609, 0x613, 0x61e, + 0x628, 0x632, 0x63d, 0x647, 0x652, 0x65d, 0x667, 0x672, 0x67d, 0x687, + 0x692, 0x69d, 0x6a8, 0x6b3, 0x6be, 0x6c9, 0x6d4, 0x6df, 0x6ea, 0x6f5, + 0x700, 0x70c, 0x717, 0x722, 0x72d, 0x739, 0x744, 0x750, 0x75b, 0x767, + 0x772, 0x77e, 0x789, 0x795, 0x7a1, 0x7ad, 0x7b8, 0x7c4, 0x7d0, 0x7dc, + 0x7e8, 0x7f4, 0x800, 0x80c, 0x818, 0x824, 0x831, 0x83d, 0x849, 0x855, + 0x862, 0x86e, 0x87b, 0x887, 0x893, 0x8a0, 0x8ad, 0x8b9, 0x8c6, 0x8d3, + 0x8df, 0x8ec, 0x8f9, 0x906, 0x913, 0x920, 0x92d, 0x93a, 0x947, 0x954, + 0x961, 0x96e, 0x97b, 0x988, 0x996, 0x9a3, 0x9b0, 0x9be, 0x9cb, 0x9d9, + 0x9e6, 0x9f4, 0xa02, 0xa0f, 0xa1d, 0xa2b, 0xa38, 0xa46, 0xa54, 0xa62, + 0xa70, 0xa7e, 0xa8c, 0xa9a, 0xaa8, 0xab6, 0xac4, 0xad2, 0xae1, 0xaef, + 0xafd, 0xb0c, 0xb1a, 0xb29, 0xb37, 0xb46, 0xb54, 0xb63, 0xb71, 0xb80, + 0xb8f, 0xb9e, 0xbac, 0xbbb, 0xbca, 0xbd9, 0xbe8, 0xbf7, 0xc06, 0xc15, + 0xc24, 0xc34, 0xc43, 0xc52, 0xc61, 0xc71, 0xc80, 0xc90, 0xc9f, 0xcaf, + 0xcbe, 0xcce, 0xcdd, 0xced, 0xcfd, 0xd0c, 0xd1c, 0xd2c, 0xd3c, 0xd4c, + 0xd5c, 0xd6c, 0xd7c, 0xd8c, 0xd9c, 0xdac, 0xdbc, 0xdcd, 0xddd, 0xded, + 0xdfe, 0xe0e, 0xe1f, 0xe2f, 0xe40, 0xe50, 0xe61, 0xe71, 0xe82, 0xe93, + 0xea4, 0xeb4, 0xec5, 0xed6, 0xee7, 0xef8, 0xf09, 0xf1a, 0xf2b, 0xf3d, + 0xf4e, 0xf5f, 0xf70, 0xf82, 0xf93, 0xfa4, 0xfb6, 0xfc7, 0xfd9, 0xfea, + 0xffc, 0x100e, 0x101f, 0x1031, 0x1043, 0x1055, 0x1067, 0x1078, 0x108a, + 0x109c, 0x10ae, 0x10c0, 0x10d3, 0x10e5, 0x10f7, 0x1109, 0x111b, 0x112e, + 0x1140, 0x1153, 0x1165, 0x1178, 0x118a, 0x119d, 0x11af, 0x11c2, 0x11d5, + 0x11e7, 0x11fa, 0x120d, 0x1220, 0x1233, 0x1246, 0x1259, 0x126c, 0x127f, + 0x1292, 0x12a5, 0x12b8, 0x12cc, 0x12df, 0x12f2, 0x1306, 0x1319, 0x132d, + 0x1340, 0x1354, 0x1367, 0x137b, 0x138f, 0x13a2, 0x13b6, 0x13ca, 0x13de, + 0x13f2, 0x1406, 0x141a, 0x142e, 0x1442, 0x1456, 0x146a, 0x147e, 0x1492, + 0x14a7, 0x14bb, 0x14cf, 0x14e4, 0x14f8, 0x150d, 0x1521, 0x1536, 0x154a, + 0x155f, 0x1574, 0x1588, 0x159d, 0x15b2, 0x15c7, 0x15dc, 0x15f1, 0x1606, + 0x161b, 0x1630, 0x1645, 0x165a, 0x1670, 0x1685, 0x169a, 0x16b0, 0x16c5, + 0x16da, 0x16f0, 0x1705, 0x171b, 0x1731, 0x1746, 0x175c, 0x1772, 0x1788, + 0x179d, 0x17b3, 0x17c9, 0x17df, 0x17f5, 0x180b, 0x1821, 0x1837, 0x184e, + 0x1864, 0x187a, 0x1890, 0x18a7, 0x18bd, 0x18d4, 0x18ea, 0x1901, 0x1917, + 0x192e, 0x1944, 0x195b, 0x1972, 0x1989, 0x19a0, 0x19b6, 0x19cd, 0x19e4, + 0x19fb, 0x1a12, 0x1a29, 0x1a41, 0x1a58, 0x1a6f, 0x1a86, 0x1a9e, 0x1ab5, + 0x1acc, 0x1ae4, 0x1afb, 0x1b13, 0x1b2a, 0x1b42, 0x1b5a, 0x1b72, 0x1b89, + 0x1ba1, 0x1bb9, 0x1bd1, 0x1be9, 0x1c01, 0x1c19, 0x1c31, 0x1c49, 0x1c61, + 0x1c79, 0x1c92, 0x1caa, 0x1cc2, 0x1cdb, 0x1cf3, 0x1d0b, 0x1d24, 0x1d3d, + 0x1d55, 0x1d6e, 0x1d86, 0x1d9f, 0x1db8, 0x1dd1, 0x1dea, 0x1e03, 0x1e1c, + 0x1e35, 0x1e4e, 0x1e67, 0x1e80, 0x1e99, 0x1eb2, 0x1ecb, 0x1ee5, 0x1efe, + 0x1f18, 0x1f31, 0x1f4a, 0x1f64, 0x1f7e, 0x1f97, 0x1fb1, 0x1fcb, 0x1fe4, + 0x1ffe, 0x2018, 0x2032, 0x204c, 0x2066, 0x2080, 0x209a, 0x20b4, 0x20ce, + 0x20e8, 0x2103, 0x211d, 0x2137, 0x2152, 0x216c, 0x2187, 0x21a1, 0x21bc, + 0x21d6, 0x21f1, 0x220c, 0x2226, 0x2241, 0x225c, 0x2277, 0x2292, 0x22ad, + 0x22c8, 0x22e3, 0x22fe, 0x2319, 0x2334, 0x2350, 0x236b, 0x2386, 0x23a2, + 0x23bd, 0x23d9, 0x23f4, 0x2410, 0x242b, 0x2447, 0x2463, 0x247e, 0x249a, + 0x24b6, 0x24d2, 0x24ee, 0x250a, 0x2526, 0x2542, 0x255e, 0x257a, 0x2596, + 0x25b3, 0x25cf, 0x25eb, 0x2608, 0x2624, 0x2640, 0x265d, 0x267a, 0x2696, + 0x26b3, 0x26d0, 0x26ec, 0x2709, 0x2726, 0x2743, 0x2760, 0x277d, 0x279a, + 0x27b7, 0x27d4, 0x27f1, 0x280e, 0x282b, 0x2849, 0x2866, 0x2884, 0x28a1, + 0x28be, 0x28dc, 0x28fa, 0x2917, 0x2935, 0x2953, 0x2970, 0x298e, 0x29ac, + 0x29ca, 0x29e8, 0x2a06, 0x2a24, 0x2a42, 0x2a60, 0x2a7e, 0x2a9c, 0x2abb, + 0x2ad9, 0x2af7, 0x2b16, 0x2b34, 0x2b53, 0x2b71, 0x2b90, 0x2bae, 0x2bcd, + 0x2bec, 0x2c0a, 0x2c29, 0x2c48, 0x2c67, 0x2c86, 0x2ca5, 0x2cc4, 0x2ce3, + 0x2d02, 0x2d21, 0x2d41, 0x2d60, 0x2d7f, 0x2d9f, 0x2dbe, 0x2dde, 0x2dfd, + 0x2e1d, 0x2e3c, 0x2e5c, 0x2e7c, 0x2e9b, 0x2ebb, 0x2edb, 0x2efb, 0x2f1b, + 0x2f3b, 0x2f5b, 0x2f7b, 0x2f9b, 0x2fbb, 0x2fdb, 0x2ffc, 0x301c, 0x303c, + 0x305d, 0x307d, 0x309e, 0x30be, 0x30df, 0x30ff, 0x3120, 0x3141, 0x3161, + 0x3182, 0x31a3, 0x31c4, 0x31e5, 0x3206, 0x3227, 0x3248, 0x3269, 0x328a, + 0x32ac, 0x32cd, 0x32ee, 0x3310, 0x3331, 0x3353, 0x3374, 0x3396, 0x33b7, + 0x33d9, 0x33fb, 0x341c, 0x343e, 0x3460, 0x3482, 0x34a4, 0x34c6, 0x34e8, + 0x350a, 0x352c, 0x354e, 0x3571, 0x3593, 0x35b5, 0x35d7, 0x35fa, 0x361c, + 0x363f, 0x3661, 0x3684, 0x36a7, 0x36c9, 0x36ec, 0x370f, 0x3732, 0x3755, + 0x3778, 0x379b, 0x37be, 0x37e1, 0x3804, 0x3827, 0x384a, 0x386d, 0x3891, + 0x38b4, 0x38d7, 0x38fb, 0x391e, 0x3942, 0x3966, 0x3989, 0x39ad, 0x39d1, + 0x39f4, 0x3a18, 0x3a3c, 0x3a60, 0x3a84, 0x3aa8, 0x3acc, 0x3af0, 0x3b14, + 0x3b39, 0x3b5d, 0x3b81, 0x3ba6, 0x3bca, 0x3bee, 0x3c13, 0x3c37, 0x3c5c, + 0x3c81, 0x3ca5, 0x3cca, 0x3cef, 0x3d14, 0x3d39, 0x3d5e, 0x3d83, 0x3da8, + 0x3dcd, 0x3df2, 0x3e17, 0x3e3c, 0x3e61, 0x3e87, 0x3eac, 0x3ed2, 0x3ef7, + 0x3f1c, 0x3f42, 0x3f68, 0x3f8d, 0x3fb3, 0x3fd9, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x0}, + {0x3, 0x0, 0x3, 0x7, 0xa, 0xe, 0x11, 0x15, 0x18, 0x1c, 0x20, 0x23, + 0x27, 0x2a, 0x2e, 0x31, 0x35, 0x38, 0x3c, 0x40, 0x43, 0x47, 0x4a, 0x4e, + 0x51, 0x55, 0x58, 0x5c, 0x60, 0x63, 0x67, 0x6a, 0x6e, 0x71, 0x75, 0x78, + 0x7c, 0x80, 0x83, 0x87, 0x8a, 0x8e, 0x91, 0x95, 0x99, 0x9c, 0xa0, 0xa3, + 0xa7, 0xaa, 0xae, 0xb1, 0xb5, 0xb9, 0xbc, 0xc0, 0xc3, 0xc7, 0xca, 0xce, + 0xd1, 0xd5, 0xd9, 0xdc, 0xe0, 0xe3, 0xe7, 0xea, 0xee, 0xf1, 0xf5, 0xf9, + 0xfc, 0x100, 0x103, 0x107, 0x10a, 0x10e, 0x112, 0x115, 0x119, 0x11c, + 0x120, 0x123, 0x126, 0x12a, 0x12d, 0x131, 0x134, 0x138, 0x13c, 0x13f, + 0x143, 0x147, 0x14b, 0x14e, 0x152, 0x156, 0x15a, 0x15e, 0x162, 0x166, + 0x16a, 0x16e, 0x172, 0x176, 0x17a, 0x17e, 0x182, 0x186, 0x18a, 0x18f, + 0x193, 0x197, 0x19b, 0x1a0, 0x1a4, 0x1a8, 0x1ad, 0x1b1, 0x1b5, 0x1ba, + 0x1be, 0x1c3, 0x1c7, 0x1cc, 0x1d0, 0x1d5, 0x1d9, 0x1de, 0x1e3, 0x1e7, + 0x1ec, 0x1f1, 0x1f6, 0x1fa, 0x1ff, 0x204, 0x209, 0x20e, 0x213, 0x217, + 0x21c, 0x221, 0x226, 0x22b, 0x230, 0x236, 0x23b, 0x240, 0x245, 0x24a, + 0x24f, 0x255, 0x25a, 0x25f, 0x264, 0x26a, 0x26f, 0x274, 0x27a, 0x27f, + 0x285, 0x28a, 0x290, 0x295, 0x29b, 0x2a0, 0x2a6, 0x2ac, 0x2b1, 0x2b7, + 0x2bd, 0x2c2, 0x2c8, 0x2ce, 0x2d4, 0x2da, 0x2df, 0x2e5, 0x2eb, 0x2f1, + 0x2f7, 0x2fd, 0x303, 0x309, 0x30f, 0x315, 0x31c, 0x322, 0x328, 0x32e, + 0x334, 0x33b, 0x341, 0x347, 0x34d, 0x354, 0x35a, 0x361, 0x367, 0x36d, + 0x374, 0x37a, 0x381, 0x388, 0x38e, 0x395, 0x39b, 0x3a2, 0x3a9, 0x3b0, + 0x3b6, 0x3bd, 0x3c4, 0x3cb, 0x3d2, 0x3d8, 0x3df, 0x3e6, 0x3ed, 0x3f4, + 0x3fb, 0x402, 0x409, 0x411, 0x418, 0x41f, 0x426, 0x42d, 0x434, 0x43c, + 0x443, 0x44a, 0x452, 0x459, 0x460, 0x468, 0x46f, 0x477, 0x47e, 0x486, + 0x48d, 0x495, 0x49c, 0x4a4, 0x4ac, 0x4b3, 0x4bb, 0x4c3, 0x4cb, 0x4d3, + 0x4da, 0x4e2, 0x4ea, 0x4f2, 0x4fa, 0x502, 0x50a, 0x512, 0x51a, 0x522, + 0x52a, 0x532, 0x53a, 0x543, 0x54b, 0x553, 0x55b, 0x564, 0x56c, 0x574, + 0x57d, 0x585, 0x58d, 0x596, 0x59e, 0x5a7, 0x5af, 0x5b8, 0x5c1, 0x5c9, + 0x5d2, 0x5db, 0x5e3, 0x5ec, 0x5f5, 0x5fe, 0x606, 0x60f, 0x618, 0x621, + 0x62a, 0x633, 0x63c, 0x645, 0x64e, 0x657, 0x660, 0x669, 0x672, 0x67b, + 0x685, 0x68e, 0x697, 0x6a0, 0x6aa, 0x6b3, 0x6bd, 0x6c6, 0x6cf, 0x6d9, + 0x6e2, 0x6ec, 0x6f5, 0x6ff, 0x709, 0x712, 0x71c, 0x726, 0x72f, 0x739, + 0x743, 0x74d, 0x756, 0x760, 0x76a, 0x774, 0x77e, 0x788, 0x792, 0x79c, + 0x7a6, 0x7b0, 0x7ba, 0x7c4, 0x7cf, 0x7d9, 0x7e3, 0x7ed, 0x7f7, 0x802, + 0x80c, 0x816, 0x821, 0x82b, 0x836, 0x840, 0x84b, 0x855, 0x860, 0x86a, + 0x875, 0x880, 0x88a, 0x895, 0x8a0, 0x8ab, 0x8b5, 0x8c0, 0x8cb, 0x8d6, + 0x8e1, 0x8ec, 0x8f7, 0x902, 0x90d, 0x918, 0x923, 0x92e, 0x939, 0x944, + 0x950, 0x95b, 0x966, 0x971, 0x97d, 0x988, 0x993, 0x99f, 0x9aa, 0x9b6, + 0x9c1, 0x9cd, 0x9d8, 0x9e4, 0x9f0, 0x9fb, 0xa07, 0xa13, 0xa1e, 0xa2a, + 0xa36, 0xa42, 0xa4e, 0xa59, 0xa65, 0xa71, 0xa7d, 0xa89, 0xa95, 0xaa1, + 0xaad, 0xab9, 0xac6, 0xad2, 0xade, 0xaea, 0xaf6, 0xb03, 0xb0f, 0xb1b, + 0xb28, 0xb34, 0xb41, 0xb4d, 0xb5a, 0xb66, 0xb73, 0xb7f, 0xb8c, 0xb98, + 0xba5, 0xbb2, 0xbbf, 0xbcb, 0xbd8, 0xbe5, 0xbf2, 0xbff, 0xc0c, 0xc19, + 0xc25, 0xc32, 0xc40, 0xc4d, 0xc5a, 0xc67, 0xc74, 0xc81, 0xc8e, 0xc9c, + 0xca9, 0xcb6, 0xcc3, 0xcd1, 0xcde, 0xcec, 0xcf9, 0xd07, 0xd14, 0xd22, + 0xd2f, 0xd3d, 0xd4a, 0xd58, 0xd66, 0xd73, 0xd81, 0xd8f, 0xd9d, 0xdab, + 0xdb8, 0xdc6, 0xdd4, 0xde2, 0xdf0, 0xdfe, 0xe0c, 0xe1a, 0xe29, 0xe37, + 0xe45, 0xe53, 0xe61, 0xe70, 0xe7e, 0xe8c, 0xe9a, 0xea9, 0xeb7, 0xec6, + 0xed4, 0xee3, 0xef1, 0xf00, 0xf0e, 0xf1d, 0xf2c, 0xf3a, 0xf49, 0xf58, + 0xf67, 0xf75, 0xf84, 0xf93, 0xfa2, 0xfb1, 0xfc0, 0xfcf, 0xfde, 0xfed, + 0xffc, 0x100b, 0x101a, 0x102a, 0x1039, 0x1048, 0x1057, 0x1067, 0x1076, + 0x1085, 0x1095, 0x10a4, 0x10b4, 0x10c3, 0x10d3, 0x10e2, 0x10f2, 0x1101, + 0x1111, 0x1121, 0x1130, 0x1140, 0x1150, 0x1160, 0x116f, 0x117f, 0x118f, + 0x119f, 0x11af, 0x11bf, 0x11cf, 0x11df, 0x11ef, 0x11ff, 0x120f, 0x121f, + 0x1230, 0x1240, 0x1250, 0x1260, 0x1271, 0x1281, 0x1291, 0x12a2, 0x12b2, + 0x12c3, 0x12d3, 0x12e4, 0x12f4, 0x1305, 0x1316, 0x1326, 0x1337, 0x1348, + 0x1359, 0x1369, 0x137a, 0x138b, 0x139c, 0x13ad, 0x13be, 0x13cf, 0x13e0, + 0x13f1, 0x1402, 0x1413, 0x1424, 0x1435, 0x1446, 0x1458, 0x1469, 0x147a, + 0x148b, 0x149d, 0x14ae, 0x14c0, 0x14d1, 0x14e3, 0x14f4, 0x1506, 0x1517, + 0x1529, 0x153a, 0x154c, 0x155e, 0x156f, 0x1581, 0x1593, 0x15a5, 0x15b7, + 0x15c9, 0x15db, 0x15ec, 0x15fe, 0x1610, 0x1623, 0x1635, 0x1647, 0x1659, + 0x166b, 0x167d, 0x168f, 0x16a2, 0x16b4, 0x16c6, 0x16d9, 0x16eb, 0x16fe, + 0x1710, 0x1722, 0x1735, 0x1748, 0x175a, 0x176d, 0x177f, 0x1792, 0x17a5, + 0x17b8, 0x17ca, 0x17dd, 0x17f0, 0x1803, 0x1816, 0x1829, 0x183c, 0x184f, + 0x1862, 0x1875, 0x1888, 0x189b, 0x18ae, 0x18c1, 0x18d5, 0x18e8, 0x18fb, + 0x190e, 0x1922, 0x1935, 0x1949, 0x195c, 0x196f, 0x1983, 0x1996, 0x19aa, + 0x19be, 0x19d1, 0x19e5, 0x19f9, 0x1a0c, 0x1a20, 0x1a34, 0x1a48, 0x1a5c, + 0x1a70, 0x1a84, 0x1a97, 0x1aab, 0x1ac0, 0x1ad4, 0x1ae8, 0x1afc, 0x1b10, + 0x1b24, 0x1b38, 0x1b4d, 0x1b61, 0x1b75, 0x1b8a, 0x1b9e, 0x1bb2, 0x1bc7, + 0x1bdb, 0x1bf0, 0x1c04, 0x1c19, 0x1c2e, 0x1c42, 0x1c57, 0x1c6c, 0x1c80, + 0x1c95, 0x1caa, 0x1cbf, 0x1cd4, 0x1ce8, 0x1cfd, 0x1d12, 0x1d27, 0x1d3c, + 0x1d51, 0x1d67, 0x1d7c, 0x1d91, 0x1da6, 0x1dbb, 0x1dd1, 0x1de6, 0x1dfb, + 0x1e10, 0x1e26, 0x1e3b, 0x1e51, 0x1e66, 0x1e7c, 0x1e91, 0x1ea7, 0x1ebd, + 0x1ed2, 0x1ee8, 0x1efe, 0x1f13, 0x1f29, 0x1f3f, 0x1f55, 0x1f6b, 0x1f81, + 0x1f96, 0x1fac, 0x1fc2, 0x1fd9, 0x1fef, 0x2005, 0x201b, 0x2031, 0x2047, + 0x205d, 0x2074, 0x208a, 0x20a0, 0x20b7, 0x20cd, 0x20e4, 0x20fa, 0x2111, + 0x2127, 0x213e, 0x2154, 0x216b, 0x2182, 0x2198, 0x21af, 0x21c6, 0x21dd, + 0x21f3, 0x220a, 0x2221, 0x2238, 0x224f, 0x2266, 0x227d, 0x2294, 0x22ab, + 0x22c2, 0x22da, 0x22f1, 0x2308, 0x231f, 0x2337, 0x234e, 0x2365, 0x237d, + 0x2394, 0x23ac, 0x23c3, 0x23db, 0x23f2, 0x240a, 0x2421, 0x2439, 0x2451, + 0x2469, 0x2480, 0x2498, 0x24b0, 0x24c8, 0x24e0, 0x24f8, 0x2510, 0x2528, + 0x2540, 0x2558, 0x2570, 0x2588, 0x25a0, 0x25b8, 0x25d0, 0x25e9, 0x2601, + 0x2619, 0x2632, 0x264a, 0x2663, 0x267b, 0x2693, 0x26ac, 0x26c5, 0x26dd, + 0x26f6, 0x270e, 0x2727, 0x2740, 0x2759, 0x2771, 0x278a, 0x27a3, 0x27bc, + 0x27d5, 0x27ee, 0x2807, 0x2820, 0x2839, 0x2852, 0x286b, 0x2884, 0x289e, + 0x28b7, 0x28d0, 0x28e9, 0x2903, 0x291c, 0x2936, 0x294f, 0x2968, 0x2982, + 0x299c, 0x29b5, 0x29cf, 0x29e8, 0x2a02, 0x2a1c, 0x2a35, 0x2a4f, 0x2a69, + 0x2a83, 0x2a9d, 0x2ab7, 0x2ad1, 0x2aeb, 0x2b05, 0x2b1f, 0x2b39, 0x2b53, + 0x2b6d, 0x2b87, 0x2ba1, 0x2bbc, 0x2bd6, 0x2bf0, 0x2c0b, 0x2c25, 0x2c3f, + 0x2c5a, 0x2c74, 0x2c8f, 0x2ca9, 0x2cc4, 0x2cdf, 0x2cf9, 0x2d14, 0x2d2f, + 0x2d49, 0x2d64, 0x2d7f, 0x2d9a, 0x2db5, 0x2dd0, 0x2deb, 0x2e06, 0x2e21, + 0x2e3c, 0x2e57, 0x2e72, 0x2e8d, 0x2ea8, 0x2ec4, 0x2edf, 0x2efa, 0x2f16, + 0x2f31, 0x2f4c, 0x2f68, 0x2f83, 0x2f9f, 0x2fba, 0x2fd6, 0x2ff1, 0x300d, + 0x3029, 0x3044, 0x3060, 0x307c, 0x3098, 0x30b4, 0x30d0, 0x30eb, 0x3107, + 0x3123, 0x313f, 0x315b, 0x3178, 0x3194, 0x31b0, 0x31cc, 0x31e8, 0x3205, + 0x3221, 0x323d, 0x325a, 0x3276, 0x3292, 0x32af, 0x32cb, 0x32e8, 0x3304, + 0x3321, 0x333e, 0x335a, 0x3377, 0x3394, 0x33b1, 0x33cd, 0x33ea, 0x3407, + 0x3424, 0x3441, 0x345e, 0x347b, 0x3498, 0x34b5, 0x34d2, 0x34ef, 0x350d, + 0x352a, 0x3547, 0x3564, 0x3582, 0x359f, 0x35bc, 0x35da, 0x35f7, 0x3615, + 0x3632, 0x3650, 0x366e, 0x368b, 0x36a9, 0x36c7, 0x36e4, 0x3702, 0x3720, + 0x373e, 0x375c, 0x377a, 0x3798, 0x37b6, 0x37d4, 0x37f2, 0x3810, 0x382e, + 0x384c, 0x386a, 0x3888, 0x38a7, 0x38c5, 0x38e3, 0x3902, 0x3920, 0x393f, + 0x395d, 0x397c, 0x399a, 0x39b9, 0x39d7, 0x39f6, 0x3a15, 0x3a33, 0x3a52, + 0x3a71, 0x3a90, 0x3aaf, 0x3acd, 0x3aec, 0x3b0b, 0x3b2a, 0x3b49, 0x3b68, + 0x3b87, 0x3ba7, 0x3bc6, 0x3be5, 0x3c04, 0x3c24, 0x3c43, 0x3c62, 0x3c82, + 0x3ca1, 0x3cc0, 0x3ce0, 0x3cff, 0x3d1f, 0x3d3f, 0x3d5e, 0x3d7e, 0x3d9e, + 0x3dbd, 0x3ddd, 0x3dfd, 0x3e1d, 0x3e3d, 0x3e5d, 0x3e7c, 0x3e9c, 0x3ebc, + 0x3edc, 0x3efd, 0x3f1d, 0x3f3d, 0x3f5d, 0x3f7d, 0x3f9e, 0x3fbe, 0x3fde, + 0x3fff, 0x0}, + {0x3, 0x0, 0xfa0, 0x13a2, 0x160c, 0x17a3, 0x18d8, 0x1a0c, 0x1ac9, + 0x1ba3, 0x1c4d, 0x1cd8, 0x1d70, 0x1e0c, 0x1e67, 0x1ec9, 0x1f33, 0x1fa3, + 0x200e, 0x204d, 0x2091, 0x20d8, 0x2122, 0x2170, 0x21c2, 0x220c, 0x2238, + 0x2267, 0x2297, 0x22c9, 0x22fd, 0x2333, 0x236a, 0x23a3, 0x23df, 0x240e, + 0x242d, 0x244d, 0x246e, 0x2491, 0x24b4, 0x24d8, 0x24fc, 0x2522, 0x2549, + 0x2570, 0x2599, 0x25c2, 0x25ed, 0x260c, 0x2622, 0x2638, 0x264f, 0x2667, + 0x267f, 0x2697, 0x26b0, 0x26c9, 0x26e3, 0x26fd, 0x2717, 0x2733, 0x274e, + 0x276a, 0x2787, 0x27a3, 0x27c1, 0x27df, 0x27fd, 0x280e, 0x281d, 0x282d, + 0x283d, 0x284d, 0x285e, 0x286e, 0x287f, 0x2891, 0x28a2, 0x28b4, 0x28c5, + 0x28d8, 0x28ea, 0x28fc, 0x290f, 0x2922, 0x2935, 0x2949, 0x295d, 0x2970, + 0x2985, 0x2999, 0x29ae, 0x29c2, 0x29d7, 0x29ed, 0x2a01, 0x2a0c, 0x2a17, + 0x2a22, 0x2a2d, 0x2a38, 0x2a44, 0x2a4f, 0x2a5b, 0x2a67, 0x2a73, 0x2a7f, + 0x2a8b, 0x2a97, 0x2aa3, 0x2ab0, 0x2abc, 0x2ac9, 0x2ad6, 0x2ae3, 0x2af0, + 0x2afd, 0x2b0a, 0x2b17, 0x2b25, 0x2b33, 0x2b40, 0x2b4e, 0x2b5c, 0x2b6a, + 0x2b78, 0x2b87, 0x2b95, 0x2ba3, 0x2bb2, 0x2bc1, 0x2bd0, 0x2bdf, 0x2bee, + 0x2bfd, 0x2c06, 0x2c0e, 0x2c15, 0x2c1d, 0x2c25, 0x2c2d, 0x2c35, 0x2c3d, + 0x2c45, 0x2c4d, 0x2c55, 0x2c5e, 0x2c66, 0x2c6e, 0x2c77, 0x2c7f, 0x2c88, + 0x2c91, 0x2c99, 0x2ca2, 0x2cab, 0x2cb4, 0x2cbd, 0x2cc5, 0x2cce, 0x2cd8, + 0x2ce1, 0x2cea, 0x2cf3, 0x2cfc, 0x2d06, 0x2d0f, 0x2d19, 0x2d22, 0x2d2c, + 0x2d35, 0x2d3f, 0x2d49, 0x2d53, 0x2d5d, 0x2d66, 0x2d70, 0x2d7a, 0x2d85, + 0x2d8f, 0x2d99, 0x2da3, 0x2dae, 0x2db8, 0x2dc2, 0x2dcd, 0x2dd7, 0x2de2, + 0x2ded, 0x2df7, 0x2e01, 0x2e06, 0x2e0c, 0x2e11, 0x2e17, 0x2e1c, 0x2e22, + 0x2e27, 0x2e2d, 0x2e33, 0x2e38, 0x2e3e, 0x2e44, 0x2e49, 0x2e4f, 0x2e55, + 0x2e5b, 0x2e61, 0x2e67, 0x2e6d, 0x2e73, 0x2e79, 0x2e7f, 0x2e85, 0x2e8b, + 0x2e91, 0x2e97, 0x2e9d, 0x2ea3, 0x2eaa, 0x2eb0, 0x2eb6, 0x2ebc, 0x2ec3, + 0x2ec9, 0x2ecf, 0x2ed6, 0x2edc, 0x2ee3, 0x2ee9, 0x2ef0, 0x2ef6, 0x2efd, + 0x2f03, 0x2f0a, 0x2f11, 0x2f17, 0x2f1e, 0x2f25, 0x2f2c, 0x2f33, 0x2f39, + 0x2f40, 0x2f47, 0x2f4e, 0x2f55, 0x2f5c, 0x2f63, 0x2f6a, 0x2f71, 0x2f78, + 0x2f7f, 0x2f87, 0x2f8e, 0x2f95, 0x2f9c, 0x2fa3, 0x2fab, 0x2fb2, 0x2fb9, + 0x2fc1, 0x2fc8, 0x2fd0, 0x2fd7, 0x2fdf, 0x2fe6, 0x2fee, 0x2ff5, 0x2ffd, + 0x3002, 0x3006, 0x300a, 0x300e, 0x3011, 0x3015, 0x3019, 0x301d, 0x3021, + 0x3025, 0x3029, 0x302d, 0x3031, 0x3035, 0x3039, 0x303d, 0x3041, 0x3045, + 0x3049, 0x304d, 0x3051, 0x3055, 0x305a, 0x305e, 0x3062, 0x3066, 0x306a, + 0x306e, 0x3073, 0x3077, 0x307b, 0x307f, 0x3084, 0x3088, 0x308c, 0x3091, + 0x3095, 0x3099, 0x309e, 0x30a2, 0x30a6, 0x30ab, 0x30af, 0x30b4, 0x30b8, + 0x30bd, 0x30c1, 0x30c5, 0x30ca, 0x30ce, 0x30d3, 0x30d8, 0x30dc, 0x30e1, + 0x30e5, 0x30ea, 0x30ee, 0x30f3, 0x30f8, 0x30fc, 0x3101, 0x3106, 0x310a, + 0x310f, 0x3114, 0x3119, 0x311d, 0x3122, 0x3127, 0x312c, 0x3131, 0x3135, + 0x313a, 0x313f, 0x3144, 0x3149, 0x314e, 0x3153, 0x3158, 0x315d, 0x3161, + 0x3166, 0x316b, 0x3170, 0x3175, 0x317a, 0x3180, 0x3185, 0x318a, 0x318f, + 0x3194, 0x3199, 0x319e, 0x31a3, 0x31a8, 0x31ae, 0x31b3, 0x31b8, 0x31bd, + 0x31c2, 0x31c8, 0x31cd, 0x31d2, 0x31d7, 0x31dd, 0x31e2, 0x31e7, 0x31ed, + 0x31f2, 0x31f7, 0x31fd, 0x3201, 0x3204, 0x3206, 0x3209, 0x320c, 0x320e, + 0x3211, 0x3214, 0x3217, 0x3219, 0x321c, 0x321f, 0x3222, 0x3225, 0x3227, + 0x322a, 0x322d, 0x3230, 0x3233, 0x3235, 0x3238, 0x323b, 0x323e, 0x3241, + 0x3244, 0x3247, 0x3249, 0x324c, 0x324f, 0x3252, 0x3255, 0x3258, 0x325b, + 0x325e, 0x3261, 0x3264, 0x3267, 0x326a, 0x326d, 0x3270, 0x3273, 0x3276, + 0x3279, 0x327c, 0x327f, 0x3282, 0x3285, 0x3288, 0x328b, 0x328e, 0x3291, + 0x3294, 0x3297, 0x329a, 0x329d, 0x32a0, 0x32a3, 0x32a6, 0x32aa, 0x32ad, + 0x32b0, 0x32b3, 0x32b6, 0x32b9, 0x32bd, 0x32c0, 0x32c3, 0x32c6, 0x32ca, + 0x32cd, 0x32d0, 0x32d4, 0x32d7, 0x32db, 0x32de, 0x32e1, 0x32e5, 0x32e8, + 0x32ec, 0x32ef, 0x32f3, 0x32f7, 0x32fa, 0x32fe, 0x3302, 0x3305, 0x3309, + 0x330d, 0x3310, 0x3314, 0x3318, 0x331c, 0x3320, 0x3324, 0x3328, 0x332b, + 0x332f, 0x3333, 0x3337, 0x333b, 0x3340, 0x3344, 0x3348, 0x334c, 0x3350, + 0x3354, 0x3358, 0x335d, 0x3361, 0x3365, 0x336a, 0x336e, 0x3372, 0x3377, + 0x337b, 0x3380, 0x3384, 0x3389, 0x338d, 0x3392, 0x3396, 0x339b, 0x33a0, + 0x33a4, 0x33a9, 0x33ae, 0x33b3, 0x33b7, 0x33bc, 0x33c1, 0x33c6, 0x33cb, + 0x33d0, 0x33d5, 0x33da, 0x33df, 0x33e4, 0x33e9, 0x33ef, 0x33f4, 0x33f9, + 0x33fe, 0x3402, 0x3404, 0x3407, 0x340a, 0x340c, 0x340f, 0x3412, 0x3415, + 0x3417, 0x341a, 0x341d, 0x3420, 0x3423, 0x3426, 0x3429, 0x342b, 0x342e, + 0x3431, 0x3434, 0x3437, 0x343a, 0x343d, 0x3440, 0x3444, 0x3447, 0x344a, + 0x344d, 0x3450, 0x3453, 0x3456, 0x345a, 0x345d, 0x3460, 0x3463, 0x3467, + 0x346a, 0x346d, 0x3471, 0x3474, 0x3477, 0x347b, 0x347e, 0x3482, 0x3485, + 0x3489, 0x348c, 0x3490, 0x3493, 0x3497, 0x349b, 0x349e, 0x34a2, 0x34a6, + 0x34a9, 0x34ad, 0x34b1, 0x34b5, 0x34b9, 0x34bc, 0x34c0, 0x34c4, 0x34c8, + 0x34cc, 0x34d0, 0x34d4, 0x34d8, 0x34dc, 0x34e0, 0x34e4, 0x34e8, 0x34ec, + 0x34f1, 0x34f5, 0x34f9, 0x34fd, 0x3502, 0x3506, 0x350a, 0x350f, 0x3513, + 0x3517, 0x351c, 0x3520, 0x3525, 0x3529, 0x352e, 0x3533, 0x3537, 0x353c, + 0x3541, 0x3545, 0x354a, 0x354f, 0x3554, 0x3558, 0x355d, 0x3562, 0x3567, + 0x356c, 0x3571, 0x3576, 0x357b, 0x3580, 0x3585, 0x358a, 0x3590, 0x3595, + 0x359a, 0x359f, 0x35a5, 0x35aa, 0x35b0, 0x35b5, 0x35ba, 0x35c0, 0x35c5, + 0x35cb, 0x35d1, 0x35d6, 0x35dc, 0x35e2, 0x35e7, 0x35ed, 0x35f3, 0x35f9, + 0x35ff, 0x3602, 0x3605, 0x3608, 0x360b, 0x360e, 0x3611, 0x3614, 0x3617, + 0x361a, 0x361e, 0x3621, 0x3624, 0x3627, 0x362a, 0x362e, 0x3631, 0x3634, + 0x3637, 0x363b, 0x363e, 0x3642, 0x3645, 0x3648, 0x364c, 0x364f, 0x3653, + 0x3656, 0x365a, 0x365d, 0x3661, 0x3664, 0x3668, 0x366c, 0x366f, 0x3673, + 0x3677, 0x367a, 0x367e, 0x3682, 0x3686, 0x368a, 0x368d, 0x3691, 0x3695, + 0x3699, 0x369d, 0x36a1, 0x36a5, 0x36a9, 0x36ad, 0x36b1, 0x36b5, 0x36ba, + 0x36be, 0x36c2, 0x36c6, 0x36ca, 0x36cf, 0x36d3, 0x36d7, 0x36dc, 0x36e0, + 0x36e4, 0x36e9, 0x36ed, 0x36f2, 0x36f6, 0x36fb, 0x36ff, 0x3704, 0x3709, + 0x370d, 0x3712, 0x3717, 0x371b, 0x3720, 0x3725, 0x372a, 0x372f, 0x3734, + 0x3739, 0x373e, 0x3743, 0x3748, 0x374d, 0x3752, 0x3757, 0x375c, 0x3761, + 0x3767, 0x376c, 0x3771, 0x3776, 0x377c, 0x3781, 0x3787, 0x378c, 0x3792, + 0x3797, 0x379d, 0x37a2, 0x37a8, 0x37ae, 0x37b3, 0x37b9, 0x37bf, 0x37c5, + 0x37cb, 0x37d1, 0x37d7, 0x37dd, 0x37e3, 0x37e9, 0x37ef, 0x37f5, 0x37fb, + 0x3800, 0x3804, 0x3807, 0x380a, 0x380d, 0x3810, 0x3813, 0x3817, 0x381a, + 0x381d, 0x3821, 0x3824, 0x3827, 0x382b, 0x382e, 0x3831, 0x3835, 0x3838, + 0x383c, 0x383f, 0x3843, 0x3846, 0x384a, 0x384e, 0x3851, 0x3855, 0x3859, + 0x385c, 0x3860, 0x3864, 0x3867, 0x386b, 0x386f, 0x3873, 0x3877, 0x387b, + 0x387f, 0x3883, 0x3887, 0x388a, 0x388f, 0x3893, 0x3897, 0x389b, 0x389f, + 0x38a3, 0x38a7, 0x38ab, 0x38b0, 0x38b4, 0x38b8, 0x38bc, 0x38c1, 0x38c5, + 0x38c9, 0x38ce, 0x38d2, 0x38d7, 0x38db, 0x38e0, 0x38e4, 0x38e9, 0x38ee, + 0x38f2, 0x38f7, 0x38fc, 0x3900, 0x3905, 0x390a, 0x390f, 0x3914, 0x3919, + 0x391d, 0x3922, 0x3927, 0x392c, 0x3931, 0x3937, 0x393c, 0x3941, 0x3946, + 0x394b, 0x3950, 0x3956, 0x395b, 0x3960, 0x3966, 0x396b, 0x3971, 0x3976, + 0x397c, 0x3981, 0x3987, 0x398c, 0x3992, 0x3998, 0x399e, 0x39a3, 0x39a9, + 0x39af, 0x39b5, 0x39bb, 0x39c1, 0x39c7, 0x39cd, 0x39d3, 0x39d9, 0x39df, + 0x39e5, 0x39ec, 0x39f2, 0x39f8, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x3}, + {0x3, 0x0, 0xea8, 0x12aa, 0x1500, 0x16ab, 0x1815, 0x1900, 0x1a0b, + 0x1aab, 0x1b60, 0x1c15, 0x1c85, 0x1d00, 0x1d86, 0x1e0b, 0x1e58, 0x1eab, + 0x1f03, 0x1f60, 0x1fc3, 0x2015, 0x204c, 0x2085, 0x20c2, 0x2100, 0x2142, + 0x2186, 0x21cc, 0x220b, 0x2231, 0x2258, 0x2281, 0x22ab, 0x22d6, 0x2303, + 0x2331, 0x2360, 0x2391, 0x23c3, 0x23f6, 0x2415, 0x2430, 0x244c, 0x2468, + 0x2485, 0x24a3, 0x24c2, 0x24e1, 0x2500, 0x2521, 0x2542, 0x2563, 0x2586, + 0x25a9, 0x25cc, 0x25f1, 0x260b, 0x261e, 0x2631, 0x2644, 0x2658, 0x266c, + 0x2681, 0x2696, 0x26ab, 0x26c0, 0x26d6, 0x26ec, 0x2703, 0x271a, 0x2731, + 0x2748, 0x2760, 0x2779, 0x2791, 0x27aa, 0x27c3, 0x27dd, 0x27f6, 0x2808, + 0x2815, 0x2823, 0x2830, 0x283e, 0x284c, 0x285a, 0x2868, 0x2877, 0x2885, + 0x2894, 0x28a3, 0x28b2, 0x28c2, 0x28d1, 0x28e1, 0x28f0, 0x2900, 0x2910, + 0x2921, 0x2931, 0x2942, 0x2952, 0x2963, 0x2974, 0x2986, 0x2997, 0x29a9, + 0x29bb, 0x29cc, 0x29df, 0x29f1, 0x2a01, 0x2a0b, 0x2a14, 0x2a1e, 0x2a27, + 0x2a31, 0x2a3a, 0x2a44, 0x2a4e, 0x2a58, 0x2a62, 0x2a6c, 0x2a76, 0x2a81, + 0x2a8b, 0x2a96, 0x2aa0, 0x2aab, 0x2ab6, 0x2ac0, 0x2acb, 0x2ad6, 0x2ae1, + 0x2aec, 0x2af8, 0x2b03, 0x2b0e, 0x2b1a, 0x2b25, 0x2b31, 0x2b3d, 0x2b48, + 0x2b54, 0x2b60, 0x2b6c, 0x2b79, 0x2b85, 0x2b91, 0x2b9d, 0x2baa, 0x2bb6, + 0x2bc3, 0x2bd0, 0x2bdd, 0x2bea, 0x2bf6, 0x2c02, 0x2c08, 0x2c0f, 0x2c15, + 0x2c1c, 0x2c23, 0x2c2a, 0x2c30, 0x2c37, 0x2c3e, 0x2c45, 0x2c4c, 0x2c53, + 0x2c5a, 0x2c61, 0x2c68, 0x2c70, 0x2c77, 0x2c7e, 0x2c85, 0x2c8d, 0x2c94, + 0x2c9c, 0x2ca3, 0x2cab, 0x2cb2, 0x2cba, 0x2cc2, 0x2cc9, 0x2cd1, 0x2cd9, + 0x2ce1, 0x2ce8, 0x2cf0, 0x2cf8, 0x2d00, 0x2d08, 0x2d10, 0x2d18, 0x2d21, + 0x2d29, 0x2d31, 0x2d39, 0x2d42, 0x2d4a, 0x2d52, 0x2d5b, 0x2d63, 0x2d6c, + 0x2d74, 0x2d7d, 0x2d86, 0x2d8e, 0x2d97, 0x2da0, 0x2da9, 0x2db2, 0x2dbb, + 0x2dc3, 0x2dcc, 0x2dd5, 0x2ddf, 0x2de8, 0x2df1, 0x2dfa, 0x2e01, 0x2e06, + 0x2e0b, 0x2e0f, 0x2e14, 0x2e19, 0x2e1e, 0x2e22, 0x2e27, 0x2e2c, 0x2e31, + 0x2e36, 0x2e3a, 0x2e3f, 0x2e44, 0x2e49, 0x2e4e, 0x2e53, 0x2e58, 0x2e5d, + 0x2e62, 0x2e67, 0x2e6c, 0x2e71, 0x2e76, 0x2e7c, 0x2e81, 0x2e86, 0x2e8b, + 0x2e90, 0x2e96, 0x2e9b, 0x2ea0, 0x2ea6, 0x2eab, 0x2eb0, 0x2eb6, 0x2ebb, + 0x2ec0, 0x2ec6, 0x2ecb, 0x2ed1, 0x2ed6, 0x2edc, 0x2ee1, 0x2ee7, 0x2eec, + 0x2ef2, 0x2ef8, 0x2efd, 0x2f03, 0x2f09, 0x2f0e, 0x2f14, 0x2f1a, 0x2f20, + 0x2f25, 0x2f2b, 0x2f31, 0x2f37, 0x2f3d, 0x2f43, 0x2f48, 0x2f4e, 0x2f54, + 0x2f5a, 0x2f60, 0x2f66, 0x2f6c, 0x2f72, 0x2f79, 0x2f7f, 0x2f85, 0x2f8b, + 0x2f91, 0x2f97, 0x2f9d, 0x2fa4, 0x2faa, 0x2fb0, 0x2fb6, 0x2fbd, 0x2fc3, + 0x2fc9, 0x2fd0, 0x2fd6, 0x2fdd, 0x2fe3, 0x2fea, 0x2ff0, 0x2ff6, 0x2ffd, + 0x3002, 0x3005, 0x3008, 0x300b, 0x300f, 0x3012, 0x3015, 0x3019, 0x301c, + 0x301f, 0x3023, 0x3026, 0x302a, 0x302d, 0x3030, 0x3034, 0x3037, 0x303b, + 0x303e, 0x3042, 0x3045, 0x3049, 0x304c, 0x3050, 0x3053, 0x3057, 0x305a, + 0x305e, 0x3061, 0x3065, 0x3068, 0x306c, 0x3070, 0x3073, 0x3077, 0x307b, + 0x307e, 0x3082, 0x3085, 0x3089, 0x308d, 0x3091, 0x3094, 0x3098, 0x309c, + 0x309f, 0x30a3, 0x30a7, 0x30ab, 0x30ae, 0x30b2, 0x30b6, 0x30ba, 0x30be, + 0x30c2, 0x30c5, 0x30c9, 0x30cd, 0x30d1, 0x30d5, 0x30d9, 0x30dd, 0x30e1, + 0x30e4, 0x30e8, 0x30ec, 0x30f0, 0x30f4, 0x30f8, 0x30fc, 0x3100, 0x3104, + 0x3108, 0x310c, 0x3110, 0x3114, 0x3118, 0x311d, 0x3121, 0x3125, 0x3129, + 0x312d, 0x3131, 0x3135, 0x3139, 0x313d, 0x3142, 0x3146, 0x314a, 0x314e, + 0x3152, 0x3157, 0x315b, 0x315f, 0x3163, 0x3168, 0x316c, 0x3170, 0x3174, + 0x3179, 0x317d, 0x3181, 0x3186, 0x318a, 0x318e, 0x3193, 0x3197, 0x319c, + 0x31a0, 0x31a4, 0x31a9, 0x31ad, 0x31b2, 0x31b6, 0x31bb, 0x31bf, 0x31c3, + 0x31c8, 0x31cc, 0x31d1, 0x31d5, 0x31da, 0x31df, 0x31e3, 0x31e8, 0x31ec, + 0x31f1, 0x31f5, 0x31fa, 0x31ff, 0x3201, 0x3204, 0x3206, 0x3208, 0x320b, + 0x320d, 0x320f, 0x3212, 0x3214, 0x3216, 0x3219, 0x321b, 0x321e, 0x3220, + 0x3222, 0x3225, 0x3227, 0x3229, 0x322c, 0x322e, 0x3231, 0x3233, 0x3236, + 0x3238, 0x323a, 0x323d, 0x323f, 0x3242, 0x3244, 0x3247, 0x3249, 0x324c, + 0x324e, 0x3251, 0x3253, 0x3256, 0x3258, 0x325b, 0x325d, 0x3260, 0x3262, + 0x3265, 0x3267, 0x326a, 0x326c, 0x326f, 0x3271, 0x3274, 0x3276, 0x3279, + 0x327c, 0x327e, 0x3281, 0x3283, 0x3286, 0x3289, 0x328b, 0x328e, 0x3290, + 0x3293, 0x3296, 0x3298, 0x329b, 0x329e, 0x32a0, 0x32a3, 0x32a6, 0x32a8, + 0x32ab, 0x32ae, 0x32b0, 0x32b3, 0x32b6, 0x32b8, 0x32bb, 0x32be, 0x32c1, + 0x32c4, 0x32c6, 0x32c9, 0x32cc, 0x32cf, 0x32d2, 0x32d5, 0x32d8, 0x32da, + 0x32dd, 0x32e0, 0x32e3, 0x32e6, 0x32e9, 0x32ec, 0x32ef, 0x32f2, 0x32f6, + 0x32f9, 0x32fc, 0x32ff, 0x3302, 0x3305, 0x3308, 0x330c, 0x330f, 0x3312, + 0x3315, 0x3318, 0x331c, 0x331f, 0x3322, 0x3326, 0x3329, 0x332c, 0x3330, + 0x3333, 0x3337, 0x333a, 0x333e, 0x3341, 0x3345, 0x3348, 0x334c, 0x334f, + 0x3353, 0x3356, 0x335a, 0x335e, 0x3361, 0x3365, 0x3369, 0x336c, 0x3370, + 0x3374, 0x3378, 0x337c, 0x337f, 0x3383, 0x3387, 0x338b, 0x338f, 0x3393, + 0x3397, 0x339b, 0x339f, 0x33a3, 0x33a7, 0x33ab, 0x33af, 0x33b3, 0x33b7, + 0x33bb, 0x33bf, 0x33c4, 0x33c8, 0x33cc, 0x33d0, 0x33d5, 0x33d9, 0x33dd, + 0x33e2, 0x33e6, 0x33eb, 0x33ef, 0x33f3, 0x33f8, 0x33fc, 0x3400, 0x3402, + 0x3405, 0x3407, 0x3409, 0x340c, 0x340e, 0x3410, 0x3413, 0x3415, 0x3418, + 0x341a, 0x341c, 0x341f, 0x3421, 0x3424, 0x3426, 0x3429, 0x342b, 0x342e, + 0x3430, 0x3433, 0x3435, 0x3438, 0x343a, 0x343d, 0x3440, 0x3442, 0x3445, + 0x3448, 0x344a, 0x344d, 0x3450, 0x3452, 0x3455, 0x3458, 0x345b, 0x345d, + 0x3460, 0x3463, 0x3466, 0x3469, 0x346b, 0x346e, 0x3471, 0x3474, 0x3477, + 0x347a, 0x347d, 0x3480, 0x3483, 0x3486, 0x3489, 0x348c, 0x348f, 0x3492, + 0x3495, 0x3498, 0x349b, 0x349e, 0x34a2, 0x34a5, 0x34a8, 0x34ab, 0x34ae, + 0x34b2, 0x34b5, 0x34b8, 0x34bb, 0x34bf, 0x34c2, 0x34c5, 0x34c9, 0x34cc, + 0x34cf, 0x34d3, 0x34d6, 0x34da, 0x34dd, 0x34e1, 0x34e4, 0x34e8, 0x34eb, + 0x34ef, 0x34f2, 0x34f6, 0x34fa, 0x34fd, 0x3501, 0x3505, 0x3508, 0x350c, + 0x3510, 0x3514, 0x3517, 0x351b, 0x351f, 0x3523, 0x3527, 0x352b, 0x352f, + 0x3532, 0x3536, 0x353a, 0x353e, 0x3542, 0x3546, 0x354b, 0x354f, 0x3553, + 0x3557, 0x355b, 0x355f, 0x3563, 0x3568, 0x356c, 0x3570, 0x3574, 0x3579, + 0x357d, 0x3581, 0x3586, 0x358a, 0x358f, 0x3593, 0x3598, 0x359c, 0x35a1, + 0x35a5, 0x35aa, 0x35ae, 0x35b3, 0x35b8, 0x35bc, 0x35c1, 0x35c6, 0x35cb, + 0x35cf, 0x35d4, 0x35d9, 0x35de, 0x35e3, 0x35e8, 0x35ed, 0x35f2, 0x35f7, + 0x35fc, 0x3600, 0x3603, 0x3605, 0x3608, 0x360a, 0x360d, 0x3610, 0x3612, + 0x3615, 0x3618, 0x361a, 0x361d, 0x3620, 0x3622, 0x3625, 0x3628, 0x362b, + 0x362d, 0x3630, 0x3633, 0x3636, 0x3639, 0x363b, 0x363e, 0x3641, 0x3644, + 0x3647, 0x364a, 0x364d, 0x3650, 0x3653, 0x3656, 0x3659, 0x365c, 0x365f, + 0x3662, 0x3665, 0x3668, 0x366b, 0x366e, 0x3672, 0x3675, 0x3678, 0x367b, + 0x367e, 0x3682, 0x3685, 0x3688, 0x368b, 0x368f, 0x3692, 0x3695, 0x3699, + 0x369c, 0x36a0, 0x36a3, 0x36a6, 0x36aa, 0x36ad, 0x36b1, 0x36b4, 0x36b8, + 0x36bb, 0x36bf, 0x36c3, 0x36c6, 0x36ca, 0x36cd, 0x36d1, 0x36d5, 0x36d9, + 0x36dc, 0x36e0, 0x36e4, 0x36e8, 0x36eb, 0x36ef, 0x36f3, 0x36f7, 0x36fb, + 0x36ff, 0x3703, 0x3707, 0x370b, 0x370f, 0x3713, 0x3717, 0x371b, 0x371f, + 0x3723, 0x3727, 0x372b, 0x372f, 0x3734, 0x3738, 0x373c, 0x3740, 0x3745, + 0x3749, 0x374d, 0x3752, 0x3756, 0x375b, 0x375f, 0x3764, 0x3768, 0x376d, + 0x3771, 0x3776, 0x377a, 0x377f, 0x3783, 0x3788, 0x378d, 0x3792, 0x3796, + 0x379b, 0x37a0, 0x37a5, 0x37aa, 0x37ae, 0x37b3, 0x37b8, 0x37bd, 0x37c2, + 0x37c7, 0x37cc, 0x37d1, 0x37d6, 0x37dc, 0x37e1, 0x37e6, 0x37eb, 0x37f0, + 0x37f6, 0x37fb, 0x3800, 0x3802, 0x3805, 0x3808, 0x380b, 0x380d, 0x3810, + 0x3813, 0x3816, 0x3818, 0x381b, 0x381e, 0x3821, 0x3824, 0x3827, 0x382a, + 0x382c, 0x382f, 0x3832, 0x3835, 0x3838, 0x383b, 0x383e, 0x3841, 0x3844, + 0x3847, 0x384a, 0x384d, 0x3851, 0x3854, 0x3857, 0x385a, 0x385d, 0x3860, + 0x3864, 0x3867, 0x386a, 0x386d, 0x3870, 0x3874, 0x3877, 0x387a, 0x387e, + 0x3881, 0x3885, 0x3888, 0x388b, 0x388f, 0x3892, 0x3896, 0x3899, 0x389d, + 0x38a0, 0x38a4, 0x38a7, 0x38ab, 0x38af, 0x38b2, 0x38b6, 0x38ba, 0x38bd, + 0x38c1, 0x38c5, 0x38c8, 0x38cc, 0x38d0, 0x38d4, 0x38d8, 0x38dc, 0x38df, + 0x38e3, 0x38e7, 0x38eb, 0x38ef, 0x38f3, 0x38f7, 0x38fb, 0x38ff, 0x3903, + 0x3907, 0x390c, 0x3910, 0x3914, 0x3918, 0x391c, 0x3920, 0x3925, 0x3929, + 0x392d, 0x3932, 0x3936, 0x393a, 0x393f, 0x3943, 0x3948, 0x394c, 0x3951, + 0x3955, 0x395a, 0x395e, 0x3963, 0x3967, 0x396c, 0x3971, 0x3975, 0x397a, + 0x397f, 0x3984, 0x3989, 0x398d, 0x3992, 0x3997, 0x399c, 0x39a1, 0x39a6, + 0x39ab, 0x39b0, 0x39b5, 0x39ba, 0x39bf, 0x39c4, 0x39c9, 0x39cf, 0x39d4, + 0x39d9, 0x39de, 0x39e4, 0x39e9, 0x39ee, 0x39f4, 0x39f9, 0x39ff, 0x3}, + {0x3, 0x0, 0x1, 0x2, 0x4, 0x5, 0x7, 0x8, 0xa, 0xb, 0xd, 0xe, 0xf, 0x11, + 0x12, 0x14, 0x15, 0x17, 0x18, 0x1a, 0x1b, 0x1c, 0x1e, 0x1f, 0x21, 0x22, + 0x24, 0x25, 0x27, 0x28, 0x29, 0x2b, 0x2c, 0x2e, 0x2f, 0x31, 0x32, 0x34, + 0x35, 0x37, 0x38, 0x3a, 0x3b, 0x3d, 0x3f, 0x40, 0x42, 0x44, 0x45, 0x47, + 0x49, 0x4b, 0x4d, 0x4f, 0x51, 0x52, 0x54, 0x56, 0x58, 0x5b, 0x5d, 0x5f, + 0x61, 0x63, 0x65, 0x67, 0x6a, 0x6c, 0x6e, 0x70, 0x73, 0x75, 0x78, 0x7a, + 0x7c, 0x7f, 0x81, 0x84, 0x87, 0x89, 0x8c, 0x8f, 0x91, 0x94, 0x97, 0x99, + 0x9c, 0x9f, 0xa2, 0xa5, 0xa8, 0xab, 0xae, 0xb1, 0xb4, 0xb7, 0xba, 0xbd, + 0xc0, 0xc4, 0xc7, 0xca, 0xcd, 0xd1, 0xd4, 0xd7, 0xdb, 0xde, 0xe2, 0xe5, + 0xe9, 0xec, 0xf0, 0xf4, 0xf7, 0xfb, 0xff, 0x103, 0x106, 0x10a, 0x10e, + 0x112, 0x116, 0x11a, 0x11e, 0x122, 0x126, 0x12a, 0x12e, 0x132, 0x137, + 0x13b, 0x13f, 0x143, 0x148, 0x14c, 0x150, 0x155, 0x159, 0x15e, 0x162, + 0x167, 0x16b, 0x170, 0x175, 0x179, 0x17e, 0x183, 0x188, 0x18d, 0x192, + 0x196, 0x19b, 0x1a0, 0x1a5, 0x1aa, 0x1b0, 0x1b5, 0x1ba, 0x1bf, 0x1c4, + 0x1c9, 0x1cf, 0x1d4, 0x1d9, 0x1df, 0x1e4, 0x1ea, 0x1ef, 0x1f5, 0x1fa, + 0x200, 0x206, 0x20b, 0x211, 0x217, 0x21d, 0x223, 0x228, 0x22e, 0x234, + 0x23a, 0x240, 0x246, 0x24c, 0x253, 0x259, 0x25f, 0x265, 0x26b, 0x272, + 0x278, 0x27f, 0x285, 0x28b, 0x292, 0x298, 0x29f, 0x2a6, 0x2ac, 0x2b3, + 0x2ba, 0x2c1, 0x2c7, 0x2ce, 0x2d5, 0x2dc, 0x2e3, 0x2ea, 0x2f1, 0x2f8, + 0x2ff, 0x306, 0x30e, 0x315, 0x31c, 0x323, 0x32b, 0x332, 0x33a, 0x341, + 0x349, 0x350, 0x358, 0x35f, 0x367, 0x36f, 0x376, 0x37e, 0x386, 0x38e, + 0x396, 0x39e, 0x3a6, 0x3ae, 0x3b6, 0x3be, 0x3c6, 0x3ce, 0x3d6, 0x3df, + 0x3e7, 0x3ef, 0x3f8, 0x400, 0x409, 0x411, 0x41a, 0x422, 0x42b, 0x434, + 0x43c, 0x445, 0x44e, 0x457, 0x45f, 0x468, 0x471, 0x47a, 0x483, 0x48c, + 0x496, 0x49f, 0x4a8, 0x4b1, 0x4bb, 0x4c4, 0x4cd, 0x4d7, 0x4e0, 0x4ea, + 0x4f3, 0x4fd, 0x506, 0x510, 0x51a, 0x523, 0x52d, 0x537, 0x541, 0x54b, + 0x555, 0x55f, 0x569, 0x573, 0x57d, 0x587, 0x592, 0x59c, 0x5a6, 0x5b0, + 0x5bb, 0x5c5, 0x5d0, 0x5da, 0x5e5, 0x5ef, 0x5fa, 0x605, 0x610, 0x61a, + 0x625, 0x630, 0x63b, 0x646, 0x651, 0x65c, 0x667, 0x672, 0x67d, 0x689, + 0x694, 0x69f, 0x6aa, 0x6b6, 0x6c1, 0x6cd, 0x6d8, 0x6e4, 0x6f0, 0x6fb, + 0x707, 0x713, 0x71e, 0x72a, 0x736, 0x742, 0x74e, 0x75a, 0x766, 0x772, + 0x77e, 0x78b, 0x797, 0x7a3, 0x7af, 0x7bc, 0x7c8, 0x7d5, 0x7e1, 0x7ee, + 0x7fa, 0x807, 0x814, 0x821, 0x82d, 0x83a, 0x847, 0x854, 0x861, 0x86e, + 0x87b, 0x888, 0x895, 0x8a3, 0x8b0, 0x8bd, 0x8ca, 0x8d8, 0x8e5, 0x8f3, + 0x900, 0x90e, 0x91b, 0x929, 0x937, 0x945, 0x952, 0x960, 0x96e, 0x97c, + 0x98a, 0x998, 0x9a6, 0x9b4, 0x9c3, 0x9d1, 0x9df, 0x9ed, 0x9fc, 0xa0a, + 0xa19, 0xa27, 0xa36, 0xa44, 0xa53, 0xa62, 0xa70, 0xa7f, 0xa8e, 0xa9d, + 0xaac, 0xabb, 0xaca, 0xad9, 0xae8, 0xaf7, 0xb07, 0xb16, 0xb25, 0xb35, + 0xb44, 0xb53, 0xb63, 0xb73, 0xb82, 0xb92, 0xba2, 0xbb1, 0xbc1, 0xbd1, + 0xbe1, 0xbf1, 0xc01, 0xc11, 0xc21, 0xc31, 0xc41, 0xc52, 0xc62, 0xc72, + 0xc83, 0xc93, 0xca4, 0xcb4, 0xcc5, 0xcd5, 0xce6, 0xcf7, 0xd08, 0xd18, + 0xd29, 0xd3a, 0xd4b, 0xd5c, 0xd6d, 0xd7e, 0xd90, 0xda1, 0xdb2, 0xdc3, + 0xdd5, 0xde6, 0xdf8, 0xe09, 0xe1b, 0xe2d, 0xe3e, 0xe50, 0xe62, 0xe74, + 0xe85, 0xe97, 0xea9, 0xebb, 0xece, 0xee0, 0xef2, 0xf04, 0xf16, 0xf29, + 0xf3b, 0xf4e, 0xf60, 0xf73, 0xf85, 0xf98, 0xfab, 0xfbd, 0xfd0, 0xfe3, + 0xff6, 0x1009, 0x101c, 0x102f, 0x1042, 0x1055, 0x1068, 0x107c, 0x108f, + 0x10a2, 0x10b6, 0x10c9, 0x10dd, 0x10f0, 0x1104, 0x1117, 0x112b, 0x113f, + 0x1153, 0x1167, 0x117b, 0x118f, 0x11a3, 0x11b7, 0x11cb, 0x11df, 0x11f3, + 0x1208, 0x121c, 0x1230, 0x1245, 0x1259, 0x126e, 0x1282, 0x1297, 0x12ac, + 0x12c1, 0x12d5, 0x12ea, 0x12ff, 0x1314, 0x1329, 0x133e, 0x1353, 0x1369, + 0x137e, 0x1393, 0x13a8, 0x13be, 0x13d3, 0x13e9, 0x13fe, 0x1414, 0x142a, + 0x143f, 0x1455, 0x146b, 0x1481, 0x1497, 0x14ad, 0x14c3, 0x14d9, 0x14ef, + 0x1505, 0x151b, 0x1532, 0x1548, 0x155f, 0x1575, 0x158c, 0x15a2, 0x15b9, + 0x15cf, 0x15e6, 0x15fd, 0x1614, 0x162b, 0x1642, 0x1659, 0x1670, 0x1687, + 0x169e, 0x16b5, 0x16cc, 0x16e4, 0x16fb, 0x1713, 0x172a, 0x1742, 0x1759, + 0x1771, 0x1789, 0x17a0, 0x17b8, 0x17d0, 0x17e8, 0x1800, 0x1818, 0x1830, + 0x1848, 0x1860, 0x1879, 0x1891, 0x18a9, 0x18c2, 0x18da, 0x18f3, 0x190b, + 0x1924, 0x193d, 0x1956, 0x196e, 0x1987, 0x19a0, 0x19b9, 0x19d2, 0x19eb, + 0x1a04, 0x1a1e, 0x1a37, 0x1a50, 0x1a69, 0x1a83, 0x1a9c, 0x1ab6, 0x1acf, + 0x1ae9, 0x1b03, 0x1b1d, 0x1b36, 0x1b50, 0x1b6a, 0x1b84, 0x1b9e, 0x1bb8, + 0x1bd2, 0x1bed, 0x1c07, 0x1c21, 0x1c3c, 0x1c56, 0x1c70, 0x1c8b, 0x1ca6, + 0x1cc0, 0x1cdb, 0x1cf6, 0x1d11, 0x1d2b, 0x1d46, 0x1d61, 0x1d7c, 0x1d97, + 0x1db3, 0x1dce, 0x1de9, 0x1e04, 0x1e20, 0x1e3b, 0x1e57, 0x1e72, 0x1e8e, + 0x1eaa, 0x1ec5, 0x1ee1, 0x1efd, 0x1f19, 0x1f35, 0x1f51, 0x1f6d, 0x1f89, + 0x1fa5, 0x1fc1, 0x1fde, 0x1ffa, 0x2017, 0x2033, 0x2050, 0x206c, 0x2089, + 0x20a5, 0x20c2, 0x20df, 0x20fc, 0x2119, 0x2136, 0x2153, 0x2170, 0x218d, + 0x21aa, 0x21c8, 0x21e5, 0x2202, 0x2220, 0x223d, 0x225b, 0x2279, 0x2296, + 0x22b4, 0x22d2, 0x22f0, 0x230e, 0x232c, 0x234a, 0x2368, 0x2386, 0x23a4, + 0x23c2, 0x23e1, 0x23ff, 0x241d, 0x243c, 0x245a, 0x2479, 0x2498, 0x24b7, + 0x24d5, 0x24f4, 0x2513, 0x2532, 0x2551, 0x2570, 0x258f, 0x25af, 0x25ce, + 0x25ed, 0x260d, 0x262c, 0x264b, 0x266b, 0x268b, 0x26aa, 0x26ca, 0x26ea, + 0x270a, 0x272a, 0x274a, 0x276a, 0x278a, 0x27aa, 0x27ca, 0x27ea, 0x280b, + 0x282b, 0x284c, 0x286c, 0x288d, 0x28ad, 0x28ce, 0x28ef, 0x2910, 0x2930, + 0x2951, 0x2972, 0x2993, 0x29b5, 0x29d6, 0x29f7, 0x2a18, 0x2a3a, 0x2a5b, + 0x2a7c, 0x2a9e, 0x2ac0, 0x2ae1, 0x2b03, 0x2b25, 0x2b47, 0x2b68, 0x2b8a, + 0x2bac, 0x2bcf, 0x2bf1, 0x2c13, 0x2c35, 0x2c57, 0x2c7a, 0x2c9c, 0x2cbf, + 0x2ce1, 0x2d04, 0x2d27, 0x2d49, 0x2d6c, 0x2d8f, 0x2db2, 0x2dd5, 0x2df8, + 0x2e1b, 0x2e3e, 0x2e62, 0x2e85, 0x2ea8, 0x2ecc, 0x2eef, 0x2f13, 0x2f36, + 0x2f5a, 0x2f7e, 0x2fa1, 0x2fc5, 0x2fe9, 0x300d, 0x3031, 0x3055, 0x3079, + 0x309e, 0x30c2, 0x30e6, 0x310b, 0x312f, 0x3154, 0x3178, 0x319d, 0x31c1, + 0x31e6, 0x320b, 0x3230, 0x3255, 0x327a, 0x329f, 0x32c4, 0x32e9, 0x330f, + 0x3334, 0x3359, 0x337f, 0x33a4, 0x33ca, 0x33ef, 0x3415, 0x343b, 0x3461, + 0x3487, 0x34ad, 0x34d3, 0x34f9, 0x351f, 0x3545, 0x356b, 0x3592, 0x35b8, + 0x35de, 0x3605, 0x362b, 0x3652, 0x3679, 0x36a0, 0x36c6, 0x36ed, 0x3714, + 0x373b, 0x3762, 0x3789, 0x37b1, 0x37d8, 0x37ff, 0x3827, 0x384e, 0x3876, + 0x389d, 0x38c5, 0x38ed, 0x3914, 0x393c, 0x3964, 0x398c, 0x39b4, 0x39dc, + 0x3a04, 0x3a2c, 0x3a55, 0x3a7d, 0x3aa5, 0x3ace, 0x3af6, 0x3b1f, 0x3b48, + 0x3b70, 0x3b99, 0x3bc2, 0x3beb, 0x3c14, 0x3c3d, 0x3c66, 0x3c8f, 0x3cb8, + 0x3ce2, 0x3d0b, 0x3d35, 0x3d5e, 0x3d88, 0x3db1, 0x3ddb, 0x3e05, 0x3e2e, + 0x3e58, 0x3e82, 0x3eac, 0x3ed6, 0x3f00, 0x3f2b, 0x3f55, 0x3f7f, 0x3faa, + 0x3fd4, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0}, + {0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x6, 0x7, 0x8, 0x9, 0xb, 0xc, 0xd, 0xe, + 0x10, 0x11, 0x12, 0x13, 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x20, 0x21, 0x22, 0x23, 0x25, 0x26, 0x27, 0x28, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2f, 0x30, 0x31, 0x32, 0x34, 0x35, 0x36, 0x37, 0x39, 0x3a, 0x3c, + 0x3d, 0x3e, 0x40, 0x41, 0x43, 0x44, 0x46, 0x47, 0x49, 0x4a, 0x4c, 0x4d, + 0x4f, 0x51, 0x52, 0x54, 0x56, 0x57, 0x59, 0x5b, 0x5d, 0x5f, 0x60, 0x62, + 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, + 0x7c, 0x7e, 0x80, 0x82, 0x85, 0x87, 0x89, 0x8b, 0x8e, 0x90, 0x92, 0x94, + 0x97, 0x99, 0x9c, 0x9e, 0xa0, 0xa3, 0xa5, 0xa8, 0xaa, 0xad, 0xb0, 0xb2, + 0xb5, 0xb7, 0xba, 0xbd, 0xc0, 0xc2, 0xc5, 0xc8, 0xcb, 0xcd, 0xd0, 0xd3, + 0xd6, 0xd9, 0xdc, 0xdf, 0xe2, 0xe5, 0xe8, 0xeb, 0xee, 0xf1, 0xf4, 0xf7, + 0xfb, 0xfe, 0x101, 0x104, 0x108, 0x10b, 0x10e, 0x111, 0x115, 0x118, + 0x11c, 0x11f, 0x123, 0x126, 0x12a, 0x12d, 0x131, 0x134, 0x138, 0x13b, + 0x13f, 0x143, 0x146, 0x14a, 0x14e, 0x152, 0x155, 0x159, 0x15d, 0x161, + 0x165, 0x169, 0x16d, 0x171, 0x175, 0x179, 0x17d, 0x181, 0x185, 0x189, + 0x18d, 0x192, 0x196, 0x19a, 0x19e, 0x1a2, 0x1a7, 0x1ab, 0x1af, 0x1b4, + 0x1b8, 0x1bd, 0x1c1, 0x1c6, 0x1ca, 0x1cf, 0x1d3, 0x1d8, 0x1dc, 0x1e1, + 0x1e6, 0x1ea, 0x1ef, 0x1f4, 0x1f9, 0x1fe, 0x202, 0x207, 0x20c, 0x211, + 0x216, 0x21b, 0x220, 0x225, 0x22a, 0x22f, 0x234, 0x239, 0x23e, 0x244, + 0x249, 0x24e, 0x253, 0x258, 0x25e, 0x263, 0x269, 0x26e, 0x273, 0x279, + 0x27e, 0x284, 0x289, 0x28f, 0x294, 0x29a, 0x2a0, 0x2a5, 0x2ab, 0x2b1, + 0x2b7, 0x2bc, 0x2c2, 0x2c8, 0x2ce, 0x2d4, 0x2da, 0x2e0, 0x2e6, 0x2ec, + 0x2f2, 0x2f8, 0x2fe, 0x304, 0x30a, 0x310, 0x316, 0x31d, 0x323, 0x329, + 0x32f, 0x336, 0x33c, 0x343, 0x349, 0x350, 0x356, 0x35d, 0x363, 0x36a, + 0x370, 0x377, 0x37e, 0x384, 0x38b, 0x392, 0x398, 0x39f, 0x3a6, 0x3ad, + 0x3b4, 0x3bb, 0x3c2, 0x3c9, 0x3d0, 0x3d7, 0x3de, 0x3e5, 0x3ec, 0x3f3, + 0x3fb, 0x402, 0x409, 0x410, 0x418, 0x41f, 0x426, 0x42e, 0x435, 0x43d, + 0x444, 0x44c, 0x453, 0x45b, 0x462, 0x46a, 0x472, 0x479, 0x481, 0x489, + 0x491, 0x499, 0x4a0, 0x4a8, 0x4b0, 0x4b8, 0x4c0, 0x4c8, 0x4d0, 0x4d8, + 0x4e0, 0x4e8, 0x4f1, 0x4f9, 0x501, 0x509, 0x512, 0x51a, 0x522, 0x52b, + 0x533, 0x53b, 0x544, 0x54c, 0x555, 0x55e, 0x566, 0x56f, 0x577, 0x580, + 0x589, 0x592, 0x59a, 0x5a3, 0x5ac, 0x5b5, 0x5be, 0x5c7, 0x5d0, 0x5d9, + 0x5e2, 0x5eb, 0x5f4, 0x5fd, 0x606, 0x60f, 0x619, 0x622, 0x62b, 0x635, + 0x63e, 0x647, 0x651, 0x65a, 0x664, 0x66d, 0x677, 0x680, 0x68a, 0x694, + 0x69d, 0x6a7, 0x6b1, 0x6bb, 0x6c4, 0x6ce, 0x6d8, 0x6e2, 0x6ec, 0x6f6, + 0x700, 0x70a, 0x714, 0x71e, 0x728, 0x732, 0x73d, 0x747, 0x751, 0x75b, + 0x766, 0x770, 0x77a, 0x785, 0x78f, 0x79a, 0x7a4, 0x7af, 0x7ba, 0x7c4, + 0x7cf, 0x7da, 0x7e4, 0x7ef, 0x7fa, 0x805, 0x810, 0x81a, 0x825, 0x830, + 0x83b, 0x846, 0x851, 0x85d, 0x868, 0x873, 0x87e, 0x889, 0x895, 0x8a0, + 0x8ab, 0x8b7, 0x8c2, 0x8cd, 0x8d9, 0x8e4, 0x8f0, 0x8fb, 0x907, 0x913, + 0x91e, 0x92a, 0x936, 0x942, 0x94d, 0x959, 0x965, 0x971, 0x97d, 0x989, + 0x995, 0x9a1, 0x9ad, 0x9b9, 0x9c5, 0x9d2, 0x9de, 0x9ea, 0x9f6, 0xa03, + 0xa0f, 0xa1c, 0xa28, 0xa34, 0xa41, 0xa4d, 0xa5a, 0xa67, 0xa73, 0xa80, + 0xa8d, 0xa99, 0xaa6, 0xab3, 0xac0, 0xacd, 0xada, 0xae7, 0xaf4, 0xb01, + 0xb0e, 0xb1b, 0xb28, 0xb35, 0xb42, 0xb50, 0xb5d, 0xb6a, 0xb77, 0xb85, + 0xb92, 0xba0, 0xbad, 0xbbb, 0xbc8, 0xbd6, 0xbe4, 0xbf1, 0xbff, 0xc0d, + 0xc1a, 0xc28, 0xc36, 0xc44, 0xc52, 0xc60, 0xc6e, 0xc7c, 0xc8a, 0xc98, + 0xca6, 0xcb4, 0xcc3, 0xcd1, 0xcdf, 0xced, 0xcfc, 0xd0a, 0xd19, 0xd27, + 0xd35, 0xd44, 0xd53, 0xd61, 0xd70, 0xd7e, 0xd8d, 0xd9c, 0xdab, 0xdba, + 0xdc8, 0xdd7, 0xde6, 0xdf5, 0xe04, 0xe13, 0xe22, 0xe31, 0xe41, 0xe50, + 0xe5f, 0xe6e, 0xe7e, 0xe8d, 0xe9c, 0xeac, 0xebb, 0xecb, 0xeda, 0xeea, + 0xef9, 0xf09, 0xf19, 0xf28, 0xf38, 0xf48, 0xf58, 0xf68, 0xf77, 0xf87, + 0xf97, 0xfa7, 0xfb7, 0xfc7, 0xfd8, 0xfe8, 0xff8, 0x1008, 0x1018, + 0x1029, 0x1039, 0x1049, 0x105a, 0x106a, 0x107b, 0x108b, 0x109c, 0x10ad, + 0x10bd, 0x10ce, 0x10df, 0x10ef, 0x1100, 0x1111, 0x1122, 0x1133, 0x1144, + 0x1155, 0x1166, 0x1177, 0x1188, 0x1199, 0x11aa, 0x11bb, 0x11cd, 0x11de, + 0x11ef, 0x1201, 0x1212, 0x1223, 0x1235, 0x1246, 0x1258, 0x126a, 0x127b, + 0x128d, 0x129f, 0x12b0, 0x12c2, 0x12d4, 0x12e6, 0x12f8, 0x130a, 0x131c, + 0x132e, 0x1340, 0x1352, 0x1364, 0x1376, 0x1388, 0x139b, 0x13ad, 0x13bf, + 0x13d2, 0x13e4, 0x13f6, 0x1409, 0x141c, 0x142e, 0x1441, 0x1453, 0x1466, + 0x1479, 0x148b, 0x149e, 0x14b1, 0x14c4, 0x14d7, 0x14ea, 0x14fd, 0x1510, + 0x1523, 0x1536, 0x1549, 0x155c, 0x1570, 0x1583, 0x1596, 0x15aa, 0x15bd, + 0x15d0, 0x15e4, 0x15f7, 0x160b, 0x161e, 0x1632, 0x1646, 0x1659, 0x166d, + 0x1681, 0x1695, 0x16a9, 0x16bd, 0x16d1, 0x16e4, 0x16f9, 0x170d, 0x1721, + 0x1735, 0x1749, 0x175d, 0x1771, 0x1786, 0x179a, 0x17af, 0x17c3, 0x17d7, + 0x17ec, 0x1800, 0x1815, 0x182a, 0x183e, 0x1853, 0x1868, 0x187d, 0x1891, + 0x18a6, 0x18bb, 0x18d0, 0x18e5, 0x18fa, 0x190f, 0x1924, 0x1939, 0x194f, + 0x1964, 0x1979, 0x198e, 0x19a4, 0x19b9, 0x19cf, 0x19e4, 0x19fa, 0x1a0f, + 0x1a25, 0x1a3a, 0x1a50, 0x1a66, 0x1a7b, 0x1a91, 0x1aa7, 0x1abd, 0x1ad3, + 0x1ae9, 0x1aff, 0x1b15, 0x1b2b, 0x1b41, 0x1b57, 0x1b6d, 0x1b84, 0x1b9a, + 0x1bb0, 0x1bc7, 0x1bdd, 0x1bf4, 0x1c0a, 0x1c21, 0x1c37, 0x1c4e, 0x1c64, + 0x1c7b, 0x1c92, 0x1ca9, 0x1cbf, 0x1cd6, 0x1ced, 0x1d04, 0x1d1b, 0x1d32, + 0x1d49, 0x1d60, 0x1d78, 0x1d8f, 0x1da6, 0x1dbd, 0x1dd5, 0x1dec, 0x1e03, + 0x1e1b, 0x1e32, 0x1e4a, 0x1e61, 0x1e79, 0x1e91, 0x1ea8, 0x1ec0, 0x1ed8, + 0x1ef0, 0x1f07, 0x1f1f, 0x1f37, 0x1f4f, 0x1f67, 0x1f7f, 0x1f98, 0x1fb0, + 0x1fc8, 0x1fe0, 0x1ff8, 0x2011, 0x2029, 0x2042, 0x205a, 0x2072, 0x208b, + 0x20a4, 0x20bc, 0x20d5, 0x20ee, 0x2106, 0x211f, 0x2138, 0x2151, 0x216a, + 0x2183, 0x219c, 0x21b5, 0x21ce, 0x21e7, 0x2200, 0x2219, 0x2233, 0x224c, + 0x2265, 0x227f, 0x2298, 0x22b2, 0x22cb, 0x22e5, 0x22fe, 0x2318, 0x2331, + 0x234b, 0x2365, 0x237f, 0x2399, 0x23b3, 0x23cc, 0x23e6, 0x2401, 0x241b, + 0x2435, 0x244f, 0x2469, 0x2483, 0x249e, 0x24b8, 0x24d2, 0x24ed, 0x2507, + 0x2522, 0x253c, 0x2557, 0x2571, 0x258c, 0x25a7, 0x25c2, 0x25dc, 0x25f7, + 0x2612, 0x262d, 0x2648, 0x2663, 0x267e, 0x2699, 0x26b4, 0x26cf, 0x26eb, + 0x2706, 0x2721, 0x273d, 0x2758, 0x2774, 0x278f, 0x27ab, 0x27c6, 0x27e2, + 0x27fd, 0x2819, 0x2835, 0x2851, 0x286d, 0x2888, 0x28a4, 0x28c0, 0x28dc, + 0x28f8, 0x2915, 0x2931, 0x294d, 0x2969, 0x2985, 0x29a2, 0x29be, 0x29db, + 0x29f7, 0x2a14, 0x2a30, 0x2a4d, 0x2a69, 0x2a86, 0x2aa3, 0x2ac0, 0x2adc, + 0x2af9, 0x2b16, 0x2b33, 0x2b50, 0x2b6d, 0x2b8a, 0x2ba7, 0x2bc4, 0x2be2, + 0x2bff, 0x2c1c, 0x2c3a, 0x2c57, 0x2c74, 0x2c92, 0x2caf, 0x2ccd, 0x2ceb, + 0x2d08, 0x2d26, 0x2d44, 0x2d61, 0x2d7f, 0x2d9d, 0x2dbb, 0x2dd9, 0x2df7, + 0x2e15, 0x2e33, 0x2e51, 0x2e70, 0x2e8e, 0x2eac, 0x2eca, 0x2ee9, 0x2f07, + 0x2f26, 0x2f44, 0x2f63, 0x2f81, 0x2fa0, 0x2fbf, 0x2fdd, 0x2ffc, 0x301b, + 0x303a, 0x3059, 0x3078, 0x3097, 0x30b6, 0x30d5, 0x30f4, 0x3113, 0x3132, + 0x3152, 0x3171, 0x3190, 0x31b0, 0x31cf, 0x31ef, 0x320e, 0x322e, 0x324e, + 0x326d, 0x328d, 0x32ad, 0x32cd, 0x32ec, 0x330c, 0x332c, 0x334c, 0x336c, + 0x338c, 0x33ac, 0x33cd, 0x33ed, 0x340d, 0x342e, 0x344e, 0x346e, 0x348f, + 0x34af, 0x34d0, 0x34f0, 0x3511, 0x3532, 0x3552, 0x3573, 0x3594, 0x35b5, + 0x35d6, 0x35f7, 0x3618, 0x3639, 0x365a, 0x367b, 0x369c, 0x36bd, 0x36df, + 0x3700, 0x3721, 0x3743, 0x3764, 0x3786, 0x37a7, 0x37c9, 0x37eb, 0x380c, + 0x382e, 0x3850, 0x3872, 0x3894, 0x38b6, 0x38d8, 0x38fa, 0x391c, 0x393e, + 0x3960, 0x3982, 0x39a4, 0x39c7, 0x39e9, 0x3a0b, 0x3a2e, 0x3a50, 0x3a73, + 0x3a95, 0x3ab8, 0x3adb, 0x3afd, 0x3b20, 0x3b43, 0x3b66, 0x3b89, 0x3bac, + 0x3bcf, 0x3bf2, 0x3c15, 0x3c38, 0x3c5b, 0x3c7e, 0x3ca2, 0x3cc5, 0x3ce8, + 0x3d0c, 0x3d2f, 0x3d53, 0x3d76, 0x3d9a, 0x3dbe, 0x3de1, 0x3e05, 0x3e29, + 0x3e4d, 0x3e71, 0x3e95, 0x3eb9, 0x3edd, 0x3f01, 0x3f25, 0x3f49, 0x3f6d, + 0x3f91, 0x3fb6, 0x3fda, 0x3fff, 0x0}, + {0x3, 0x0, 0x200, 0x600, 0x800, 0x980, 0xaa0, 0xba0, 0xc70, 0xd20, + 0xde0, 0xe58, 0xed0, 0xf58, 0xfe0, 0x103c, 0x1090, 0x10e8, 0x1144, + 0x11a8, 0x120a, 0x1242, 0x127e, 0x12bc, 0x1300, 0x1346, 0x138e, 0x13dc, + 0x1416, 0x1441, 0x146d, 0x149b, 0x14cc, 0x14fe, 0x1532, 0x1569, 0x15a2, + 0x15dd, 0x160d, 0x162c, 0x164d, 0x166f, 0x1693, 0x16b7, 0x16dd, 0x1704, + 0x172c, 0x1756, 0x1781, 0x17ad, 0x17db, 0x1805, 0x181d, 0x1836, 0x1850, + 0x186b, 0x1886, 0x18a2, 0x18bf, 0x18dd, 0x18fb, 0x191b, 0x193b, 0x195c, + 0x197e, 0x19a1, 0x19c5, 0x19e9, 0x1a07, 0x1a1b, 0x1a2e, 0x1a43, 0x1a57, + 0x1a6d, 0x1a83, 0x1a99, 0x1ab0, 0x1ac7, 0x1adf, 0x1af8, 0x1b11, 0x1b2a, + 0x1b45, 0x1b5f, 0x1b7b, 0x1b97, 0x1bb4, 0x1bd1, 0x1bef, 0x1c06, 0x1c16, + 0x1c26, 0x1c36, 0x1c47, 0x1c58, 0x1c69, 0x1c7b, 0x1c8d, 0x1c9f, 0x1cb2, + 0x1cc5, 0x1cd9, 0x1ced, 0x1d01, 0x1d16, 0x1d2b, 0x1d40, 0x1d56, 0x1d6d, + 0x1d83, 0x1d9b, 0x1db2, 0x1dca, 0x1de3, 0x1dfc, 0x1e0a, 0x1e17, 0x1e25, + 0x1e32, 0x1e40, 0x1e4e, 0x1e5c, 0x1e6a, 0x1e79, 0x1e88, 0x1e97, 0x1ea7, + 0x1eb7, 0x1ec7, 0x1ed7, 0x1ee8, 0x1ef9, 0x1f0a, 0x1f1b, 0x1f2d, 0x1f3f, + 0x1f52, 0x1f64, 0x1f77, 0x1f8b, 0x1f9e, 0x1fb2, 0x1fc6, 0x1fdb, 0x1ff0, + 0x2002, 0x200d, 0x2018, 0x2024, 0x202f, 0x203a, 0x2046, 0x2052, 0x205e, + 0x206a, 0x2077, 0x2084, 0x2091, 0x209e, 0x20ab, 0x20b8, 0x20c6, 0x20d4, + 0x20e2, 0x20f1, 0x20ff, 0x210e, 0x211d, 0x212c, 0x213c, 0x214b, 0x215b, + 0x216b, 0x217c, 0x218d, 0x219d, 0x21af, 0x21c0, 0x21d2, 0x21e4, 0x21f6, + 0x2204, 0x220d, 0x2217, 0x2220, 0x222a, 0x2234, 0x223e, 0x2248, 0x2253, + 0x225d, 0x2268, 0x2273, 0x227d, 0x2289, 0x2294, 0x229f, 0x22ab, 0x22b6, + 0x22c2, 0x22ce, 0x22db, 0x22e7, 0x22f4, 0x2300, 0x230d, 0x231a, 0x2328, + 0x2335, 0x2343, 0x2350, 0x235e, 0x236d, 0x237b, 0x238a, 0x2398, 0x23a7, + 0x23b7, 0x23c6, 0x23d5, 0x23e5, 0x23f5, 0x2402, 0x240b, 0x2413, 0x241b, + 0x2424, 0x242d, 0x2435, 0x243e, 0x2447, 0x2450, 0x245a, 0x2463, 0x246c, + 0x2476, 0x2480, 0x248a, 0x2493, 0x249e, 0x24a8, 0x24b2, 0x24bd, 0x24c7, + 0x24d2, 0x24dd, 0x24e8, 0x24f3, 0x24fe, 0x250a, 0x2515, 0x2521, 0x252d, + 0x2539, 0x2545, 0x2552, 0x255e, 0x256b, 0x2578, 0x2584, 0x2592, 0x259f, + 0x25ac, 0x25ba, 0x25c8, 0x25d6, 0x25e4, 0x25f2, 0x2600, 0x2607, 0x260f, + 0x2616, 0x261e, 0x2626, 0x262d, 0x2635, 0x263d, 0x2645, 0x264d, 0x2656, + 0x265e, 0x2666, 0x266f, 0x2678, 0x2680, 0x2689, 0x2692, 0x269b, 0x26a5, + 0x26ae, 0x26b7, 0x26c1, 0x26ca, 0x26d4, 0x26de, 0x26e8, 0x26f2, 0x26fc, + 0x2707, 0x2711, 0x271c, 0x2726, 0x2731, 0x273c, 0x2747, 0x2753, 0x275e, + 0x2769, 0x2775, 0x2781, 0x278d, 0x2799, 0x27a5, 0x27b1, 0x27bd, 0x27ca, + 0x27d7, 0x27e4, 0x27f1, 0x27fe, 0x2805, 0x280c, 0x2813, 0x281a, 0x2821, + 0x2828, 0x282f, 0x2836, 0x283d, 0x2845, 0x284c, 0x2854, 0x285b, 0x2863, + 0x286b, 0x2873, 0x287b, 0x2883, 0x288b, 0x2893, 0x289c, 0x28a4, 0x28ad, + 0x28b5, 0x28be, 0x28c7, 0x28d0, 0x28d9, 0x28e2, 0x28eb, 0x28f4, 0x28fe, + 0x2907, 0x2911, 0x291b, 0x2925, 0x292f, 0x2939, 0x2943, 0x294d, 0x2958, + 0x2962, 0x296d, 0x2977, 0x2982, 0x298d, 0x2998, 0x29a4, 0x29af, 0x29bb, + 0x29c6, 0x29d2, 0x29de, 0x29ea, 0x29f6, 0x2a01, 0x2a07, 0x2a0d, 0x2a14, + 0x2a1a, 0x2a21, 0x2a27, 0x2a2e, 0x2a34, 0x2a3b, 0x2a42, 0x2a49, 0x2a50, + 0x2a57, 0x2a5e, 0x2a65, 0x2a6d, 0x2a74, 0x2a7b, 0x2a83, 0x2a8a, 0x2a92, + 0x2a9a, 0x2aa2, 0x2aaa, 0x2ab2, 0x2aba, 0x2ac2, 0x2aca, 0x2ad3, 0x2adb, + 0x2ae4, 0x2aec, 0x2af5, 0x2afe, 0x2b07, 0x2b10, 0x2b19, 0x2b22, 0x2b2b, + 0x2b35, 0x2b3e, 0x2b48, 0x2b52, 0x2b5c, 0x2b65, 0x2b6f, 0x2b7a, 0x2b84, + 0x2b8e, 0x2b99, 0x2ba3, 0x2bae, 0x2bb9, 0x2bc4, 0x2bcf, 0x2bda, 0x2be5, + 0x2bf0, 0x2bfc, 0x2c03, 0x2c09, 0x2c0f, 0x2c15, 0x2c1b, 0x2c21, 0x2c28, + 0x2c2e, 0x2c34, 0x2c3b, 0x2c41, 0x2c48, 0x2c4e, 0x2c55, 0x2c5b, 0x2c62, + 0x2c69, 0x2c70, 0x2c77, 0x2c7e, 0x2c85, 0x2c8c, 0x2c94, 0x2c9b, 0x2ca3, + 0x2caa, 0x2cb2, 0x2cb9, 0x2cc1, 0x2cc9, 0x2cd1, 0x2cd9, 0x2ce1, 0x2ce9, + 0x2cf2, 0x2cfa, 0x2d02, 0x2d0b, 0x2d13, 0x2d1c, 0x2d25, 0x2d2e, 0x2d37, + 0x2d40, 0x2d49, 0x2d52, 0x2d5c, 0x2d65, 0x2d6f, 0x2d78, 0x2d82, 0x2d8c, + 0x2d96, 0x2da0, 0x2daa, 0x2db4, 0x2dbf, 0x2dc9, 0x2dd4, 0x2dde, 0x2de9, + 0x2df4, 0x2dff, 0x2e05, 0x2e0b, 0x2e10, 0x2e16, 0x2e1c, 0x2e22, 0x2e28, + 0x2e2d, 0x2e34, 0x2e3a, 0x2e40, 0x2e46, 0x2e4c, 0x2e53, 0x2e59, 0x2e60, + 0x2e66, 0x2e6d, 0x2e73, 0x2e7a, 0x2e81, 0x2e88, 0x2e8f, 0x2e96, 0x2e9d, + 0x2ea4, 0x2eab, 0x2eb3, 0x2eba, 0x2ec2, 0x2ec9, 0x2ed1, 0x2ed9, 0x2ee0, + 0x2ee8, 0x2ef0, 0x2ef8, 0x2f00, 0x2f09, 0x2f11, 0x2f19, 0x2f22, 0x2f2a, + 0x2f33, 0x2f3c, 0x2f44, 0x2f4d, 0x2f56, 0x2f5f, 0x2f68, 0x2f72, 0x2f7b, + 0x2f85, 0x2f8e, 0x2f98, 0x2fa1, 0x2fab, 0x2fb5, 0x2fbf, 0x2fc9, 0x2fd4, + 0x2fde, 0x2fe8, 0x2ff3, 0x2ffe, 0x3004, 0x3009, 0x300f, 0x3014, 0x301a, + 0x3020, 0x3025, 0x302b, 0x3031, 0x3037, 0x303d, 0x3043, 0x3049, 0x304f, + 0x3055, 0x305c, 0x3062, 0x3068, 0x306f, 0x3075, 0x307c, 0x3083, 0x3089, + 0x3090, 0x3097, 0x309e, 0x30a5, 0x30ac, 0x30b3, 0x30ba, 0x30c2, 0x30c9, + 0x30d1, 0x30d8, 0x30e0, 0x30e7, 0x30ef, 0x30f7, 0x30ff, 0x3107, 0x310f, + 0x3117, 0x311f, 0x3128, 0x3130, 0x3139, 0x3141, 0x314a, 0x3153, 0x315b, + 0x3164, 0x316d, 0x3176, 0x3180, 0x3189, 0x3192, 0x319c, 0x31a5, 0x31af, + 0x31b9, 0x31c3, 0x31cd, 0x31d7, 0x31e1, 0x31eb, 0x31f6, 0x3200, 0x3205, + 0x320a, 0x3210, 0x3215, 0x321b, 0x3220, 0x3226, 0x322c, 0x3232, 0x3237, + 0x323d, 0x3243, 0x3249, 0x324f, 0x3255, 0x325b, 0x3262, 0x3268, 0x326e, + 0x3275, 0x327b, 0x3282, 0x3288, 0x328f, 0x3296, 0x329d, 0x32a4, 0x32ab, + 0x32b2, 0x32b9, 0x32c0, 0x32c7, 0x32cf, 0x32d6, 0x32dd, 0x32e5, 0x32ed, + 0x32f4, 0x32fc, 0x3304, 0x330c, 0x3314, 0x331c, 0x3324, 0x332d, 0x3335, + 0x333d, 0x3346, 0x334f, 0x3357, 0x3360, 0x3369, 0x3372, 0x337b, 0x3384, + 0x338d, 0x3397, 0x33a0, 0x33aa, 0x33b3, 0x33bd, 0x33c7, 0x33d1, 0x33db, + 0x33e5, 0x33ef, 0x33fa, 0x3402, 0x3407, 0x340d, 0x3412, 0x3417, 0x341d, + 0x3422, 0x3428, 0x342e, 0x3434, 0x3439, 0x343f, 0x3445, 0x344b, 0x3451, + 0x3457, 0x345d, 0x3464, 0x346a, 0x3470, 0x3477, 0x347d, 0x3484, 0x348a, + 0x3491, 0x3498, 0x349f, 0x34a6, 0x34ad, 0x34b4, 0x34bb, 0x34c2, 0x34c9, + 0x34d1, 0x34d8, 0x34df, 0x34e7, 0x34ef, 0x34f6, 0x34fe, 0x3506, 0x350e, + 0x3516, 0x351e, 0x3526, 0x352f, 0x3537, 0x3540, 0x3548, 0x3551, 0x355a, + 0x3562, 0x356b, 0x3574, 0x357e, 0x3587, 0x3590, 0x359a, 0x35a3, 0x35ad, + 0x35b6, 0x35c0, 0x35ca, 0x35d4, 0x35de, 0x35e8, 0x35f3, 0x35fd, 0x3604, + 0x3609, 0x360e, 0x3614, 0x3619, 0x361f, 0x3624, 0x362a, 0x3630, 0x3636, + 0x363b, 0x3641, 0x3647, 0x364d, 0x3654, 0x365a, 0x3660, 0x3666, 0x366d, + 0x3673, 0x367a, 0x3680, 0x3687, 0x368d, 0x3694, 0x369b, 0x36a2, 0x36a9, + 0x36b0, 0x36b7, 0x36be, 0x36c6, 0x36cd, 0x36d5, 0x36dc, 0x36e4, 0x36eb, + 0x36f3, 0x36fb, 0x3703, 0x370b, 0x3713, 0x371b, 0x3724, 0x372c, 0x3734, + 0x373d, 0x3746, 0x374e, 0x3757, 0x3760, 0x3769, 0x3772, 0x377b, 0x3785, + 0x378e, 0x3798, 0x37a1, 0x37ab, 0x37b5, 0x37bf, 0x37c9, 0x37d3, 0x37dd, + 0x37e7, 0x37f2, 0x37fd, 0x3803, 0x3809, 0x380e, 0x3814, 0x3819, 0x381f, + 0x3825, 0x382a, 0x3830, 0x3836, 0x383c, 0x3842, 0x3848, 0x384e, 0x3855, + 0x385b, 0x3861, 0x3868, 0x386e, 0x3875, 0x387b, 0x3882, 0x3889, 0x3890, + 0x3897, 0x389e, 0x38a5, 0x38ac, 0x38b3, 0x38ba, 0x38c2, 0x38c9, 0x38d1, + 0x38d8, 0x38e0, 0x38e8, 0x38f0, 0x38f8, 0x3900, 0x3908, 0x3910, 0x3919, + 0x3921, 0x3929, 0x3932, 0x393b, 0x3944, 0x394c, 0x3955, 0x395f, 0x3968, + 0x3971, 0x397a, 0x3984, 0x398e, 0x3997, 0x39a1, 0x39ab, 0x39b5, 0x39bf, + 0x39c9, 0x39d4, 0x39de, 0x39e9, 0x39f4, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x3}, + {0x3, 0x0, 0x4, 0x8, 0xc, 0x10, 0x14, 0x18, 0x1c, 0x20, 0x24, 0x28, + 0x2c, 0x30, 0x34, 0x38, 0x3c, 0x41, 0x45, 0x49, 0x4d, 0x51, 0x55, 0x59, + 0x5d, 0x61, 0x65, 0x69, 0x6d, 0x71, 0x75, 0x79, 0x7d, 0x82, 0x86, 0x8a, + 0x8e, 0x92, 0x96, 0x9a, 0x9e, 0xa2, 0xa6, 0xaa, 0xae, 0xb2, 0xb6, 0xba, + 0xbe, 0xc3, 0xc7, 0xcb, 0xcf, 0xd3, 0xd7, 0xdb, 0xdf, 0xe3, 0xe7, 0xeb, + 0xef, 0xf3, 0xf7, 0xfb, 0xff, 0x104, 0x108, 0x10c, 0x110, 0x114, 0x118, + 0x11c, 0x120, 0x124, 0x127, 0x12b, 0x12f, 0x134, 0x138, 0x13c, 0x140, + 0x144, 0x149, 0x14d, 0x151, 0x156, 0x15a, 0x15f, 0x163, 0x168, 0x16c, + 0x171, 0x175, 0x17a, 0x17f, 0x183, 0x188, 0x18d, 0x192, 0x197, 0x19b, + 0x1a0, 0x1a5, 0x1aa, 0x1af, 0x1b4, 0x1b9, 0x1be, 0x1c3, 0x1c9, 0x1ce, + 0x1d3, 0x1d8, 0x1dd, 0x1e3, 0x1e8, 0x1ed, 0x1f3, 0x1f8, 0x1fe, 0x203, + 0x209, 0x20e, 0x214, 0x21a, 0x21f, 0x225, 0x22b, 0x230, 0x236, 0x23c, + 0x242, 0x248, 0x24e, 0x254, 0x25a, 0x260, 0x266, 0x26c, 0x272, 0x278, + 0x27e, 0x285, 0x28b, 0x291, 0x297, 0x29e, 0x2a4, 0x2ab, 0x2b1, 0x2b8, + 0x2be, 0x2c5, 0x2cb, 0x2d2, 0x2d9, 0x2df, 0x2e6, 0x2ed, 0x2f4, 0x2fa, + 0x301, 0x308, 0x30f, 0x316, 0x31d, 0x324, 0x32b, 0x332, 0x339, 0x341, + 0x348, 0x34f, 0x356, 0x35e, 0x365, 0x36c, 0x374, 0x37b, 0x383, 0x38a, + 0x392, 0x399, 0x3a1, 0x3a8, 0x3b0, 0x3b8, 0x3c0, 0x3c7, 0x3cf, 0x3d7, + 0x3df, 0x3e7, 0x3ef, 0x3f7, 0x3ff, 0x407, 0x40f, 0x417, 0x41f, 0x427, + 0x430, 0x438, 0x440, 0x449, 0x451, 0x459, 0x462, 0x46a, 0x473, 0x47b, + 0x484, 0x48d, 0x495, 0x49e, 0x4a7, 0x4af, 0x4b8, 0x4c1, 0x4ca, 0x4d3, + 0x4dc, 0x4e5, 0x4ee, 0x4f7, 0x500, 0x509, 0x512, 0x51b, 0x525, 0x52e, + 0x537, 0x540, 0x54a, 0x553, 0x55d, 0x566, 0x570, 0x579, 0x583, 0x58c, + 0x596, 0x5a0, 0x5a9, 0x5b3, 0x5bd, 0x5c7, 0x5d1, 0x5db, 0x5e5, 0x5ef, + 0x5f9, 0x603, 0x60d, 0x617, 0x621, 0x62b, 0x635, 0x640, 0x64a, 0x654, + 0x65f, 0x669, 0x674, 0x67e, 0x689, 0x693, 0x69e, 0x6a8, 0x6b3, 0x6be, + 0x6c8, 0x6d3, 0x6de, 0x6e9, 0x6f4, 0x6ff, 0x70a, 0x715, 0x720, 0x72b, + 0x736, 0x741, 0x74c, 0x757, 0x763, 0x76e, 0x779, 0x785, 0x790, 0x79c, + 0x7a7, 0x7b3, 0x7be, 0x7ca, 0x7d5, 0x7e1, 0x7ed, 0x7f8, 0x804, 0x810, + 0x81c, 0x828, 0x834, 0x840, 0x84c, 0x858, 0x864, 0x870, 0x87c, 0x888, + 0x894, 0x8a1, 0x8ad, 0x8b9, 0x8c6, 0x8d2, 0x8df, 0x8eb, 0x8f8, 0x904, + 0x911, 0x91d, 0x92a, 0x937, 0x944, 0x950, 0x95d, 0x96a, 0x977, 0x984, + 0x991, 0x99e, 0x9ab, 0x9b8, 0x9c5, 0x9d2, 0x9e0, 0x9ed, 0x9fa, 0xa07, + 0xa15, 0xa22, 0xa30, 0xa3d, 0xa4b, 0xa58, 0xa66, 0xa73, 0xa81, 0xa8f, + 0xa9d, 0xaaa, 0xab8, 0xac6, 0xad4, 0xae2, 0xaf0, 0xafe, 0xb0c, 0xb1a, + 0xb28, 0xb36, 0xb44, 0xb53, 0xb61, 0xb6f, 0xb7e, 0xb8c, 0xb9a, 0xba9, + 0xbb7, 0xbc6, 0xbd5, 0xbe3, 0xbf2, 0xc01, 0xc0f, 0xc1e, 0xc2d, 0xc3c, + 0xc4b, 0xc5a, 0xc69, 0xc78, 0xc87, 0xc96, 0xca5, 0xcb4, 0xcc3, 0xcd3, + 0xce2, 0xcf1, 0xd01, 0xd10, 0xd1f, 0xd2f, 0xd3f, 0xd4e, 0xd5e, 0xd6d, + 0xd7d, 0xd8d, 0xd9c, 0xdac, 0xdbc, 0xdcc, 0xddc, 0xdec, 0xdfc, 0xe0c, + 0xe1c, 0xe2c, 0xe3c, 0xe4c, 0xe5d, 0xe6d, 0xe7d, 0xe8e, 0xe9e, 0xeae, + 0xebf, 0xecf, 0xee0, 0xef1, 0xf01, 0xf12, 0xf23, 0xf33, 0xf44, 0xf55, + 0xf66, 0xf77, 0xf88, 0xf99, 0xfaa, 0xfbb, 0xfcc, 0xfdd, 0xfee, 0xfff, + 0x1011, 0x1022, 0x1033, 0x1045, 0x1056, 0x1068, 0x1079, 0x108b, 0x109c, + 0x10ae, 0x10c0, 0x10d1, 0x10e3, 0x10f5, 0x1107, 0x1119, 0x112b, 0x113c, + 0x114e, 0x1160, 0x1173, 0x1185, 0x1197, 0x11a9, 0x11bb, 0x11ce, 0x11e0, + 0x11f2, 0x1205, 0x1217, 0x1229, 0x123c, 0x124f, 0x1261, 0x1274, 0x1286, + 0x1299, 0x12ac, 0x12bf, 0x12d2, 0x12e4, 0x12f7, 0x130a, 0x131d, 0x1330, + 0x1343, 0x1357, 0x136a, 0x137d, 0x1390, 0x13a3, 0x13b7, 0x13ca, 0x13de, + 0x13f1, 0x1405, 0x1418, 0x142c, 0x143f, 0x1453, 0x1467, 0x147a, 0x148e, + 0x14a2, 0x14b6, 0x14ca, 0x14de, 0x14f2, 0x1506, 0x151a, 0x152e, 0x1542, + 0x1556, 0x156a, 0x157f, 0x1593, 0x15a7, 0x15bc, 0x15d0, 0x15e5, 0x15f9, + 0x160e, 0x1622, 0x1637, 0x164c, 0x1660, 0x1675, 0x168a, 0x169f, 0x16b4, + 0x16c9, 0x16de, 0x16f3, 0x1708, 0x171d, 0x1732, 0x1747, 0x175c, 0x1772, + 0x1787, 0x179c, 0x17b2, 0x17c7, 0x17dc, 0x17f2, 0x1808, 0x181d, 0x1833, + 0x1848, 0x185e, 0x1874, 0x188a, 0x18a0, 0x18b5, 0x18cb, 0x18e1, 0x18f7, + 0x190d, 0x1923, 0x193a, 0x1950, 0x1966, 0x197c, 0x1993, 0x19a9, 0x19bf, + 0x19d6, 0x19ec, 0x1a03, 0x1a19, 0x1a30, 0x1a47, 0x1a5d, 0x1a74, 0x1a8b, + 0x1aa2, 0x1ab8, 0x1acf, 0x1ae6, 0x1afd, 0x1b14, 0x1b2b, 0x1b42, 0x1b59, + 0x1b71, 0x1b88, 0x1b9f, 0x1bb6, 0x1bce, 0x1be5, 0x1bfd, 0x1c14, 0x1c2c, + 0x1c43, 0x1c5b, 0x1c72, 0x1c8a, 0x1ca2, 0x1cba, 0x1cd2, 0x1ce9, 0x1d01, + 0x1d19, 0x1d31, 0x1d49, 0x1d61, 0x1d79, 0x1d92, 0x1daa, 0x1dc2, 0x1dda, + 0x1df3, 0x1e0b, 0x1e23, 0x1e3c, 0x1e54, 0x1e6d, 0x1e86, 0x1e9e, 0x1eb7, + 0x1ed0, 0x1ee8, 0x1f01, 0x1f1a, 0x1f33, 0x1f4c, 0x1f65, 0x1f7e, 0x1f97, + 0x1fb0, 0x1fc9, 0x1fe2, 0x1ffb, 0x2015, 0x202e, 0x2047, 0x2061, 0x207a, + 0x2094, 0x20ad, 0x20c7, 0x20e0, 0x20fa, 0x2114, 0x212d, 0x2147, 0x2161, + 0x217b, 0x2195, 0x21af, 0x21c9, 0x21e3, 0x21fd, 0x2217, 0x2231, 0x224b, + 0x2266, 0x2280, 0x229a, 0x22b5, 0x22cf, 0x22ea, 0x2304, 0x231f, 0x2339, + 0x2354, 0x236f, 0x2389, 0x23a4, 0x23bf, 0x23da, 0x23f5, 0x2410, 0x242b, + 0x2446, 0x2461, 0x247c, 0x2497, 0x24b2, 0x24ce, 0x24e9, 0x2504, 0x2520, + 0x253b, 0x2556, 0x2572, 0x258e, 0x25a9, 0x25c5, 0x25e0, 0x25fc, 0x2618, + 0x2634, 0x2650, 0x266c, 0x2687, 0x26a3, 0x26c0, 0x26dc, 0x26f8, 0x2714, + 0x2730, 0x274c, 0x2769, 0x2785, 0x27a1, 0x27be, 0x27da, 0x27f7, 0x2813, + 0x2830, 0x284d, 0x2869, 0x2886, 0x28a3, 0x28c0, 0x28dc, 0x28f9, 0x2916, + 0x2933, 0x2950, 0x296d, 0x298b, 0x29a8, 0x29c5, 0x29e2, 0x2a00, 0x2a1d, + 0x2a3a, 0x2a58, 0x2a75, 0x2a93, 0x2ab0, 0x2ace, 0x2aec, 0x2b09, 0x2b27, + 0x2b45, 0x2b63, 0x2b81, 0x2b9e, 0x2bbc, 0x2bda, 0x2bf8, 0x2c17, 0x2c35, + 0x2c53, 0x2c71, 0x2c8f, 0x2cae, 0x2ccc, 0x2ceb, 0x2d09, 0x2d27, 0x2d46, + 0x2d65, 0x2d83, 0x2da2, 0x2dc1, 0x2ddf, 0x2dfe, 0x2e1d, 0x2e3c, 0x2e5b, + 0x2e7a, 0x2e99, 0x2eb8, 0x2ed7, 0x2ef6, 0x2f15, 0x2f35, 0x2f54, 0x2f73, + 0x2f93, 0x2fb2, 0x2fd2, 0x2ff1, 0x3011, 0x3030, 0x3050, 0x3070, 0x308f, + 0x30af, 0x30cf, 0x30ef, 0x310f, 0x312f, 0x314f, 0x316f, 0x318f, 0x31af, + 0x31cf, 0x31f0, 0x3210, 0x3230, 0x3251, 0x3271, 0x3291, 0x32b2, 0x32d2, + 0x32f3, 0x3314, 0x3334, 0x3355, 0x3376, 0x3397, 0x33b8, 0x33d8, 0x33f9, + 0x341a, 0x343b, 0x345d, 0x347e, 0x349f, 0x34c0, 0x34e1, 0x3503, 0x3524, + 0x3545, 0x3567, 0x3588, 0x35aa, 0x35cb, 0x35ed, 0x360f, 0x3631, 0x3652, + 0x3674, 0x3696, 0x36b8, 0x36da, 0x36fc, 0x371e, 0x3740, 0x3762, 0x3784, + 0x37a6, 0x37c9, 0x37eb, 0x380d, 0x3830, 0x3852, 0x3875, 0x3897, 0x38ba, + 0x38dc, 0x38ff, 0x3922, 0x3945, 0x3967, 0x398a, 0x39ad, 0x39d0, 0x39f3, + 0x3a16, 0x3a39, 0x3a5c, 0x3a7f, 0x3aa3, 0x3ac6, 0x3ae9, 0x3b0d, 0x3b30, + 0x3b53, 0x3b77, 0x3b9a, 0x3bbe, 0x3be2, 0x3c05, 0x3c29, 0x3c4d, 0x3c71, + 0x3c94, 0x3cb8, 0x3cdc, 0x3d00, 0x3d24, 0x3d48, 0x3d6c, 0x3d91, 0x3db5, + 0x3dd9, 0x3dfd, 0x3e22, 0x3e46, 0x3e6b, 0x3e8f, 0x3eb4, 0x3ed8, 0x3efd, + 0x3f21, 0x3f46, 0x3f6b, 0x3f90, 0x3fb5, 0x3fda, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0}, + {0x3, 0x0, 0xf78, 0x137a, 0x15ea, 0x177a, 0x18b7, 0x19ea, 0x1aaa, + 0x1b7a, 0x1c33, 0x1cb7, 0x1d4a, 0x1dea, 0x1e4c, 0x1eaa, 0x1f0e, 0x1f7a, + 0x1fed, 0x2033, 0x2074, 0x20b7, 0x20ff, 0x214a, 0x2198, 0x21ea, 0x221f, + 0x224c, 0x227a, 0x22aa, 0x22db, 0x230e, 0x2343, 0x237a, 0x23b3, 0x23ed, + 0x2414, 0x2433, 0x2453, 0x2474, 0x2495, 0x24b7, 0x24db, 0x24ff, 0x2524, + 0x254a, 0x2570, 0x2598, 0x25c0, 0x25ea, 0x260a, 0x261f, 0x2635, 0x264c, + 0x2662, 0x267a, 0x2691, 0x26aa, 0x26c2, 0x26db, 0x26f5, 0x270e, 0x2729, + 0x2743, 0x275f, 0x277a, 0x2796, 0x27b3, 0x27d0, 0x27ed, 0x2805, 0x2814, + 0x2824, 0x2833, 0x2843, 0x2853, 0x2863, 0x2874, 0x2884, 0x2895, 0x28a6, + 0x28b7, 0x28c9, 0x28db, 0x28ed, 0x28ff, 0x2911, 0x2924, 0x2937, 0x294a, + 0x295d, 0x2970, 0x2984, 0x2998, 0x29ac, 0x29c0, 0x29d5, 0x29ea, 0x29ff, + 0x2a0a, 0x2a14, 0x2a1f, 0x2a2a, 0x2a35, 0x2a40, 0x2a4c, 0x2a57, 0x2a62, + 0x2a6e, 0x2a7a, 0x2a85, 0x2a91, 0x2a9d, 0x2aaa, 0x2ab6, 0x2ac2, 0x2acf, + 0x2adb, 0x2ae8, 0x2af5, 0x2b01, 0x2b0e, 0x2b1c, 0x2b29, 0x2b36, 0x2b43, + 0x2b51, 0x2b5f, 0x2b6c, 0x2b7a, 0x2b88, 0x2b96, 0x2ba5, 0x2bb3, 0x2bc1, + 0x2bd0, 0x2bde, 0x2bed, 0x2bfc, 0x2c05, 0x2c0d, 0x2c14, 0x2c1c, 0x2c24, + 0x2c2b, 0x2c33, 0x2c3b, 0x2c43, 0x2c4b, 0x2c53, 0x2c5b, 0x2c63, 0x2c6b, + 0x2c74, 0x2c7c, 0x2c84, 0x2c8d, 0x2c95, 0x2c9e, 0x2ca6, 0x2caf, 0x2cb7, + 0x2cc0, 0x2cc9, 0x2cd2, 0x2cdb, 0x2ce4, 0x2ced, 0x2cf6, 0x2cff, 0x2d08, + 0x2d11, 0x2d1a, 0x2d24, 0x2d2d, 0x2d37, 0x2d40, 0x2d4a, 0x2d53, 0x2d5d, + 0x2d67, 0x2d70, 0x2d7a, 0x2d84, 0x2d8e, 0x2d98, 0x2da2, 0x2dac, 0x2db6, + 0x2dc0, 0x2dcb, 0x2dd5, 0x2ddf, 0x2dea, 0x2df4, 0x2dff, 0x2e04, 0x2e0a, + 0x2e0f, 0x2e14, 0x2e1a, 0x2e1f, 0x2e25, 0x2e2a, 0x2e30, 0x2e35, 0x2e3b, + 0x2e40, 0x2e46, 0x2e4c, 0x2e51, 0x2e57, 0x2e5d, 0x2e62, 0x2e68, 0x2e6e, + 0x2e74, 0x2e7a, 0x2e80, 0x2e85, 0x2e8b, 0x2e91, 0x2e97, 0x2e9d, 0x2ea3, + 0x2eaa, 0x2eb0, 0x2eb6, 0x2ebc, 0x2ec2, 0x2ec8, 0x2ecf, 0x2ed5, 0x2edb, + 0x2ee1, 0x2ee8, 0x2eee, 0x2ef5, 0x2efb, 0x2f01, 0x2f08, 0x2f0e, 0x2f15, + 0x2f1c, 0x2f22, 0x2f29, 0x2f2f, 0x2f36, 0x2f3d, 0x2f43, 0x2f4a, 0x2f51, + 0x2f58, 0x2f5f, 0x2f66, 0x2f6c, 0x2f73, 0x2f7a, 0x2f81, 0x2f88, 0x2f8f, + 0x2f96, 0x2f9d, 0x2fa5, 0x2fac, 0x2fb3, 0x2fba, 0x2fc1, 0x2fc8, 0x2fd0, + 0x2fd7, 0x2fde, 0x2fe6, 0x2fed, 0x2ff5, 0x2ffc, 0x3001, 0x3005, 0x3009, + 0x300d, 0x3011, 0x3014, 0x3018, 0x301c, 0x3020, 0x3024, 0x3028, 0x302b, + 0x302f, 0x3033, 0x3037, 0x303b, 0x303f, 0x3043, 0x3047, 0x304b, 0x304f, + 0x3053, 0x3057, 0x305b, 0x305f, 0x3063, 0x3067, 0x306b, 0x306f, 0x3074, + 0x3078, 0x307c, 0x3080, 0x3084, 0x3088, 0x308d, 0x3091, 0x3095, 0x3099, + 0x309e, 0x30a2, 0x30a6, 0x30aa, 0x30af, 0x30b3, 0x30b7, 0x30bc, 0x30c0, + 0x30c5, 0x30c9, 0x30cd, 0x30d2, 0x30d6, 0x30db, 0x30df, 0x30e4, 0x30e8, + 0x30ed, 0x30f1, 0x30f6, 0x30fa, 0x30ff, 0x3103, 0x3108, 0x310d, 0x3111, + 0x3116, 0x311a, 0x311f, 0x3124, 0x3128, 0x312d, 0x3132, 0x3137, 0x313b, + 0x3140, 0x3145, 0x314a, 0x314e, 0x3153, 0x3158, 0x315d, 0x3162, 0x3167, + 0x316b, 0x3170, 0x3175, 0x317a, 0x317f, 0x3184, 0x3189, 0x318e, 0x3193, + 0x3198, 0x319d, 0x31a2, 0x31a7, 0x31ac, 0x31b1, 0x31b6, 0x31bb, 0x31c0, + 0x31c5, 0x31cb, 0x31d0, 0x31d5, 0x31da, 0x31df, 0x31e4, 0x31ea, 0x31ef, + 0x31f4, 0x31f9, 0x31ff, 0x3202, 0x3204, 0x3207, 0x320a, 0x320c, 0x320f, + 0x3212, 0x3214, 0x3217, 0x321a, 0x321c, 0x321f, 0x3222, 0x3225, 0x3227, + 0x322a, 0x322d, 0x3230, 0x3232, 0x3235, 0x3238, 0x323b, 0x323e, 0x3240, + 0x3243, 0x3246, 0x3249, 0x324c, 0x324e, 0x3251, 0x3254, 0x3257, 0x325a, + 0x325d, 0x3260, 0x3262, 0x3265, 0x3268, 0x326b, 0x326e, 0x3271, 0x3274, + 0x3277, 0x327a, 0x327d, 0x3280, 0x3282, 0x3285, 0x3288, 0x328b, 0x328e, + 0x3291, 0x3294, 0x3297, 0x329a, 0x329d, 0x32a0, 0x32a3, 0x32a6, 0x32aa, + 0x32ad, 0x32b0, 0x32b3, 0x32b6, 0x32b9, 0x32bc, 0x32bf, 0x32c2, 0x32c6, + 0x32c9, 0x32cc, 0x32cf, 0x32d3, 0x32d6, 0x32d9, 0x32dd, 0x32e0, 0x32e4, + 0x32e7, 0x32ea, 0x32ee, 0x32f1, 0x32f5, 0x32f8, 0x32fc, 0x32ff, 0x3303, + 0x3307, 0x330a, 0x330e, 0x3312, 0x3315, 0x3319, 0x331d, 0x3321, 0x3324, + 0x3328, 0x332c, 0x3330, 0x3334, 0x3338, 0x333c, 0x3340, 0x3344, 0x3348, + 0x334c, 0x3350, 0x3354, 0x3358, 0x335c, 0x3360, 0x3365, 0x3369, 0x336d, + 0x3371, 0x3376, 0x337a, 0x337e, 0x3383, 0x3387, 0x338c, 0x3390, 0x3394, + 0x3399, 0x339e, 0x33a2, 0x33a7, 0x33ab, 0x33b0, 0x33b5, 0x33b9, 0x33be, + 0x33c3, 0x33c8, 0x33cd, 0x33d2, 0x33d6, 0x33db, 0x33e0, 0x33e5, 0x33ea, + 0x33ef, 0x33f5, 0x33fa, 0x33ff, 0x3402, 0x3404, 0x3407, 0x340a, 0x340c, + 0x340f, 0x3412, 0x3414, 0x3417, 0x341a, 0x341d, 0x341f, 0x3422, 0x3425, + 0x3428, 0x342b, 0x342e, 0x3430, 0x3433, 0x3436, 0x3439, 0x343c, 0x343f, + 0x3442, 0x3445, 0x3448, 0x344b, 0x344e, 0x3451, 0x3455, 0x3458, 0x345b, + 0x345e, 0x3461, 0x3464, 0x3468, 0x346b, 0x346e, 0x3471, 0x3475, 0x3478, + 0x347b, 0x347f, 0x3482, 0x3486, 0x3489, 0x348d, 0x3490, 0x3493, 0x3497, + 0x349b, 0x349e, 0x34a2, 0x34a5, 0x34a9, 0x34ad, 0x34b0, 0x34b4, 0x34b8, + 0x34bc, 0x34bf, 0x34c3, 0x34c7, 0x34cb, 0x34cf, 0x34d3, 0x34d6, 0x34da, + 0x34de, 0x34e2, 0x34e6, 0x34ea, 0x34ee, 0x34f3, 0x34f7, 0x34fb, 0x34ff, + 0x3503, 0x3507, 0x350c, 0x3510, 0x3514, 0x3519, 0x351d, 0x3521, 0x3526, + 0x352a, 0x352f, 0x3533, 0x3538, 0x353c, 0x3541, 0x3545, 0x354a, 0x354f, + 0x3553, 0x3558, 0x355d, 0x3562, 0x3566, 0x356b, 0x3570, 0x3575, 0x357a, + 0x357f, 0x3584, 0x3589, 0x358e, 0x3593, 0x3598, 0x359d, 0x35a3, 0x35a8, + 0x35ad, 0x35b2, 0x35b8, 0x35bd, 0x35c2, 0x35c8, 0x35cd, 0x35d3, 0x35d8, + 0x35de, 0x35e4, 0x35e9, 0x35ef, 0x35f4, 0x35fa, 0x3600, 0x3603, 0x3606, + 0x3609, 0x360b, 0x360e, 0x3611, 0x3614, 0x3617, 0x361b, 0x361e, 0x3621, + 0x3624, 0x3627, 0x362a, 0x362d, 0x3630, 0x3634, 0x3637, 0x363a, 0x363d, + 0x3641, 0x3644, 0x3647, 0x364b, 0x364e, 0x3651, 0x3655, 0x3658, 0x365c, + 0x365f, 0x3663, 0x3666, 0x366a, 0x366d, 0x3671, 0x3675, 0x3678, 0x367c, + 0x3680, 0x3683, 0x3687, 0x368b, 0x368e, 0x3692, 0x3696, 0x369a, 0x369e, + 0x36a2, 0x36a6, 0x36aa, 0x36ae, 0x36b2, 0x36b6, 0x36ba, 0x36be, 0x36c2, + 0x36c6, 0x36ca, 0x36ce, 0x36d2, 0x36d7, 0x36db, 0x36df, 0x36e3, 0x36e8, + 0x36ec, 0x36f0, 0x36f5, 0x36f9, 0x36fe, 0x3702, 0x3707, 0x370b, 0x3710, + 0x3715, 0x3719, 0x371e, 0x3723, 0x3727, 0x372c, 0x3731, 0x3736, 0x373a, + 0x373f, 0x3744, 0x3749, 0x374e, 0x3753, 0x3758, 0x375d, 0x3762, 0x3767, + 0x376d, 0x3772, 0x3777, 0x377c, 0x3782, 0x3787, 0x378c, 0x3792, 0x3797, + 0x379c, 0x37a2, 0x37a7, 0x37ad, 0x37b3, 0x37b8, 0x37be, 0x37c4, 0x37c9, + 0x37cf, 0x37d5, 0x37db, 0x37e1, 0x37e7, 0x37ec, 0x37f2, 0x37f8, 0x37ff, + 0x3802, 0x3805, 0x3808, 0x380b, 0x380e, 0x3812, 0x3815, 0x3818, 0x381b, + 0x381e, 0x3822, 0x3825, 0x3828, 0x382c, 0x382f, 0x3832, 0x3836, 0x3839, + 0x383c, 0x3840, 0x3843, 0x3847, 0x384a, 0x384e, 0x3851, 0x3855, 0x3858, + 0x385c, 0x3860, 0x3863, 0x3867, 0x386b, 0x386e, 0x3872, 0x3876, 0x387a, + 0x387e, 0x3881, 0x3885, 0x3889, 0x388d, 0x3891, 0x3895, 0x3899, 0x389d, + 0x38a1, 0x38a5, 0x38a9, 0x38ad, 0x38b1, 0x38b6, 0x38ba, 0x38be, 0x38c2, + 0x38c7, 0x38cb, 0x38cf, 0x38d4, 0x38d8, 0x38dc, 0x38e1, 0x38e5, 0x38ea, + 0x38ee, 0x38f3, 0x38f7, 0x38fc, 0x3901, 0x3905, 0x390a, 0x390f, 0x3913, + 0x3918, 0x391d, 0x3922, 0x3927, 0x392c, 0x3931, 0x3936, 0x393b, 0x3940, + 0x3945, 0x394a, 0x394f, 0x3954, 0x3959, 0x395e, 0x3964, 0x3969, 0x396e, + 0x3974, 0x3979, 0x397e, 0x3984, 0x3989, 0x398f, 0x3994, 0x399a, 0x39a0, + 0x39a5, 0x39ab, 0x39b1, 0x39b6, 0x39bc, 0x39c2, 0x39c8, 0x39ce, 0x39d4, + 0x39da, 0x39e0, 0x39e6, 0x39ec, 0x39f2, 0x39f8, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x3}, +}; + +static const u32 dcss_cscbs[5][29] = { + {0x8000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0}, + {0x3, 0x503c, 0x2a1c, 0x58a, 0x8d7, 0x7598, 0x174, 0x219, 0xb42, + 0x7288, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3fff, 0x3fff, 0x3fff, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xffc0000, 0xffc0000, 0xffc0000}, + {0x3, 0x53c5, 0x26a5, 0x57a, 0x93a, 0x7539, 0x170, 0x231, 0xc84, + 0x712f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3fff, 0x3fff, 0x3fff, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xffc0000, 0xffc0000, 0xffc0000}, + {0x3, 0x42d2, 0xfffffd30, 0x0, 0x0, 0x4000, 0x0, 0x0, 0xc2, 0x3f3f, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3fff, 0x3fff, 0x3fff, 0xe, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x3fff, 0x3fff, 0x3fff}, + {0x3, 0x3d4d, 0x2b4, 0x0, 0x0, 0x4000, 0x0, 0x0, 0xffffff3e, 0x40c4, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3fff, 0x3fff, 0x3fff, 0xe, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x3fff, 0x3fff, 0x3fff}, +}; + +static const u16 dcss_oluts[11][1024] = { + {0x155, 0xe, 0x13ef, 0x1, 0x4f, 0x536, 0x3fff, 0x0, 0x5, 0x23, 0xa7, + 0x2a7, 0xa2f, 0x2758, 0x3fff, 0x0, 0x0, 0x2, 0x9, 0x17, 0x35, 0x73, + 0xef, 0x1e2, 0x3b8, 0x749, 0xe3d, 0x1bf7, 0x378e, 0x3fff, 0x3fff, 0x0, + 0x0, 0x0, 0x1, 0x2, 0x3, 0x6, 0xb, 0x12, 0x1c, 0x2b, 0x41, 0x60, 0x8b, + 0xc8, 0x11e, 0x195, 0x23c, 0x324, 0x467, 0x629, 0x89d, 0xc0a, 0x10d8, + 0x179b, 0x2128, 0x2ebb, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x2, 0x3, 0x4, 0x6, 0x7, 0xa, + 0xd, 0x10, 0x14, 0x19, 0x20, 0x27, 0x30, 0x3b, 0x47, 0x57, 0x69, 0x7f, + 0x98, 0xb7, 0xdb, 0x106, 0x138, 0x174, 0x1ba, 0x20d, 0x26f, 0x2e3, + 0x36b, 0x40c, 0x4ca, 0x5aa, 0x6b3, 0x7ec, 0x95d, 0xb12, 0xd18, 0xf7c, + 0x1253, 0x15b1, 0x19b1, 0x1e73, 0x241e, 0x2ae0, 0x32f2, 0x3c9a, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x1, 0x1, 0x1, 0x2, 0x2, 0x3, 0x3, 0x4, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, + 0xa, 0xc, 0xd, 0xf, 0x11, 0x13, 0x15, 0x18, 0x1b, 0x1e, 0x21, 0x25, + 0x29, 0x2e, 0x32, 0x38, 0x3e, 0x44, 0x4b, 0x53, 0x5b, 0x64, 0x6e, 0x79, + 0x85, 0x92, 0xa0, 0xaf, 0xbf, 0xd1, 0xe5, 0xfa, 0x111, 0x12b, 0x146, + 0x164, 0x184, 0x1a7, 0x1ce, 0x1f7, 0x224, 0x255, 0x28a, 0x2c4, 0x303, + 0x347, 0x391, 0x3e1, 0x439, 0x498, 0x4ff, 0x56f, 0x5e8, 0x66d, 0x6fc, + 0x799, 0x842, 0x8fb, 0x9c4, 0xa9e, 0xb8c, 0xc8e, 0xda7, 0xed9, 0x1027, + 0x1191, 0x131d, 0x14cb, 0x16a0, 0x18a0, 0x1ace, 0x1d2e, 0x1fc6, 0x229b, + 0x25b2, 0x2912, 0x2cc2, 0x30ca, 0x3533, 0x3a06, 0x3f4d, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x4, + 0x4, 0x4, 0x5, 0x5, 0x5, 0x6, 0x6, 0x7, 0x7, 0x8, 0x8, 0x9, 0x9, 0xa, + 0xb, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x10, 0x11, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x19, 0x1a, 0x1c, 0x1d, 0x1f, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, + 0x2c, 0x2f, 0x31, 0x34, 0x36, 0x39, 0x3c, 0x3f, 0x42, 0x46, 0x49, 0x4d, + 0x51, 0x55, 0x59, 0x5d, 0x62, 0x67, 0x6c, 0x71, 0x76, 0x7c, 0x82, 0x88, + 0x8e, 0x95, 0x9c, 0xa3, 0xab, 0xb3, 0xbb, 0xc4, 0xcd, 0xd6, 0xe0, 0xea, + 0xf5, 0x100, 0x10b, 0x118, 0x124, 0x131, 0x13f, 0x14d, 0x15c, 0x16c, + 0x17c, 0x18d, 0x19e, 0x1b1, 0x1c4, 0x1d8, 0x1ec, 0x202, 0x218, 0x230, + 0x248, 0x262, 0x27d, 0x298, 0x2b5, 0x2d3, 0x2f3, 0x313, 0x335, 0x359, + 0x37e, 0x3a4, 0x3cd, 0x3f7, 0x422, 0x450, 0x47f, 0x4b1, 0x4e4, 0x51a, + 0x552, 0x58c, 0x5c9, 0x608, 0x64b, 0x68f, 0x6d7, 0x722, 0x770, 0x7c2, + 0x817, 0x86f, 0x8cb, 0x92c, 0x990, 0x9f9, 0xa66, 0xad8, 0xb4e, 0xbca, + 0xc4b, 0xcd2, 0xd5f, 0xdf1, 0xe8a, 0xf2a, 0xfd1, 0x107e, 0x1134, + 0x11f1, 0x12b7, 0x1385, 0x145c, 0x153d, 0x1627, 0x171c, 0x181c, 0x1927, + 0x1a3e, 0x1b61, 0x1c91, 0x1dcf, 0x1f1b, 0x2075, 0x21df, 0x235a, 0x24e5, + 0x2683, 0x2833, 0x29f6, 0x2bce, 0x2dbc, 0x2fc0, 0x31db, 0x340f, 0x365d, + 0x38c6, 0x3b4c, 0x3def, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, + 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, 0x6, 0x7, 0x7, + 0x7, 0x7, 0x8, 0x8, 0x8, 0x8, 0x9, 0x9, 0x9, 0xa, 0xa, 0xa, 0xb, 0xb, + 0xb, 0xc, 0xc, 0xc, 0xd, 0xd, 0xe, 0xe, 0xe, 0xf, 0xf, 0x10, 0x10, + 0x11, 0x11, 0x12, 0x12, 0x13, 0x13, 0x14, 0x15, 0x15, 0x16, 0x16, 0x17, + 0x18, 0x18, 0x19, 0x1a, 0x1a, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x24, 0x25, 0x26, 0x27, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x31, 0x32, 0x33, 0x34, 0x36, 0x37, 0x39, 0x3a, 0x3b, + 0x3d, 0x3e, 0x40, 0x42, 0x43, 0x45, 0x47, 0x48, 0x4a, 0x4c, 0x4e, 0x50, + 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x61, 0x63, 0x65, 0x68, 0x6a, + 0x6d, 0x6f, 0x72, 0x75, 0x77, 0x7a, 0x7d, 0x80, 0x83, 0x86, 0x89, 0x8d, + 0x90, 0x93, 0x97, 0x9a, 0x9e, 0xa1, 0xa5, 0xa9, 0xad, 0xb1, 0xb5, 0xb9, + 0xbd, 0xc1, 0xc6, 0xca, 0xcf, 0xd4, 0xd8, 0xdd, 0xe2, 0xe7, 0xed, 0xf2, + 0xf7, 0xfd, 0x103, 0x108, 0x10e, 0x114, 0x11b, 0x121, 0x127, 0x12e, + 0x135, 0x13b, 0x142, 0x14a, 0x151, 0x158, 0x160, 0x168, 0x170, 0x178, + 0x180, 0x188, 0x191, 0x19a, 0x1a3, 0x1ac, 0x1b5, 0x1bf, 0x1c9, 0x1d3, + 0x1dd, 0x1e7, 0x1f2, 0x1fc, 0x207, 0x213, 0x21e, 0x22a, 0x236, 0x242, + 0x24f, 0x25b, 0x268, 0x276, 0x283, 0x291, 0x29f, 0x2ae, 0x2bd, 0x2cc, + 0x2db, 0x2eb, 0x2fb, 0x30b, 0x31c, 0x32d, 0x33e, 0x350, 0x362, 0x374, + 0x387, 0x39b, 0x3ae, 0x3c2, 0x3d7, 0x3ec, 0x401, 0x417, 0x42d, 0x444, + 0x45b, 0x473, 0x48b, 0x4a4, 0x4bd, 0x4d7, 0x4f1, 0x50c, 0x528, 0x544, + 0x560, 0x57d, 0x59b, 0x5ba, 0x5d9, 0x5f8, 0x619, 0x63a, 0x65c, 0x67e, + 0x6a1, 0x6c5, 0x6ea, 0x70f, 0x735, 0x75c, 0x784, 0x7ad, 0x7d7, 0x801, + 0x82c, 0x859, 0x886, 0x8b4, 0x8e3, 0x913, 0x944, 0x977, 0x9aa, 0x9de, + 0xa14, 0xa4a, 0xa82, 0xabb, 0xaf5, 0xb30, 0xb6d, 0xbab, 0xbea, 0xc2b, + 0xc6d, 0xcb0, 0xcf5, 0xd3b, 0xd83, 0xdcc, 0xe17, 0xe63, 0xeb2, 0xf01, + 0xf53, 0xfa6, 0xffb, 0x1052, 0x10ab, 0x1106, 0x1162, 0x11c1, 0x1222, + 0x1284, 0x12e9, 0x1350, 0x13ba, 0x1425, 0x1493, 0x1504, 0x1576, 0x15ec, + 0x1664, 0x16de, 0x175b, 0x17db, 0x185e, 0x18e3, 0x196b, 0x19f7, 0x1a85, + 0x1b17, 0x1bac, 0x1c44, 0x1cdf, 0x1d7e, 0x1e20, 0x1ec6, 0x1f70, 0x201d, + 0x20ce, 0x2183, 0x223d, 0x22fa, 0x23bb, 0x2481, 0x254b, 0x261a, 0x26ed, + 0x27c5, 0x28a2, 0x2983, 0x2a6a, 0x2b56, 0x2c47, 0x2d3e, 0x2e3a, 0x2f3c, + 0x3044, 0x3152, 0x3266, 0x3380, 0x34a0, 0x35c7, 0x36f5, 0x3829, 0x3965, + 0x3aa8, 0x3bf2, 0x3d44, 0x3e9d, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x6}, + {0x2c59, 0x2416, 0x3322, 0x1dd0, 0x287e, 0x2ff0, 0x3679, 0x188b, + 0x2131, 0x2661, 0x2a78, 0x2e2b, 0x3186, 0x34c8, 0x3836, 0x141d, 0x1b81, + 0x1fa3, 0x22a3, 0x2531, 0x277a, 0x2988, 0x2b6b, 0x2d33, 0x2eed, 0x30a3, + 0x325a, 0x3416, 0x35b5, 0x374e, 0x38f8, 0xff8, 0x1698, 0x1a32, 0x1ca3, + 0x1eab, 0x206e, 0x2210, 0x2355, 0x2497, 0x25e9, 0x26e2, 0x2816, 0x28f9, + 0x2a17, 0x2ae8, 0x2c01, 0x2cbe, 0x2dbb, 0x2e85, 0x2f65, 0x3047, 0x310d, + 0x3209, 0x32b7, 0x339e, 0x3469, 0x3536, 0x3624, 0x36db, 0x37d2, 0x388f, + 0x3971, 0xbe0, 0x124a, 0x1572, 0x17b5, 0x1962, 0x1acc, 0x1c29, 0x1d30, + 0x1e43, 0x1f20, 0x201b, 0x20ca, 0x21a2, 0x2256, 0x22f8, 0x23bc, 0x2454, + 0x24e1, 0x2589, 0x2629, 0x269f, 0x272b, 0x27d0, 0x2848, 0x28b9, 0x293d, + 0x29d8, 0x2a45, 0x2aae, 0x2b27, 0x2bb4, 0x2c2b, 0x2c89, 0x2cf6, 0x2d74, + 0x2e03, 0x2e57, 0x2eb8, 0x2f27, 0x2fa8, 0x301e, 0x3073, 0x30d6, 0x3147, + 0x31ca, 0x3230, 0x3287, 0x32eb, 0x335e, 0x33e3, 0x343e, 0x3497, 0x34fd, + 0x3573, 0x35fc, 0x364d, 0x36a8, 0x3712, 0x378d, 0x380e, 0x3861, 0x38c1, + 0x3932, 0x39b5, 0x700, 0xe30, 0x1120, 0x132c, 0x14bb, 0x1621, 0x171f, + 0x182e, 0x18f2, 0x19de, 0x1a7c, 0x1b23, 0x1be6, 0x1c64, 0x1ce7, 0x1d7d, + 0x1e14, 0x1e75, 0x1ee3, 0x1f60, 0x1feb, 0x2043, 0x209b, 0x20fc, 0x2168, + 0x21e0, 0x2232, 0x227b, 0x22cc, 0x2325, 0x2387, 0x23f3, 0x2434, 0x2475, + 0x24bb, 0x2508, 0x255d, 0x25b8, 0x260e, 0x2644, 0x2680, 0x26c0, 0x2706, + 0x2752, 0x27a4, 0x27fd, 0x282f, 0x2863, 0x289b, 0x28d8, 0x291a, 0x2962, + 0x29af, 0x2a01, 0x2a2e, 0x2a5e, 0x2a92, 0x2aca, 0x2b07, 0x2b48, 0x2b8f, + 0x2bda, 0x2c16, 0x2c41, 0x2c70, 0x2ca3, 0x2cda, 0x2d14, 0x2d53, 0x2d97, + 0x2de0, 0x2e17, 0x2e41, 0x2e6e, 0x2e9e, 0x2ed2, 0x2f0a, 0x2f46, 0x2f86, + 0x2fcb, 0x300a, 0x3032, 0x305d, 0x308b, 0x30bc, 0x30f1, 0x3129, 0x3166, + 0x31a8, 0x31ee, 0x321c, 0x3245, 0x3270, 0x329f, 0x32d1, 0x3306, 0x3340, + 0x337e, 0x33c0, 0x3404, 0x342a, 0x3453, 0x347f, 0x34af, 0x34e2, 0x3519, + 0x3554, 0x3593, 0x35d8, 0x3610, 0x3638, 0x3662, 0x3690, 0x36c1, 0x36f6, + 0x372f, 0x376d, 0x37af, 0x37f7, 0x3821, 0x384b, 0x3878, 0x38a8, 0x38dc, + 0x3914, 0x3951, 0x3992, 0x39d9, 0x200, 0xa20, 0xd00, 0xf00, 0x1084, + 0x11d0, 0x12b6, 0x13ae, 0x1469, 0x1513, 0x15d7, 0x165b, 0x16da, 0x1768, + 0x1803, 0x185b, 0x18bd, 0x1929, 0x199f, 0x1a10, 0x1a56, 0x1aa3, 0x1af6, + 0x1b51, 0x1bb2, 0x1c0e, 0x1c46, 0x1c83, 0x1cc5, 0x1d0b, 0x1d56, 0x1da6, + 0x1dfc, 0x1e2b, 0x1e5c, 0x1e90, 0x1ec7, 0x1f01, 0x1f3f, 0x1f81, 0x1fc7, + 0x2008, 0x202f, 0x2058, 0x2084, 0x20b2, 0x20e3, 0x2116, 0x214c, 0x2185, + 0x21c1, 0x2200, 0x2221, 0x2244, 0x2268, 0x228f, 0x22b7, 0x22e2, 0x230e, + 0x233d, 0x236e, 0x23a1, 0x23d7, 0x2407, 0x2425, 0x2444, 0x2464, 0x2486, + 0x24a9, 0x24ce, 0x24f4, 0x251d, 0x2547, 0x2573, 0x25a1, 0x25d1, 0x2601, + 0x261b, 0x2636, 0x2653, 0x2670, 0x268f, 0x26af, 0x26d1, 0x26f4, 0x2718, + 0x273e, 0x2766, 0x278f, 0x27b9, 0x27e6, 0x280a, 0x2822, 0x283b, 0x2855, + 0x2870, 0x288d, 0x28aa, 0x28c9, 0x28e8, 0x2909, 0x292c, 0x294f, 0x2975, + 0x299b, 0x29c3, 0x29ed, 0x2a0c, 0x2a22, 0x2a39, 0x2a52, 0x2a6b, 0x2a85, + 0x2aa0, 0x2abc, 0x2ad9, 0x2af7, 0x2b17, 0x2b38, 0x2b59, 0x2b7d, 0x2ba1, + 0x2bc7, 0x2bee, 0x2c0b, 0x2c20, 0x2c36, 0x2c4d, 0x2c64, 0x2c7d, 0x2c96, + 0x2cb0, 0x2ccc, 0x2ce8, 0x2d05, 0x2d24, 0x2d43, 0x2d64, 0x2d85, 0x2da9, + 0x2dcd, 0x2df3, 0x2e0d, 0x2e21, 0x2e36, 0x2e4c, 0x2e62, 0x2e79, 0x2e92, + 0x2eab, 0x2ec5, 0x2ee0, 0x2efb, 0x2f18, 0x2f36, 0x2f55, 0x2f76, 0x2f97, + 0x2fb9, 0x2fdd, 0x3001, 0x3014, 0x3028, 0x303d, 0x3052, 0x3068, 0x307f, + 0x3097, 0x30af, 0x30c9, 0x30e3, 0x30ff, 0x311b, 0x3138, 0x3157, 0x3176, + 0x3197, 0x31b9, 0x31dc, 0x3200, 0x3213, 0x3226, 0x323a, 0x324f, 0x3265, + 0x327b, 0x3293, 0x32ab, 0x32c4, 0x32de, 0x32f8, 0x3314, 0x3331, 0x334f, + 0x336e, 0x338e, 0x33af, 0x33d2, 0x33f5, 0x340d, 0x3420, 0x3434, 0x3448, + 0x345e, 0x3474, 0x348b, 0x34a3, 0x34bb, 0x34d5, 0x34ef, 0x350b, 0x3527, + 0x3545, 0x3563, 0x3583, 0x35a4, 0x35c6, 0x35ea, 0x3607, 0x361a, 0x362e, + 0x3642, 0x3657, 0x366d, 0x3684, 0x369c, 0x36b5, 0x36ce, 0x36e9, 0x3704, + 0x3721, 0x373e, 0x375d, 0x377d, 0x379e, 0x37c1, 0x37e4, 0x3804, 0x3818, + 0x382c, 0x3840, 0x3856, 0x386c, 0x3883, 0x389b, 0x38b5, 0x38cf, 0x38ea, + 0x3906, 0x3923, 0x3941, 0x3961, 0x3981, 0x39a3, 0x39c7, 0x39ec, 0x0, + 0x500, 0x8c0, 0xae0, 0xc70, 0xdb0, 0xe98, 0xf78, 0x103c, 0x10d0, + 0x1178, 0x1218, 0x127e, 0x12f0, 0x136c, 0x13f2, 0x1442, 0x1491, 0x14e6, + 0x1542, 0x15a4, 0x1606, 0x163e, 0x1679, 0x16b9, 0x16fc, 0x1743, 0x178e, + 0x17dd, 0x1818, 0x1844, 0x1873, 0x18a4, 0x18d7, 0x190d, 0x1945, 0x1980, + 0x19be, 0x19ff, 0x1a21, 0x1a44, 0x1a69, 0x1a8f, 0x1ab7, 0x1ae1, 0x1b0c, + 0x1b39, 0x1b68, 0x1b99, 0x1bcc, 0x1c00, 0x1c1b, 0x1c38, 0x1c55, 0x1c74, + 0x1c93, 0x1cb4, 0x1cd6, 0x1cf9, 0x1d1d, 0x1d42, 0x1d69, 0x1d91, 0x1dbb, + 0x1de6, 0x1e09, 0x1e20, 0x1e37, 0x1e4f, 0x1e69, 0x1e82, 0x1e9d, 0x1eb9, + 0x1ed5, 0x1ef2, 0x1f10, 0x1f2f, 0x1f4f, 0x1f70, 0x1f92, 0x1fb5, 0x1fd9, + 0x1ffe, 0x2012, 0x2025, 0x2039, 0x204e, 0x2063, 0x2079, 0x208f, 0x20a6, + 0x20be, 0x20d6, 0x20ef, 0x2109, 0x2123, 0x213e, 0x215a, 0x2176, 0x2194, + 0x21b2, 0x21d0, 0x21f0, 0x2208, 0x2218, 0x2229, 0x223b, 0x224d, 0x225f, + 0x2272, 0x2285, 0x2299, 0x22ad, 0x22c2, 0x22d7, 0x22ed, 0x2303, 0x231a, + 0x2331, 0x2349, 0x2361, 0x237a, 0x2394, 0x23ae, 0x23c9, 0x23e5, 0x2400, + 0x240f, 0x241d, 0x242c, 0x243c, 0x244c, 0x245c, 0x246c, 0x247d, 0x248e, + 0x24a0, 0x24b2, 0x24c4, 0x24d7, 0x24eb, 0x24fe, 0x2512, 0x2527, 0x253c, + 0x2552, 0x2568, 0x257e, 0x2595, 0x25ac, 0x25c4, 0x25dd, 0x25f6, 0x2607, + 0x2614, 0x2622, 0x262f, 0x263d, 0x264c, 0x265a, 0x2669, 0x2678, 0x2687, + 0x2697, 0x26a7, 0x26b8, 0x26c8, 0x26d9, 0x26eb, 0x26fd, 0x270f, 0x2722, + 0x2734, 0x2748, 0x275c, 0x2770, 0x2784, 0x2799, 0x27af, 0x27c4, 0x27db, + 0x27f1, 0x2804, 0x2810, 0x281c, 0x2828, 0x2835, 0x2842, 0x284f, 0x285c, + 0x286a, 0x2877, 0x2886, 0x2894, 0x28a3, 0x28b2, 0x28c1, 0x28d0, 0x28e0, + 0x28f1, 0x2901, 0x2912, 0x2923, 0x2935, 0x2946, 0x2959, 0x296b, 0x297e, + 0x2991, 0x29a5, 0x29b9, 0x29cd, 0x29e2, 0x29f7, 0x2a06, 0x2a11, 0x2a1c, + 0x2a28, 0x2a33, 0x2a3f, 0x2a4b, 0x2a58, 0x2a64, 0x2a71, 0x2a7e, 0x2a8b, + 0x2a99, 0x2aa7, 0x2ab5, 0x2ac3, 0x2ad2, 0x2ae1, 0x2af0, 0x2aff, 0x2b0f, + 0x2b1f, 0x2b2f, 0x2b40, 0x2b51, 0x2b62, 0x2b74, 0x2b86, 0x2b98, 0x2baa, + 0x2bbd, 0x2bd0, 0x2be4, 0x2bf8, 0x2c06, 0x2c10, 0x2c1b, 0x2c26, 0x2c31, + 0x2c3c, 0x2c47, 0x2c53, 0x2c5e, 0x2c6a, 0x2c77, 0x2c83, 0x2c90, 0x2c9d, + 0x2caa, 0x2cb7, 0x2cc5, 0x2cd3, 0x2ce1, 0x2cef, 0x2cfe, 0x2d0d, 0x2d1c, + 0x2d2b, 0x2d3b, 0x2d4b, 0x2d5b, 0x2d6c, 0x2d7d, 0x2d8e, 0x2da0, 0x2db2, + 0x2dc4, 0x2dd6, 0x2de9, 0x2dfc, 0x2e08, 0x2e12, 0x2e1c, 0x2e26, 0x2e30, + 0x2e3b, 0x2e46, 0x2e51, 0x2e5c, 0x2e68, 0x2e73, 0x2e7f, 0x2e8b, 0x2e98, + 0x2ea4, 0x2eb1, 0x2ebe, 0x2ecb, 0x2ed9, 0x2ee6, 0x2ef4, 0x2f03, 0x2f11, + 0x2f20, 0x2f2f, 0x2f3e, 0x2f4e, 0x2f5d, 0x2f6d, 0x2f7e, 0x2f8e, 0x2f9f, + 0x2fb1, 0x2fc2, 0x2fd4, 0x2fe6, 0x2ff9, 0x3006, 0x300f, 0x3019, 0x3023, + 0x302d, 0x3037, 0x3042, 0x304d, 0x3057, 0x3062, 0x306e, 0x3079, 0x3085, + 0x3091, 0x309d, 0x30a9, 0x30b6, 0x30c2, 0x30cf, 0x30dd, 0x30ea, 0x30f8, + 0x3106, 0x3114, 0x3122, 0x3131, 0x3140, 0x314f, 0x315e, 0x316e, 0x317e, + 0x318f, 0x319f, 0x31b0, 0x31c1, 0x31d3, 0x31e5, 0x31f7, 0x3204, 0x320e, + 0x3217, 0x3221, 0x322b, 0x3235, 0x323f, 0x324a, 0x3255, 0x325f, 0x326a, + 0x3276, 0x3281, 0x328d, 0x3299, 0x32a5, 0x32b1, 0x32bd, 0x32ca, 0x32d7, + 0x32e4, 0x32f2, 0x32ff, 0x330d, 0x331b, 0x332a, 0x3338, 0x3347, 0x3357, + 0x3366, 0x3376, 0x3386, 0x3396, 0x33a7, 0x33b8, 0x33c9, 0x33da, 0x33ec, + 0x33fe, 0x3408, 0x3412, 0x341b, 0x3425, 0x342f, 0x3439, 0x3443, 0x344e, + 0x3458, 0x3463, 0x346e, 0x347a, 0x3485, 0x3491, 0x349d, 0x34a9, 0x34b5, + 0x34c2, 0x34ce, 0x34db, 0x34e9, 0x34f6, 0x3504, 0x3512, 0x3520, 0x352f, + 0x353d, 0x354c, 0x355c, 0x356b, 0x357b, 0x358b, 0x359c, 0x35ad, 0x35be, + 0x35cf, 0x35e1, 0x35f3, 0x3602, 0x360c, 0x3615, 0x361f, 0x3629, 0x3633, + 0x363d, 0x3647, 0x3652, 0x365d, 0x3668, 0x3673, 0x367f, 0x368a, 0x3696, + 0x36a2, 0x36ae, 0x36bb, 0x36c8, 0x36d5, 0x36e2, 0x36f0, 0x36fd, 0x370b, + 0x371a, 0x3728, 0x3737, 0x3746, 0x3755, 0x3765, 0x3775, 0x3785, 0x3796, + 0x37a7, 0x37b8, 0x37c9, 0x37db, 0x37ed, 0x3800, 0x3809, 0x3813, 0x381d, + 0x3826, 0x3831, 0x383b, 0x3846, 0x3850, 0x385b, 0x3866, 0x3872, 0x387d, + 0x3889, 0x3895, 0x38a2, 0x38ae, 0x38bb, 0x38c8, 0x38d5, 0x38e3, 0x38f1, + 0x38ff, 0x390d, 0x391b, 0x392a, 0x3939, 0x3949, 0x3959, 0x3969, 0x3979, + 0x398a, 0x399b, 0x39ac, 0x39be, 0x39d0, 0x39e2, 0x39f5, 0xc}, + {0x1630, 0x667, 0x30ff, 0x241, 0xcfd, 0x2226, 0x3fff, 0x105, 0x40a, + 0x961, 0x1140, 0x1bd1, 0x2935, 0x3989, 0x3fff, 0x80, 0x193, 0x313, + 0x525, 0x7d0, 0xb1a, 0xf09, 0x13a2, 0x18ea, 0x1ee5, 0x2596, 0x2d02, + 0x352c, 0x3e17, 0x3fff, 0x3fff, 0x3e, 0xc3, 0x148, 0x1e6, 0x2a6, 0x38a, + 0x493, 0x5c2, 0x717, 0x893, 0xa38, 0xc06, 0xdfe, 0x101f, 0x126c, + 0x14e4, 0x1788, 0x1a58, 0x1d55, 0x2080, 0x23d9, 0x2760, 0x2b16, 0x2efb, + 0x3310, 0x3755, 0x3bca, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x1d, + 0x5f, 0xa2, 0xe4, 0x126, 0x16c, 0x1bb, 0x212, 0x273, 0x2dc, 0x34e, + 0x3c9, 0x44d, 0x4db, 0x572, 0x613, 0x6be, 0x772, 0x831, 0x8f9, 0x9cb, + 0xaa8, 0xb8f, 0xc80, 0xd7c, 0xe82, 0xf93, 0x10ae, 0x11d5, 0x1306, + 0x1442, 0x1588, 0x16da, 0x1837, 0x19a0, 0x1b13, 0x1c92, 0x1e1c, 0x1fb1, + 0x2152, 0x22fe, 0x24b6, 0x267a, 0x2849, 0x2a24, 0x2c0a, 0x2dfd, 0x2ffc, + 0x3206, 0x341c, 0x363f, 0x386d, 0x3aa8, 0x3cef, 0x3f42, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0xc, 0x2d, + 0x4e, 0x70, 0x91, 0xb2, 0xd3, 0xf5, 0x116, 0x137, 0x15a, 0x17f, 0x1a6, + 0x1d0, 0x1fc, 0x22a, 0x25a, 0x28c, 0x2c0, 0x2f7, 0x330, 0x36c, 0x3a9, + 0x3e9, 0x42b, 0x470, 0x4b7, 0x500, 0x54c, 0x59a, 0x5ea, 0x63d, 0x692, + 0x6ea, 0x744, 0x7a1, 0x800, 0x862, 0x8c6, 0x92d, 0x996, 0xa02, 0xa70, + 0xae1, 0xb54, 0xbca, 0xc43, 0xcbe, 0xd3c, 0xdbc, 0xe40, 0xec5, 0xf4e, + 0xfd9, 0x1067, 0x10f7, 0x118a, 0x1220, 0x12b8, 0x1354, 0x13f2, 0x1492, + 0x1536, 0x15dc, 0x1685, 0x1731, 0x17df, 0x1890, 0x1944, 0x19fb, 0x1ab5, + 0x1b72, 0x1c31, 0x1cf3, 0x1db8, 0x1e80, 0x1f4a, 0x2018, 0x20e8, 0x21bc, + 0x2292, 0x236b, 0x2447, 0x2526, 0x2608, 0x26ec, 0x27d4, 0x28be, 0x29ac, + 0x2a9c, 0x2b90, 0x2c86, 0x2d7f, 0x2e7c, 0x2f7b, 0x307d, 0x3182, 0x328a, + 0x3396, 0x34a4, 0x35b5, 0x36c9, 0x37e1, 0x38fb, 0x3a18, 0x3b39, 0x3c5c, + 0x3d83, 0x3eac, 0x3fd9, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x4, 0x14, 0x25, 0x36, 0x46, 0x57, 0x67, 0x78, + 0x89, 0x99, 0xaa, 0xbb, 0xcb, 0xdc, 0xec, 0xfd, 0x10e, 0x11e, 0x12e, + 0x13f, 0x151, 0x163, 0x176, 0x189, 0x19c, 0x1b1, 0x1c5, 0x1db, 0x1f1, + 0x207, 0x21e, 0x235, 0x24d, 0x266, 0x27f, 0x299, 0x2b3, 0x2ce, 0x2e9, + 0x305, 0x322, 0x33f, 0x35c, 0x37b, 0x399, 0x3b9, 0x3d9, 0x3f9, 0x41a, + 0x43c, 0x45e, 0x481, 0x4a5, 0x4c9, 0x4ed, 0x513, 0x538, 0x55f, 0x586, + 0x5ad, 0x5d6, 0x5ff, 0x628, 0x652, 0x67d, 0x6a8, 0x6d4, 0x700, 0x72d, + 0x75b, 0x789, 0x7b8, 0x7e8, 0x818, 0x849, 0x87b, 0x8ad, 0x8df, 0x913, + 0x947, 0x97b, 0x9b0, 0x9e6, 0xa1d, 0xa54, 0xa8c, 0xac4, 0xafd, 0xb37, + 0xb71, 0xbac, 0xbe8, 0xc24, 0xc61, 0xc9f, 0xcdd, 0xd1c, 0xd5c, 0xd9c, + 0xddd, 0xe1f, 0xe61, 0xea4, 0xee7, 0xf2b, 0xf70, 0xfb6, 0xffc, 0x1043, + 0x108a, 0x10d3, 0x111b, 0x1165, 0x11af, 0x11fa, 0x1246, 0x1292, 0x12df, + 0x132d, 0x137b, 0x13ca, 0x141a, 0x146a, 0x14bb, 0x150d, 0x155f, 0x15b2, + 0x1606, 0x165a, 0x16b0, 0x1705, 0x175c, 0x17b3, 0x180b, 0x1864, 0x18bd, + 0x1917, 0x1972, 0x19cd, 0x1a29, 0x1a86, 0x1ae4, 0x1b42, 0x1ba1, 0x1c01, + 0x1c61, 0x1cc2, 0x1d24, 0x1d86, 0x1dea, 0x1e4e, 0x1eb2, 0x1f18, 0x1f7e, + 0x1fe4, 0x204c, 0x20b4, 0x211d, 0x2187, 0x21f1, 0x225c, 0x22c8, 0x2334, + 0x23a2, 0x2410, 0x247e, 0x24ee, 0x255e, 0x25cf, 0x2640, 0x26b3, 0x2726, + 0x279a, 0x280e, 0x2884, 0x28fa, 0x2970, 0x29e8, 0x2a60, 0x2ad9, 0x2b53, + 0x2bcd, 0x2c48, 0x2cc4, 0x2d41, 0x2dbe, 0x2e3c, 0x2ebb, 0x2f3b, 0x2fbb, + 0x303c, 0x30be, 0x3141, 0x31c4, 0x3248, 0x32cd, 0x3353, 0x33d9, 0x3460, + 0x34e8, 0x3571, 0x35fa, 0x3684, 0x370f, 0x379b, 0x3827, 0x38b4, 0x3942, + 0x39d1, 0x3a60, 0x3af0, 0x3b81, 0x3c13, 0x3ca5, 0x3d39, 0x3dcd, 0x3e61, + 0x3ef7, 0x3f8d, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x0, 0x8, 0x10, 0x18, 0x21, 0x29, 0x31, 0x3a, + 0x42, 0x4a, 0x53, 0x5b, 0x63, 0x6c, 0x74, 0x7c, 0x84, 0x8d, 0x95, 0x9d, + 0xa6, 0xae, 0xb6, 0xbf, 0xc7, 0xcf, 0xd8, 0xe0, 0xe8, 0xf1, 0xf9, + 0x101, 0x109, 0x112, 0x11a, 0x122, 0x12a, 0x132, 0x13b, 0x143, 0x14c, + 0x155, 0x15e, 0x167, 0x171, 0x17a, 0x184, 0x18e, 0x197, 0x1a1, 0x1ac, + 0x1b6, 0x1c0, 0x1cb, 0x1d5, 0x1e0, 0x1eb, 0x1f6, 0x201, 0x20d, 0x218, + 0x224, 0x230, 0x23b, 0x247, 0x254, 0x260, 0x26c, 0x279, 0x286, 0x292, + 0x29f, 0x2ad, 0x2ba, 0x2c7, 0x2d5, 0x2e2, 0x2f0, 0x2fe, 0x30c, 0x31b, + 0x329, 0x338, 0x346, 0x355, 0x364, 0x373, 0x382, 0x392, 0x3a1, 0x3b1, + 0x3c1, 0x3d1, 0x3e1, 0x3f1, 0x402, 0x412, 0x423, 0x434, 0x445, 0x456, + 0x467, 0x478, 0x48a, 0x49c, 0x4ae, 0x4c0, 0x4d2, 0x4e4, 0x4f7, 0x509, + 0x51c, 0x52f, 0x542, 0x555, 0x569, 0x57c, 0x590, 0x5a4, 0x5b7, 0x5cc, + 0x5e0, 0x5f4, 0x609, 0x61e, 0x632, 0x647, 0x65d, 0x672, 0x687, 0x69d, + 0x6b3, 0x6c9, 0x6df, 0x6f5, 0x70c, 0x722, 0x739, 0x750, 0x767, 0x77e, + 0x795, 0x7ad, 0x7c4, 0x7dc, 0x7f4, 0x80c, 0x824, 0x83d, 0x855, 0x86e, + 0x887, 0x8a0, 0x8b9, 0x8d3, 0x8ec, 0x906, 0x920, 0x93a, 0x954, 0x96e, + 0x988, 0x9a3, 0x9be, 0x9d9, 0x9f4, 0xa0f, 0xa2b, 0xa46, 0xa62, 0xa7e, + 0xa9a, 0xab6, 0xad2, 0xaef, 0xb0c, 0xb29, 0xb46, 0xb63, 0xb80, 0xb9e, + 0xbbb, 0xbd9, 0xbf7, 0xc15, 0xc34, 0xc52, 0xc71, 0xc90, 0xcaf, 0xcce, + 0xced, 0xd0c, 0xd2c, 0xd4c, 0xd6c, 0xd8c, 0xdac, 0xdcd, 0xded, 0xe0e, + 0xe2f, 0xe50, 0xe71, 0xe93, 0xeb4, 0xed6, 0xef8, 0xf1a, 0xf3d, 0xf5f, + 0xf82, 0xfa4, 0xfc7, 0xfea, 0x100e, 0x1031, 0x1055, 0x1078, 0x109c, + 0x10c0, 0x10e5, 0x1109, 0x112e, 0x1153, 0x1178, 0x119d, 0x11c2, 0x11e7, + 0x120d, 0x1233, 0x1259, 0x127f, 0x12a5, 0x12cc, 0x12f2, 0x1319, 0x1340, + 0x1367, 0x138f, 0x13b6, 0x13de, 0x1406, 0x142e, 0x1456, 0x147e, 0x14a7, + 0x14cf, 0x14f8, 0x1521, 0x154a, 0x1574, 0x159d, 0x15c7, 0x15f1, 0x161b, + 0x1645, 0x1670, 0x169a, 0x16c5, 0x16f0, 0x171b, 0x1746, 0x1772, 0x179d, + 0x17c9, 0x17f5, 0x1821, 0x184e, 0x187a, 0x18a7, 0x18d4, 0x1901, 0x192e, + 0x195b, 0x1989, 0x19b6, 0x19e4, 0x1a12, 0x1a41, 0x1a6f, 0x1a9e, 0x1acc, + 0x1afb, 0x1b2a, 0x1b5a, 0x1b89, 0x1bb9, 0x1be9, 0x1c19, 0x1c49, 0x1c79, + 0x1caa, 0x1cdb, 0x1d0b, 0x1d3d, 0x1d6e, 0x1d9f, 0x1dd1, 0x1e03, 0x1e35, + 0x1e67, 0x1e99, 0x1ecb, 0x1efe, 0x1f31, 0x1f64, 0x1f97, 0x1fcb, 0x1ffe, + 0x2032, 0x2066, 0x209a, 0x20ce, 0x2103, 0x2137, 0x216c, 0x21a1, 0x21d6, + 0x220c, 0x2241, 0x2277, 0x22ad, 0x22e3, 0x2319, 0x2350, 0x2386, 0x23bd, + 0x23f4, 0x242b, 0x2463, 0x249a, 0x24d2, 0x250a, 0x2542, 0x257a, 0x25b3, + 0x25eb, 0x2624, 0x265d, 0x2696, 0x26d0, 0x2709, 0x2743, 0x277d, 0x27b7, + 0x27f1, 0x282b, 0x2866, 0x28a1, 0x28dc, 0x2917, 0x2953, 0x298e, 0x29ca, + 0x2a06, 0x2a42, 0x2a7e, 0x2abb, 0x2af7, 0x2b34, 0x2b71, 0x2bae, 0x2bec, + 0x2c29, 0x2c67, 0x2ca5, 0x2ce3, 0x2d21, 0x2d60, 0x2d9f, 0x2dde, 0x2e1d, + 0x2e5c, 0x2e9b, 0x2edb, 0x2f1b, 0x2f5b, 0x2f9b, 0x2fdb, 0x301c, 0x305d, + 0x309e, 0x30df, 0x3120, 0x3161, 0x31a3, 0x31e5, 0x3227, 0x3269, 0x32ac, + 0x32ee, 0x3331, 0x3374, 0x33b7, 0x33fb, 0x343e, 0x3482, 0x34c6, 0x350a, + 0x354e, 0x3593, 0x35d7, 0x361c, 0x3661, 0x36a7, 0x36ec, 0x3732, 0x3778, + 0x37be, 0x3804, 0x384a, 0x3891, 0x38d7, 0x391e, 0x3966, 0x39ad, 0x39f4, + 0x3a3c, 0x3a84, 0x3acc, 0x3b14, 0x3b5d, 0x3ba6, 0x3bee, 0x3c37, 0x3c81, + 0x3cca, 0x3d14, 0x3d5e, 0x3da8, 0x3df2, 0x3e3c, 0x3e87, 0x3ed2, 0x3f1c, + 0x3f68, 0x3fb3, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x6}, + {0x1095, 0x4fa, 0x240a, 0x1d9, 0x9d8, 0x1949, 0x30eb, 0xe0, 0x334, + 0x72f, 0xcf9, 0x14ae, 0x1e66, 0x2a35, 0x382e, 0x6e, 0x152, 0x27a, + 0x409, 0x606, 0x875, 0xb5a, 0xeb7, 0x1291, 0x16eb, 0x1bc7, 0x2127, + 0x270e, 0x2d7f, 0x347b, 0x3c04, 0x35, 0xa7, 0x119, 0x193, 0x226, 0x2d4, + 0x39b, 0x47e, 0x57d, 0x697, 0x7cf, 0x923, 0xa95, 0xc25, 0xdd4, 0xfa2, + 0x118f, 0x139c, 0x15c9, 0x1816, 0x1a84, 0x1d12, 0x1fc2, 0x2294, 0x2588, + 0x289e, 0x2bd6, 0x2f31, 0x32af, 0x3650, 0x3a15, 0x3dfd, 0x18, 0x51, + 0x8a, 0xc3, 0xfc, 0x134, 0x172, 0x1b5, 0x1ff, 0x24f, 0x2a6, 0x303, + 0x367, 0x3d2, 0x443, 0x4bb, 0x53a, 0x5c1, 0x64e, 0x6e2, 0x77e, 0x821, + 0x8cb, 0x97d, 0xa36, 0xaf6, 0xbbf, 0xc8e, 0xd66, 0xe45, 0xf2c, 0x101a, + 0x1111, 0x120f, 0x1316, 0x1424, 0x153a, 0x1659, 0x177f, 0x18ae, 0x19e5, + 0x1b24, 0x1c6c, 0x1dbb, 0x1f13, 0x2074, 0x21dd, 0x234e, 0x24c8, 0x264a, + 0x27d5, 0x2968, 0x2b05, 0x2ca9, 0x2e57, 0x300d, 0x31cc, 0x3394, 0x3564, + 0x373e, 0x3920, 0x3b0b, 0x3cff, 0x3efd, 0xa, 0x27, 0x43, 0x60, 0x7c, + 0x99, 0xb5, 0xd1, 0xee, 0x10a, 0x126, 0x143, 0x162, 0x182, 0x1a4, + 0x1c7, 0x1ec, 0x213, 0x23b, 0x264, 0x290, 0x2bd, 0x2eb, 0x31c, 0x34d, + 0x381, 0x3b6, 0x3ed, 0x426, 0x460, 0x49c, 0x4da, 0x51a, 0x55b, 0x59e, + 0x5e3, 0x62a, 0x672, 0x6bd, 0x709, 0x756, 0x7a6, 0x7f7, 0x84b, 0x8a0, + 0x8f7, 0x950, 0x9aa, 0xa07, 0xa65, 0xac6, 0xb28, 0xb8c, 0xbf2, 0xc5a, + 0xcc3, 0xd2f, 0xd9d, 0xe0c, 0xe7e, 0xef1, 0xf67, 0xfde, 0x1057, 0x10d3, + 0x1150, 0x11cf, 0x1250, 0x12d3, 0x1359, 0x13e0, 0x1469, 0x14f4, 0x1581, + 0x1610, 0x16a2, 0x1735, 0x17ca, 0x1862, 0x18fb, 0x1996, 0x1a34, 0x1ad4, + 0x1b75, 0x1c19, 0x1cbf, 0x1d67, 0x1e10, 0x1ebd, 0x1f6b, 0x201b, 0x20cd, + 0x2182, 0x2238, 0x22f1, 0x23ac, 0x2469, 0x2528, 0x25e9, 0x26ac, 0x2771, + 0x2839, 0x2903, 0x29cf, 0x2a9d, 0x2b6d, 0x2c3f, 0x2d14, 0x2deb, 0x2ec4, + 0x2f9f, 0x307c, 0x315b, 0x323d, 0x3321, 0x3407, 0x34ef, 0x35da, 0x36c7, + 0x37b6, 0x38a7, 0x399a, 0x3a90, 0x3b87, 0x3c82, 0x3d7e, 0x3e7c, 0x3f7d, + 0x3, 0x11, 0x20, 0x2e, 0x3c, 0x4a, 0x58, 0x67, 0x75, 0x83, 0x91, 0xa0, + 0xae, 0xbc, 0xca, 0xd9, 0xe7, 0xf5, 0x103, 0x112, 0x120, 0x12d, 0x13c, + 0x14b, 0x15a, 0x16a, 0x17a, 0x18a, 0x19b, 0x1ad, 0x1be, 0x1d0, 0x1e3, + 0x1f6, 0x209, 0x21c, 0x230, 0x245, 0x25a, 0x26f, 0x285, 0x29b, 0x2b1, + 0x2c8, 0x2df, 0x2f7, 0x30f, 0x328, 0x341, 0x35a, 0x374, 0x38e, 0x3a9, + 0x3c4, 0x3df, 0x3fb, 0x418, 0x434, 0x452, 0x46f, 0x48d, 0x4ac, 0x4cb, + 0x4ea, 0x50a, 0x52a, 0x54b, 0x56c, 0x58d, 0x5af, 0x5d2, 0x5f5, 0x618, + 0x63c, 0x660, 0x685, 0x6aa, 0x6cf, 0x6f5, 0x71c, 0x743, 0x76a, 0x792, + 0x7ba, 0x7e3, 0x80c, 0x836, 0x860, 0x88a, 0x8b5, 0x8e1, 0x90d, 0x939, + 0x966, 0x993, 0x9c1, 0x9f0, 0xa1e, 0xa4e, 0xa7d, 0xaad, 0xade, 0xb0f, + 0xb41, 0xb73, 0xba5, 0xbd8, 0xc0c, 0xc40, 0xc74, 0xca9, 0xcde, 0xd14, + 0xd4a, 0xd81, 0xdb8, 0xdf0, 0xe29, 0xe61, 0xe9a, 0xed4, 0xf0e, 0xf49, + 0xf84, 0xfc0, 0xffc, 0x1039, 0x1076, 0x10b4, 0x10f2, 0x1130, 0x116f, + 0x11af, 0x11ef, 0x1230, 0x1271, 0x12b2, 0x12f4, 0x1337, 0x137a, 0x13be, + 0x1402, 0x1446, 0x148b, 0x14d1, 0x1517, 0x155e, 0x15a5, 0x15ec, 0x1635, + 0x167d, 0x16c6, 0x1710, 0x175a, 0x17a5, 0x17f0, 0x183c, 0x1888, 0x18d5, + 0x1922, 0x196f, 0x19be, 0x1a0c, 0x1a5c, 0x1aab, 0x1afc, 0x1b4d, 0x1b9e, + 0x1bf0, 0x1c42, 0x1c95, 0x1ce8, 0x1d3c, 0x1d91, 0x1de6, 0x1e3b, 0x1e91, + 0x1ee8, 0x1f3f, 0x1f96, 0x1fef, 0x2047, 0x20a0, 0x20fa, 0x2154, 0x21af, + 0x220a, 0x2266, 0x22c2, 0x231f, 0x237d, 0x23db, 0x2439, 0x2498, 0x24f8, + 0x2558, 0x25b8, 0x2619, 0x267b, 0x26dd, 0x2740, 0x27a3, 0x2807, 0x286b, + 0x28d0, 0x2936, 0x299c, 0x2a02, 0x2a69, 0x2ad1, 0x2b39, 0x2ba1, 0x2c0b, + 0x2c74, 0x2cdf, 0x2d49, 0x2db5, 0x2e21, 0x2e8d, 0x2efa, 0x2f68, 0x2fd6, + 0x3044, 0x30b4, 0x3123, 0x3194, 0x3205, 0x3276, 0x32e8, 0x335a, 0x33cd, + 0x3441, 0x34b5, 0x352a, 0x359f, 0x3615, 0x368b, 0x3702, 0x377a, 0x37f2, + 0x386a, 0x38e3, 0x395d, 0x39d7, 0x3a52, 0x3acd, 0x3b49, 0x3bc6, 0x3c43, + 0x3cc0, 0x3d3f, 0x3dbd, 0x3e3d, 0x3ebc, 0x3f3d, 0x3fbe, 0x0, 0x7, 0xe, + 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, 0x40, 0x47, 0x4e, 0x55, 0x5c, 0x63, + 0x6a, 0x71, 0x78, 0x80, 0x87, 0x8e, 0x95, 0x9c, 0xa3, 0xaa, 0xb1, 0xb9, + 0xc0, 0xc7, 0xce, 0xd5, 0xdc, 0xe3, 0xea, 0xf1, 0xf9, 0x100, 0x107, + 0x10e, 0x115, 0x11c, 0x123, 0x12a, 0x131, 0x138, 0x13f, 0x147, 0x14e, + 0x156, 0x15e, 0x166, 0x16e, 0x176, 0x17e, 0x186, 0x18f, 0x197, 0x1a0, + 0x1a8, 0x1b1, 0x1ba, 0x1c3, 0x1cc, 0x1d5, 0x1de, 0x1e7, 0x1f1, 0x1fa, + 0x204, 0x20e, 0x217, 0x221, 0x22b, 0x236, 0x240, 0x24a, 0x255, 0x25f, + 0x26a, 0x274, 0x27f, 0x28a, 0x295, 0x2a0, 0x2ac, 0x2b7, 0x2c2, 0x2ce, + 0x2da, 0x2e5, 0x2f1, 0x2fd, 0x309, 0x315, 0x322, 0x32e, 0x33b, 0x347, + 0x354, 0x361, 0x36d, 0x37a, 0x388, 0x395, 0x3a2, 0x3b0, 0x3bd, 0x3cb, + 0x3d8, 0x3e6, 0x3f4, 0x402, 0x411, 0x41f, 0x42d, 0x43c, 0x44a, 0x459, + 0x468, 0x477, 0x486, 0x495, 0x4a4, 0x4b3, 0x4c3, 0x4d3, 0x4e2, 0x4f2, + 0x502, 0x512, 0x522, 0x532, 0x543, 0x553, 0x564, 0x574, 0x585, 0x596, + 0x5a7, 0x5b8, 0x5c9, 0x5db, 0x5ec, 0x5fe, 0x60f, 0x621, 0x633, 0x645, + 0x657, 0x669, 0x67b, 0x68e, 0x6a0, 0x6b3, 0x6c6, 0x6d9, 0x6ec, 0x6ff, + 0x712, 0x726, 0x739, 0x74d, 0x760, 0x774, 0x788, 0x79c, 0x7b0, 0x7c4, + 0x7d9, 0x7ed, 0x802, 0x816, 0x82b, 0x840, 0x855, 0x86a, 0x880, 0x895, + 0x8ab, 0x8c0, 0x8d6, 0x8ec, 0x902, 0x918, 0x92e, 0x944, 0x95b, 0x971, + 0x988, 0x99f, 0x9b6, 0x9cd, 0x9e4, 0x9fb, 0xa13, 0xa2a, 0xa42, 0xa59, + 0xa71, 0xa89, 0xaa1, 0xab9, 0xad2, 0xaea, 0xb03, 0xb1b, 0xb34, 0xb4d, + 0xb66, 0xb7f, 0xb98, 0xbb2, 0xbcb, 0xbe5, 0xbff, 0xc19, 0xc32, 0xc4d, + 0xc67, 0xc81, 0xc9c, 0xcb6, 0xcd1, 0xcec, 0xd07, 0xd22, 0xd3d, 0xd58, + 0xd73, 0xd8f, 0xdab, 0xdc6, 0xde2, 0xdfe, 0xe1a, 0xe37, 0xe53, 0xe70, + 0xe8c, 0xea9, 0xec6, 0xee3, 0xf00, 0xf1d, 0xf3a, 0xf58, 0xf75, 0xf93, + 0xfb1, 0xfcf, 0xfed, 0x100b, 0x102a, 0x1048, 0x1067, 0x1085, 0x10a4, + 0x10c3, 0x10e2, 0x1101, 0x1121, 0x1140, 0x1160, 0x117f, 0x119f, 0x11bf, + 0x11df, 0x11ff, 0x121f, 0x1240, 0x1260, 0x1281, 0x12a2, 0x12c3, 0x12e4, + 0x1305, 0x1326, 0x1348, 0x1369, 0x138b, 0x13ad, 0x13cf, 0x13f1, 0x1413, + 0x1435, 0x1458, 0x147a, 0x149d, 0x14c0, 0x14e3, 0x1506, 0x1529, 0x154c, + 0x156f, 0x1593, 0x15b7, 0x15db, 0x15fe, 0x1623, 0x1647, 0x166b, 0x168f, + 0x16b4, 0x16d9, 0x16fe, 0x1722, 0x1748, 0x176d, 0x1792, 0x17b8, 0x17dd, + 0x1803, 0x1829, 0x184f, 0x1875, 0x189b, 0x18c1, 0x18e8, 0x190e, 0x1935, + 0x195c, 0x1983, 0x19aa, 0x19d1, 0x19f9, 0x1a20, 0x1a48, 0x1a70, 0x1a97, + 0x1ac0, 0x1ae8, 0x1b10, 0x1b38, 0x1b61, 0x1b8a, 0x1bb2, 0x1bdb, 0x1c04, + 0x1c2e, 0x1c57, 0x1c80, 0x1caa, 0x1cd4, 0x1cfd, 0x1d27, 0x1d51, 0x1d7c, + 0x1da6, 0x1dd1, 0x1dfb, 0x1e26, 0x1e51, 0x1e7c, 0x1ea7, 0x1ed2, 0x1efe, + 0x1f29, 0x1f55, 0x1f81, 0x1fac, 0x1fd9, 0x2005, 0x2031, 0x205d, 0x208a, + 0x20b7, 0x20e4, 0x2111, 0x213e, 0x216b, 0x2198, 0x21c6, 0x21f3, 0x2221, + 0x224f, 0x227d, 0x22ab, 0x22da, 0x2308, 0x2337, 0x2365, 0x2394, 0x23c3, + 0x23f2, 0x2421, 0x2451, 0x2480, 0x24b0, 0x24e0, 0x2510, 0x2540, 0x2570, + 0x25a0, 0x25d0, 0x2601, 0x2632, 0x2663, 0x2693, 0x26c5, 0x26f6, 0x2727, + 0x2759, 0x278a, 0x27bc, 0x27ee, 0x2820, 0x2852, 0x2884, 0x28b7, 0x28e9, + 0x291c, 0x294f, 0x2982, 0x29b5, 0x29e8, 0x2a1c, 0x2a4f, 0x2a83, 0x2ab7, + 0x2aeb, 0x2b1f, 0x2b53, 0x2b87, 0x2bbc, 0x2bf0, 0x2c25, 0x2c5a, 0x2c8f, + 0x2cc4, 0x2cf9, 0x2d2f, 0x2d64, 0x2d9a, 0x2dd0, 0x2e06, 0x2e3c, 0x2e72, + 0x2ea8, 0x2edf, 0x2f16, 0x2f4c, 0x2f83, 0x2fba, 0x2ff1, 0x3029, 0x3060, + 0x3098, 0x30d0, 0x3107, 0x313f, 0x3178, 0x31b0, 0x31e8, 0x3221, 0x325a, + 0x3292, 0x32cb, 0x3304, 0x333e, 0x3377, 0x33b1, 0x33ea, 0x3424, 0x345e, + 0x3498, 0x34d2, 0x350d, 0x3547, 0x3582, 0x35bc, 0x35f7, 0x3632, 0x366e, + 0x36a9, 0x36e4, 0x3720, 0x375c, 0x3798, 0x37d4, 0x3810, 0x384c, 0x3888, + 0x38c5, 0x3902, 0x393f, 0x397c, 0x39b9, 0x39f6, 0x3a33, 0x3a71, 0x3aaf, + 0x3aec, 0x3b2a, 0x3b68, 0x3ba7, 0x3be5, 0x3c24, 0x3c62, 0x3ca1, 0x3ce0, + 0x3d1f, 0x3d5e, 0x3d9e, 0x3ddd, 0x3e1d, 0x3e5d, 0x3e9c, 0x3edc, 0x3f1d, + 0x3f5d, 0x3f9e, 0x3fde, 0x6}, + {0x798, 0x1ce, 0x20ac, 0x72, 0x413, 0xf47, 0x3fff, 0x1c, 0x103, 0x2d4, + 0x58e, 0xaaa, 0x1639, 0x3065, 0x3fff, 0x6, 0x40, 0xb3, 0x161, 0x24a, + 0x36c, 0x4c9, 0x679, 0x8f9, 0xcbc, 0x1266, 0x1aeb, 0x27bc, 0x3b05, + 0x3fff, 0x3fff, 0x1, 0xf, 0x2c, 0x57, 0x91, 0xd9, 0x130, 0x196, 0x20a, + 0x28d, 0x31e, 0x3be, 0x46c, 0x529, 0x5fe, 0x701, 0x840, 0x9c6, 0xba6, + 0xdf1, 0x10c2, 0x1437, 0x1873, 0x1da6, 0x2406, 0x2bd8, 0x3570, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0, 0x3, 0xa, 0x15, 0x23, 0x35, 0x4b, + 0x64, 0x81, 0xa2, 0xc6, 0xee, 0x119, 0x148, 0x17b, 0x1b2, 0x1ec, 0x229, + 0x26b, 0x2b0, 0x2f8, 0x345, 0x395, 0x3e8, 0x43f, 0x49a, 0x4f9, 0x55b, + 0x5c5, 0x63a, 0x6bb, 0x74b, 0x7ea, 0x89a, 0x95d, 0xa35, 0xb24, 0xc2d, + 0xd53, 0xe98, 0x1000, 0x118f, 0x1348, 0x1531, 0x174f, 0x19a7, 0x1c3f, + 0x1f1f, 0x224e, 0x25d5, 0x29bc, 0x2e10, 0x32da, 0x3828, 0x3e08, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0, + 0x0, 0x2, 0x5, 0x8, 0xd, 0x12, 0x18, 0x1f, 0x28, 0x31, 0x3a, 0x45, + 0x51, 0x5e, 0x6b, 0x7a, 0x89, 0x99, 0xaa, 0xbd, 0xd0, 0xe4, 0xf8, + 0x10e, 0x125, 0x13c, 0x155, 0x16e, 0x189, 0x1a4, 0x1c0, 0x1dd, 0x1fb, + 0x21a, 0x239, 0x25a, 0x27c, 0x29e, 0x2c2, 0x2e6, 0x30b, 0x331, 0x358, + 0x380, 0x3a9, 0x3d3, 0x3fe, 0x429, 0x456, 0x483, 0x4b2, 0x4e1, 0x511, + 0x542, 0x574, 0x5a9, 0x5e1, 0x61b, 0x659, 0x69a, 0x6de, 0x726, 0x771, + 0x7c1, 0x814, 0x86c, 0x8c9, 0x92b, 0x991, 0x9fd, 0xa6f, 0xae6, 0xb64, + 0xbe9, 0xc74, 0xd07, 0xda1, 0xe44, 0xeef, 0xfa3, 0x1060, 0x1127, + 0x11f9, 0x12d6, 0x13be, 0x14b2, 0x15b4, 0x16c2, 0x17df, 0x190b, 0x1a47, + 0x1b93, 0x1cf0, 0x1e60, 0x1fe3, 0x217b, 0x2327, 0x24ea, 0x26c5, 0x28b9, + 0x2ac7, 0x2cf0, 0x2f37, 0x319b, 0x3420, 0x36c7, 0x3992, 0x3c82, 0x3f99, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x7, 0x9, 0xb, 0xe, 0x11, 0x13, + 0x17, 0x1a, 0x1e, 0x21, 0x25, 0x2a, 0x2e, 0x33, 0x38, 0x3d, 0x42, 0x48, + 0x4e, 0x54, 0x5a, 0x61, 0x68, 0x6f, 0x76, 0x7d, 0x85, 0x8d, 0x95, 0x9d, + 0xa6, 0xaf, 0xb8, 0xc1, 0xcb, 0xd4, 0xde, 0xe9, 0xf3, 0xfe, 0x109, + 0x114, 0x11f, 0x12b, 0x136, 0x142, 0x14f, 0x15b, 0x168, 0x175, 0x182, + 0x18f, 0x19d, 0x1ab, 0x1b9, 0x1c7, 0x1d6, 0x1e4, 0x1f3, 0x202, 0x212, + 0x222, 0x231, 0x242, 0x252, 0x262, 0x273, 0x284, 0x296, 0x2a7, 0x2b9, + 0x2cb, 0x2dd, 0x2ef, 0x302, 0x315, 0x328, 0x33b, 0x34f, 0x362, 0x376, + 0x38a, 0x39f, 0x3b4, 0x3c8, 0x3de, 0x3f3, 0x409, 0x41e, 0x434, 0x44b, + 0x461, 0x478, 0x48f, 0x4a6, 0x4bd, 0x4d5, 0x4ed, 0x505, 0x51d, 0x536, + 0x54f, 0x568, 0x581, 0x59c, 0x5b7, 0x5d3, 0x5ef, 0x60c, 0x62a, 0x649, + 0x669, 0x689, 0x6aa, 0x6cd, 0x6f0, 0x713, 0x738, 0x75e, 0x785, 0x7ac, + 0x7d5, 0x7ff, 0x82a, 0x856, 0x883, 0x8b1, 0x8e1, 0x912, 0x944, 0x977, + 0x9ac, 0x9e2, 0xa19, 0xa52, 0xa8c, 0xac8, 0xb05, 0xb44, 0xb85, 0xbc7, + 0xc0b, 0xc51, 0xc98, 0xce1, 0xd2d, 0xd7a, 0xdc9, 0xe1a, 0xe6e, 0xec3, + 0xf1b, 0xf75, 0xfd1, 0x1030, 0x1091, 0x10f4, 0x115b, 0x11c3, 0x122f, + 0x129d, 0x130f, 0x1383, 0x13fa, 0x1474, 0x14f1, 0x1572, 0x15f6, 0x167d, + 0x1708, 0x1797, 0x1829, 0x18bf, 0x1959, 0x19f6, 0x1a98, 0x1b3e, 0x1be8, + 0x1c97, 0x1d4a, 0x1e02, 0x1ebf, 0x1f81, 0x2047, 0x2113, 0x21e4, 0x22ba, + 0x2396, 0x2477, 0x255f, 0x264c, 0x2740, 0x283a, 0x293a, 0x2a41, 0x2b4e, + 0x2c63, 0x2d7f, 0x2ea2, 0x2fcd, 0x30ff, 0x323a, 0x337c, 0x34c7, 0x361a, + 0x3777, 0x38dc, 0x3a4a, 0x3bc2, 0x3d44, 0x3ecf, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x2, 0x2, 0x3, 0x4, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xf, 0x10, 0x11, 0x13, 0x14, 0x16, 0x17, + 0x19, 0x1b, 0x1d, 0x1e, 0x20, 0x22, 0x24, 0x26, 0x29, 0x2b, 0x2d, 0x2f, + 0x32, 0x34, 0x37, 0x39, 0x3c, 0x3e, 0x41, 0x44, 0x47, 0x4a, 0x4c, 0x4f, + 0x53, 0x56, 0x59, 0x5c, 0x5f, 0x63, 0x66, 0x69, 0x6d, 0x70, 0x74, 0x78, + 0x7b, 0x7f, 0x83, 0x87, 0x8b, 0x8f, 0x93, 0x97, 0x9b, 0xa0, 0xa4, 0xa8, + 0xad, 0xb1, 0xb6, 0xba, 0xbf, 0xc4, 0xc8, 0xcd, 0xd2, 0xd7, 0xdc, 0xe1, + 0xe6, 0xeb, 0xf0, 0xf6, 0xfb, 0x100, 0x106, 0x10b, 0x111, 0x116, 0x11c, + 0x122, 0x128, 0x12d, 0x133, 0x139, 0x13f, 0x145, 0x14c, 0x152, 0x158, + 0x15e, 0x165, 0x16b, 0x171, 0x178, 0x17f, 0x185, 0x18c, 0x193, 0x199, + 0x1a0, 0x1a7, 0x1ae, 0x1b5, 0x1bc, 0x1c3, 0x1cb, 0x1d2, 0x1d9, 0x1e1, + 0x1e8, 0x1ef, 0x1f7, 0x1ff, 0x206, 0x20e, 0x216, 0x21e, 0x225, 0x22d, + 0x235, 0x23d, 0x246, 0x24e, 0x256, 0x25e, 0x267, 0x26f, 0x277, 0x280, + 0x289, 0x291, 0x29a, 0x2a3, 0x2ab, 0x2b4, 0x2bd, 0x2c6, 0x2cf, 0x2d8, + 0x2e1, 0x2eb, 0x2f4, 0x2fd, 0x306, 0x310, 0x319, 0x323, 0x32d, 0x336, + 0x340, 0x34a, 0x353, 0x35d, 0x367, 0x371, 0x37b, 0x385, 0x390, 0x39a, + 0x3a4, 0x3ae, 0x3b9, 0x3c3, 0x3ce, 0x3d8, 0x3e3, 0x3ee, 0x3f8, 0x403, + 0x40e, 0x419, 0x424, 0x42f, 0x43a, 0x445, 0x450, 0x45b, 0x467, 0x472, + 0x47e, 0x489, 0x495, 0x4a0, 0x4ac, 0x4b7, 0x4c3, 0x4cf, 0x4db, 0x4e7, + 0x4f3, 0x4ff, 0x50b, 0x517, 0x523, 0x530, 0x53c, 0x548, 0x555, 0x561, + 0x56e, 0x57b, 0x588, 0x595, 0x5a2, 0x5b0, 0x5be, 0x5cc, 0x5da, 0x5e8, + 0x5f6, 0x605, 0x614, 0x623, 0x632, 0x641, 0x651, 0x661, 0x671, 0x681, + 0x691, 0x6a2, 0x6b3, 0x6c4, 0x6d5, 0x6e7, 0x6f8, 0x70a, 0x71d, 0x72f, + 0x742, 0x754, 0x768, 0x77b, 0x78f, 0x7a2, 0x7b7, 0x7cb, 0x7e0, 0x7f4, + 0x80a, 0x81f, 0x835, 0x84b, 0x861, 0x878, 0x88f, 0x8a6, 0x8bd, 0x8d5, + 0x8ed, 0x905, 0x91e, 0x937, 0x950, 0x96a, 0x984, 0x99e, 0x9b9, 0x9d4, + 0x9ef, 0xa0b, 0xa27, 0xa43, 0xa60, 0xa7d, 0xa9b, 0xab9, 0xad7, 0xaf6, + 0xb15, 0xb34, 0xb54, 0xb74, 0xb95, 0xbb6, 0xbd8, 0xbfa, 0xc1c, 0xc3f, + 0xc62, 0xc86, 0xcaa, 0xccf, 0xcf4, 0xd1a, 0xd40, 0xd66, 0xd8d, 0xdb5, + 0xddd, 0xe06, 0xe2f, 0xe58, 0xe83, 0xead, 0xed9, 0xf05, 0xf31, 0xf5e, + 0xf8c, 0xfba, 0xfe8, 0x1018, 0x1048, 0x1078, 0x10a9, 0x10db, 0x110e, + 0x1141, 0x1175, 0x11a9, 0x11de, 0x1214, 0x124a, 0x1282, 0x12b9, 0x12f2, + 0x132b, 0x1365, 0x13a0, 0x13dc, 0x1418, 0x1455, 0x1493, 0x14d2, 0x1511, + 0x1552, 0x1593, 0x15d5, 0x1618, 0x165b, 0x16a0, 0x16e5, 0x172b, 0x1773, + 0x17bb, 0x1804, 0x184e, 0x1899, 0x18e5, 0x1932, 0x1980, 0x19ce, 0x1a1e, + 0x1a6f, 0x1ac1, 0x1b14, 0x1b68, 0x1bbe, 0x1c14, 0x1c6b, 0x1cc4, 0x1d1d, + 0x1d78, 0x1dd4, 0x1e31, 0x1e8f, 0x1eef, 0x1f50, 0x1fb2, 0x2015, 0x2079, + 0x20df, 0x2146, 0x21af, 0x2219, 0x2284, 0x22f0, 0x235e, 0x23ce, 0x243f, + 0x24b1, 0x2524, 0x259a, 0x2610, 0x2689, 0x2702, 0x277e, 0x27fa, 0x2879, + 0x28f9, 0x297b, 0x29fe, 0x2a84, 0x2b0a, 0x2b93, 0x2c1d, 0x2ca9, 0x2d37, + 0x2dc7, 0x2e59, 0x2eec, 0x2f81, 0x3019, 0x30b2, 0x314d, 0x31ea, 0x3289, + 0x332b, 0x33ce, 0x3473, 0x351b, 0x35c5, 0x3671, 0x371f, 0x37cf, 0x3882, + 0x3936, 0x39ee, 0x3aa7, 0x3b63, 0x3c21, 0x3ce2, 0x3da5, 0x3e6b, 0x3f34, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x6}, + {0x32a8, 0x2ea6, 0x361d, 0x2aa0, 0x30fc, 0x344a, 0x3808, 0x2696, + 0x2cf8, 0x3012, 0x3208, 0x3374, 0x3517, 0x36eb, 0x38d8, 0x2281, 0x28f0, + 0x2c0f, 0x2e06, 0x2f5a, 0x3082, 0x3181, 0x3256, 0x3305, 0x33f8, 0x34a8, + 0x359c, 0x367b, 0x3771, 0x3867, 0x395e, 0x1e58, 0x24e1, 0x2808, 0x2a01, + 0x2b54, 0x2c7e, 0x2d7d, 0x2e53, 0x2efd, 0x2fbd, 0x3049, 0x30be, 0x313d, + 0x31c8, 0x322e, 0x327e, 0x32d5, 0x333a, 0x33b3, 0x3421, 0x3477, 0x34dd, + 0x3557, 0x35e8, 0x364a, 0x36b1, 0x372b, 0x37bd, 0x3835, 0x389d, 0x3918, + 0x39ab, 0x1a0b, 0x20c2, 0x23f6, 0x25f1, 0x2748, 0x2877, 0x2974, 0x2a4e, + 0x2af8, 0x2bb6, 0x2c45, 0x2cba, 0x2d39, 0x2dc3, 0x2e2c, 0x2e7c, 0x2ed1, + 0x2f2b, 0x2f8b, 0x2ff0, 0x302d, 0x3065, 0x309f, 0x30dd, 0x311d, 0x315f, + 0x31a4, 0x31ec, 0x321b, 0x3242, 0x326a, 0x3293, 0x32be, 0x32ec, 0x331f, + 0x3356, 0x3393, 0x33d5, 0x340e, 0x3435, 0x3460, 0x348f, 0x34c2, 0x34fa, + 0x3536, 0x3579, 0x35c1, 0x3608, 0x3633, 0x3662, 0x3695, 0x36cd, 0x370b, + 0x374d, 0x3796, 0x37e6, 0x381e, 0x384d, 0x3881, 0x38ba, 0x38f7, 0x393a, + 0x3984, 0x39d4, 0x1500, 0x1c85, 0x1fc3, 0x21cc, 0x2331, 0x2468, 0x2563, + 0x2644, 0x26ec, 0x27aa, 0x283e, 0x28b2, 0x2931, 0x29bb, 0x2a27, 0x2a76, + 0x2acb, 0x2b25, 0x2b85, 0x2bea, 0x2c2a, 0x2c61, 0x2c9c, 0x2cd9, 0x2d18, + 0x2d5b, 0x2da0, 0x2de8, 0x2e19, 0x2e3f, 0x2e67, 0x2e90, 0x2ebb, 0x2ee7, + 0x2f14, 0x2f43, 0x2f72, 0x2fa4, 0x2fd6, 0x3005, 0x301f, 0x303b, 0x3057, + 0x3073, 0x3091, 0x30ae, 0x30cd, 0x30ec, 0x310c, 0x312d, 0x314e, 0x3170, + 0x3193, 0x31b6, 0x31da, 0x31ff, 0x3212, 0x3225, 0x3238, 0x324c, 0x3260, + 0x3274, 0x3289, 0x329e, 0x32b3, 0x32c9, 0x32e0, 0x32f9, 0x3312, 0x332c, + 0x3348, 0x3365, 0x3383, 0x33a3, 0x33c4, 0x33e6, 0x3405, 0x3418, 0x342b, + 0x3440, 0x3455, 0x346b, 0x3483, 0x349b, 0x34b5, 0x34cf, 0x34eb, 0x3508, + 0x3527, 0x3546, 0x3568, 0x358a, 0x35ae, 0x35d4, 0x35fc, 0x3612, 0x3628, + 0x363e, 0x3656, 0x366e, 0x3688, 0x36a3, 0x36bf, 0x36dc, 0x36fb, 0x371b, + 0x373c, 0x375f, 0x3783, 0x37aa, 0x37d1, 0x37fb, 0x3813, 0x382a, 0x3841, + 0x385a, 0x3874, 0x388f, 0x38ab, 0x38c8, 0x38e7, 0x3907, 0x3929, 0x394c, + 0x3971, 0x3997, 0x39bf, 0x39e9, 0xea8, 0x1815, 0x1b60, 0x1d86, 0x1f03, + 0x204c, 0x2142, 0x2231, 0x22d6, 0x2391, 0x2430, 0x24a3, 0x2521, 0x25a9, + 0x261e, 0x266c, 0x26c0, 0x271a, 0x2779, 0x27dd, 0x2823, 0x285a, 0x2894, + 0x28d1, 0x2910, 0x2952, 0x2997, 0x29df, 0x2a14, 0x2a3a, 0x2a62, 0x2a8b, + 0x2ab6, 0x2ae1, 0x2b0e, 0x2b3d, 0x2b6c, 0x2b9d, 0x2bd0, 0x2c02, 0x2c1c, + 0x2c37, 0x2c53, 0x2c70, 0x2c8d, 0x2cab, 0x2cc9, 0x2ce8, 0x2d08, 0x2d29, + 0x2d4a, 0x2d6c, 0x2d8e, 0x2db2, 0x2dd5, 0x2dfa, 0x2e0f, 0x2e22, 0x2e36, + 0x2e49, 0x2e5d, 0x2e71, 0x2e86, 0x2e9b, 0x2eb0, 0x2ec6, 0x2edc, 0x2ef2, + 0x2f09, 0x2f20, 0x2f37, 0x2f4e, 0x2f66, 0x2f7f, 0x2f97, 0x2fb0, 0x2fc9, + 0x2fe3, 0x2ffd, 0x300b, 0x3019, 0x3026, 0x3034, 0x3042, 0x3050, 0x305e, + 0x306c, 0x307b, 0x3089, 0x3098, 0x30a7, 0x30b6, 0x30c5, 0x30d5, 0x30e4, + 0x30f4, 0x3104, 0x3114, 0x3125, 0x3135, 0x3146, 0x3157, 0x3168, 0x3179, + 0x318a, 0x319c, 0x31ad, 0x31bf, 0x31d1, 0x31e3, 0x31f5, 0x3204, 0x320d, + 0x3216, 0x3220, 0x3229, 0x3233, 0x323d, 0x3247, 0x3251, 0x325b, 0x3265, + 0x326f, 0x3279, 0x3283, 0x328e, 0x3298, 0x32a3, 0x32ae, 0x32b8, 0x32c4, + 0x32cf, 0x32da, 0x32e6, 0x32f2, 0x32ff, 0x330c, 0x3318, 0x3326, 0x3333, + 0x3341, 0x334f, 0x335e, 0x336c, 0x337c, 0x338b, 0x339b, 0x33ab, 0x33bb, + 0x33cc, 0x33dd, 0x33ef, 0x3400, 0x3409, 0x3413, 0x341c, 0x3426, 0x3430, + 0x343a, 0x3445, 0x3450, 0x345b, 0x3466, 0x3471, 0x347d, 0x3489, 0x3495, + 0x34a2, 0x34ae, 0x34bb, 0x34c9, 0x34d6, 0x34e4, 0x34f2, 0x3501, 0x3510, + 0x351f, 0x352f, 0x353e, 0x354f, 0x355f, 0x3570, 0x3581, 0x3593, 0x35a5, + 0x35b8, 0x35cb, 0x35de, 0x35f2, 0x3603, 0x360d, 0x3618, 0x3622, 0x362d, + 0x3639, 0x3644, 0x3650, 0x365c, 0x3668, 0x3675, 0x3682, 0x368f, 0x369c, + 0x36aa, 0x36b8, 0x36c6, 0x36d5, 0x36e4, 0x36f3, 0x3703, 0x3713, 0x3723, + 0x3734, 0x3745, 0x3756, 0x3768, 0x377a, 0x378d, 0x37a0, 0x37b3, 0x37c7, + 0x37dc, 0x37f0, 0x3802, 0x380d, 0x3818, 0x3824, 0x382f, 0x383b, 0x3847, + 0x3854, 0x3860, 0x386d, 0x387a, 0x3888, 0x3896, 0x38a4, 0x38b2, 0x38c1, + 0x38d0, 0x38df, 0x38ef, 0x38ff, 0x3910, 0x3920, 0x3932, 0x3943, 0x3955, + 0x3967, 0x397a, 0x398d, 0x39a1, 0x39b5, 0x39c9, 0x39de, 0x39f4, 0x0, + 0x12aa, 0x16ab, 0x1900, 0x1aab, 0x1c15, 0x1d00, 0x1e0b, 0x1eab, 0x1f60, + 0x2015, 0x2085, 0x2100, 0x2186, 0x220b, 0x2258, 0x22ab, 0x2303, 0x2360, + 0x23c3, 0x2415, 0x244c, 0x2485, 0x24c2, 0x2500, 0x2542, 0x2586, 0x25cc, + 0x260b, 0x2631, 0x2658, 0x2681, 0x26ab, 0x26d6, 0x2703, 0x2731, 0x2760, + 0x2791, 0x27c3, 0x27f6, 0x2815, 0x2830, 0x284c, 0x2868, 0x2885, 0x28a3, + 0x28c2, 0x28e1, 0x2900, 0x2921, 0x2942, 0x2963, 0x2986, 0x29a9, 0x29cc, + 0x29f1, 0x2a0b, 0x2a1e, 0x2a31, 0x2a44, 0x2a58, 0x2a6c, 0x2a81, 0x2a96, + 0x2aab, 0x2ac0, 0x2ad6, 0x2aec, 0x2b03, 0x2b1a, 0x2b31, 0x2b48, 0x2b60, + 0x2b79, 0x2b91, 0x2baa, 0x2bc3, 0x2bdd, 0x2bf6, 0x2c08, 0x2c15, 0x2c23, + 0x2c30, 0x2c3e, 0x2c4c, 0x2c5a, 0x2c68, 0x2c77, 0x2c85, 0x2c94, 0x2ca3, + 0x2cb2, 0x2cc2, 0x2cd1, 0x2ce1, 0x2cf0, 0x2d00, 0x2d10, 0x2d21, 0x2d31, + 0x2d42, 0x2d52, 0x2d63, 0x2d74, 0x2d86, 0x2d97, 0x2da9, 0x2dbb, 0x2dcc, + 0x2ddf, 0x2df1, 0x2e01, 0x2e0b, 0x2e14, 0x2e1e, 0x2e27, 0x2e31, 0x2e3a, + 0x2e44, 0x2e4e, 0x2e58, 0x2e62, 0x2e6c, 0x2e76, 0x2e81, 0x2e8b, 0x2e96, + 0x2ea0, 0x2eab, 0x2eb6, 0x2ec0, 0x2ecb, 0x2ed6, 0x2ee1, 0x2eec, 0x2ef8, + 0x2f03, 0x2f0e, 0x2f1a, 0x2f25, 0x2f31, 0x2f3d, 0x2f48, 0x2f54, 0x2f60, + 0x2f6c, 0x2f79, 0x2f85, 0x2f91, 0x2f9d, 0x2faa, 0x2fb6, 0x2fc3, 0x2fd0, + 0x2fdd, 0x2fea, 0x2ff6, 0x3002, 0x3008, 0x300f, 0x3015, 0x301c, 0x3023, + 0x302a, 0x3030, 0x3037, 0x303e, 0x3045, 0x304c, 0x3053, 0x305a, 0x3061, + 0x3068, 0x3070, 0x3077, 0x307e, 0x3085, 0x308d, 0x3094, 0x309c, 0x30a3, + 0x30ab, 0x30b2, 0x30ba, 0x30c2, 0x30c9, 0x30d1, 0x30d9, 0x30e1, 0x30e8, + 0x30f0, 0x30f8, 0x3100, 0x3108, 0x3110, 0x3118, 0x3121, 0x3129, 0x3131, + 0x3139, 0x3142, 0x314a, 0x3152, 0x315b, 0x3163, 0x316c, 0x3174, 0x317d, + 0x3186, 0x318e, 0x3197, 0x31a0, 0x31a9, 0x31b2, 0x31bb, 0x31c3, 0x31cc, + 0x31d5, 0x31df, 0x31e8, 0x31f1, 0x31fa, 0x3201, 0x3206, 0x320b, 0x320f, + 0x3214, 0x3219, 0x321e, 0x3222, 0x3227, 0x322c, 0x3231, 0x3236, 0x323a, + 0x323f, 0x3244, 0x3249, 0x324e, 0x3253, 0x3258, 0x325d, 0x3262, 0x3267, + 0x326c, 0x3271, 0x3276, 0x327c, 0x3281, 0x3286, 0x328b, 0x3290, 0x3296, + 0x329b, 0x32a0, 0x32a6, 0x32ab, 0x32b0, 0x32b6, 0x32bb, 0x32c1, 0x32c6, + 0x32cc, 0x32d2, 0x32d8, 0x32dd, 0x32e3, 0x32e9, 0x32ef, 0x32f6, 0x32fc, + 0x3302, 0x3308, 0x330f, 0x3315, 0x331c, 0x3322, 0x3329, 0x3330, 0x3337, + 0x333e, 0x3345, 0x334c, 0x3353, 0x335a, 0x3361, 0x3369, 0x3370, 0x3378, + 0x337f, 0x3387, 0x338f, 0x3397, 0x339f, 0x33a7, 0x33af, 0x33b7, 0x33bf, + 0x33c8, 0x33d0, 0x33d9, 0x33e2, 0x33eb, 0x33f3, 0x33fc, 0x3402, 0x3407, + 0x340c, 0x3410, 0x3415, 0x341a, 0x341f, 0x3424, 0x3429, 0x342e, 0x3433, + 0x3438, 0x343d, 0x3442, 0x3448, 0x344d, 0x3452, 0x3458, 0x345d, 0x3463, + 0x3469, 0x346e, 0x3474, 0x347a, 0x3480, 0x3486, 0x348c, 0x3492, 0x3498, + 0x349e, 0x34a5, 0x34ab, 0x34b2, 0x34b8, 0x34bf, 0x34c5, 0x34cc, 0x34d3, + 0x34da, 0x34e1, 0x34e8, 0x34ef, 0x34f6, 0x34fd, 0x3505, 0x350c, 0x3514, + 0x351b, 0x3523, 0x352b, 0x3532, 0x353a, 0x3542, 0x354b, 0x3553, 0x355b, + 0x3563, 0x356c, 0x3574, 0x357d, 0x3586, 0x358f, 0x3598, 0x35a1, 0x35aa, + 0x35b3, 0x35bc, 0x35c6, 0x35cf, 0x35d9, 0x35e3, 0x35ed, 0x35f7, 0x3600, + 0x3605, 0x360a, 0x3610, 0x3615, 0x361a, 0x3620, 0x3625, 0x362b, 0x3630, + 0x3636, 0x363b, 0x3641, 0x3647, 0x364d, 0x3653, 0x3659, 0x365f, 0x3665, + 0x366b, 0x3672, 0x3678, 0x367e, 0x3685, 0x368b, 0x3692, 0x3699, 0x36a0, + 0x36a6, 0x36ad, 0x36b4, 0x36bb, 0x36c3, 0x36ca, 0x36d1, 0x36d9, 0x36e0, + 0x36e8, 0x36ef, 0x36f7, 0x36ff, 0x3707, 0x370f, 0x3717, 0x371f, 0x3727, + 0x372f, 0x3738, 0x3740, 0x3749, 0x3752, 0x375b, 0x3764, 0x376d, 0x3776, + 0x377f, 0x3788, 0x3792, 0x379b, 0x37a5, 0x37ae, 0x37b8, 0x37c2, 0x37cc, + 0x37d6, 0x37e1, 0x37eb, 0x37f6, 0x3800, 0x3805, 0x380b, 0x3810, 0x3816, + 0x381b, 0x3821, 0x3827, 0x382c, 0x3832, 0x3838, 0x383e, 0x3844, 0x384a, + 0x3851, 0x3857, 0x385d, 0x3864, 0x386a, 0x3870, 0x3877, 0x387e, 0x3885, + 0x388b, 0x3892, 0x3899, 0x38a0, 0x38a7, 0x38af, 0x38b6, 0x38bd, 0x38c5, + 0x38cc, 0x38d4, 0x38dc, 0x38e3, 0x38eb, 0x38f3, 0x38fb, 0x3903, 0x390c, + 0x3914, 0x391c, 0x3925, 0x392d, 0x3936, 0x393f, 0x3948, 0x3951, 0x395a, + 0x3963, 0x396c, 0x3975, 0x397f, 0x3989, 0x3992, 0x399c, 0x39a6, 0x39b0, + 0x39ba, 0x39c4, 0x39cf, 0x39d9, 0x39e4, 0x39ee, 0x39f9, 0xc}, + {0x1329, 0x468, 0x2f5a, 0x12e, 0xa44, 0x1f6d, 0x3fff, 0x65, 0x27f, + 0x6fb, 0xe50, 0x18da, 0x26ea, 0x38c5, 0x3fff, 0x2c, 0xba, 0x1c4, 0x35f, + 0x59c, 0x888, 0xc31, 0x10a2, 0x15e6, 0x1c07, 0x230e, 0x2b03, 0x33ef, + 0x3ddb, 0x3fff, 0x3fff, 0x15, 0x45, 0x8c, 0xf0, 0x175, 0x21d, 0x2ea, + 0x3df, 0x4fd, 0x646, 0x7bc, 0x960, 0xb35, 0xd3a, 0xf73, 0x11df, 0x1481, + 0x1759, 0x1a69, 0x1db3, 0x2136, 0x24f4, 0x28ef, 0x2d27, 0x319d, 0x3652, + 0x3b48, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0xa, 0x21, 0x38, 0x54, + 0x78, 0xa2, 0xd4, 0x10e, 0x150, 0x19b, 0x1ef, 0x24c, 0x2b3, 0x323, + 0x39e, 0x422, 0x4b1, 0x54b, 0x5ef, 0x69f, 0x75a, 0x821, 0x8f3, 0x9d1, + 0xabb, 0xbb1, 0xcb4, 0xdc3, 0xee0, 0x1009, 0x113f, 0x1282, 0x13d3, + 0x1532, 0x169e, 0x1818, 0x19a0, 0x1b36, 0x1cdb, 0x1e8e, 0x2050, 0x2220, + 0x23ff, 0x25ed, 0x27ea, 0x29f7, 0x2c13, 0x2e3e, 0x3079, 0x32c4, 0x351f, + 0x3789, 0x3a04, 0x3c8f, 0x3f2b, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x4, 0xf, 0x1b, 0x27, 0x32, 0x3f, 0x4d, + 0x5d, 0x6e, 0x81, 0x97, 0xae, 0xc7, 0xe2, 0xff, 0x11e, 0x13f, 0x162, + 0x188, 0x1b0, 0x1d9, 0x206, 0x234, 0x265, 0x298, 0x2ce, 0x306, 0x341, + 0x37e, 0x3be, 0x400, 0x445, 0x48c, 0x4d7, 0x523, 0x573, 0x5c5, 0x61a, + 0x672, 0x6cd, 0x72a, 0x78b, 0x7ee, 0x854, 0x8bd, 0x929, 0x998, 0xa0a, + 0xa7f, 0xaf7, 0xb73, 0xbf1, 0xc72, 0xcf7, 0xd7e, 0xe09, 0xe97, 0xf29, + 0xfbd, 0x1055, 0x10f0, 0x118f, 0x1230, 0x12d5, 0x137e, 0x142a, 0x14d9, + 0x158c, 0x1642, 0x16fb, 0x17b8, 0x1879, 0x193d, 0x1a04, 0x1acf, 0x1b9e, + 0x1c70, 0x1d46, 0x1e20, 0x1efd, 0x1fde, 0x20c2, 0x21aa, 0x2296, 0x2386, + 0x2479, 0x2570, 0x266b, 0x276a, 0x286c, 0x2972, 0x2a7c, 0x2b8a, 0x2c9c, + 0x2db2, 0x2ecc, 0x2fe9, 0x310b, 0x3230, 0x3359, 0x3487, 0x35b8, 0x36ed, + 0x3827, 0x3964, 0x3aa5, 0x3beb, 0x3d35, 0x3e82, 0x3fd4, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x1, 0x7, 0xd, + 0x12, 0x18, 0x1e, 0x24, 0x29, 0x2f, 0x35, 0x3b, 0x42, 0x49, 0x51, 0x58, + 0x61, 0x6a, 0x73, 0x7c, 0x87, 0x91, 0x9c, 0xa8, 0xb4, 0xc0, 0xcd, 0xdb, + 0xe9, 0xf7, 0x106, 0x116, 0x126, 0x137, 0x148, 0x159, 0x16b, 0x17e, + 0x192, 0x1a5, 0x1ba, 0x1cf, 0x1e4, 0x1fa, 0x211, 0x228, 0x240, 0x259, + 0x272, 0x28b, 0x2a6, 0x2c1, 0x2dc, 0x2f8, 0x315, 0x332, 0x350, 0x36f, + 0x38e, 0x3ae, 0x3ce, 0x3ef, 0x411, 0x434, 0x457, 0x47a, 0x49f, 0x4c4, + 0x4ea, 0x510, 0x537, 0x55f, 0x587, 0x5b0, 0x5da, 0x605, 0x630, 0x65c, + 0x689, 0x6b6, 0x6e4, 0x713, 0x742, 0x772, 0x7a3, 0x7d5, 0x807, 0x83a, + 0x86e, 0x8a3, 0x8d8, 0x90e, 0x945, 0x97c, 0x9b4, 0x9ed, 0xa27, 0xa62, + 0xa9d, 0xad9, 0xb16, 0xb53, 0xb92, 0xbd1, 0xc11, 0xc52, 0xc93, 0xcd5, + 0xd18, 0xd5c, 0xda1, 0xde6, 0xe2d, 0xe74, 0xebb, 0xf04, 0xf4e, 0xf98, + 0xfe3, 0x102f, 0x107c, 0x10c9, 0x1117, 0x1167, 0x11b7, 0x1208, 0x1259, + 0x12ac, 0x12ff, 0x1353, 0x13a8, 0x13fe, 0x1455, 0x14ad, 0x1505, 0x155f, + 0x15b9, 0x1614, 0x1670, 0x16cc, 0x172a, 0x1789, 0x17e8, 0x1848, 0x18a9, + 0x190b, 0x196e, 0x19d2, 0x1a37, 0x1a9c, 0x1b03, 0x1b6a, 0x1bd2, 0x1c3c, + 0x1ca6, 0x1d11, 0x1d7c, 0x1de9, 0x1e57, 0x1ec5, 0x1f35, 0x1fa5, 0x2017, + 0x2089, 0x20fc, 0x2170, 0x21e5, 0x225b, 0x22d2, 0x234a, 0x23c2, 0x243c, + 0x24b7, 0x2532, 0x25af, 0x262c, 0x26aa, 0x272a, 0x27aa, 0x282b, 0x28ad, + 0x2930, 0x29b5, 0x2a3a, 0x2ac0, 0x2b47, 0x2bcf, 0x2c57, 0x2ce1, 0x2d6c, + 0x2df8, 0x2e85, 0x2f13, 0x2fa1, 0x3031, 0x30c2, 0x3154, 0x31e6, 0x327a, + 0x330f, 0x33a4, 0x343b, 0x34d3, 0x356b, 0x3605, 0x36a0, 0x373b, 0x37d8, + 0x3876, 0x3914, 0x39b4, 0x3a55, 0x3af6, 0x3b99, 0x3c3d, 0x3ce2, 0x3d88, + 0x3e2e, 0x3ed6, 0x3f7f, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0, 0x2, 0x5, 0x8, 0xb, 0xe, 0x11, + 0x14, 0x17, 0x1a, 0x1c, 0x1f, 0x22, 0x25, 0x28, 0x2b, 0x2e, 0x31, 0x34, + 0x37, 0x3a, 0x3d, 0x40, 0x44, 0x47, 0x4b, 0x4f, 0x52, 0x56, 0x5b, 0x5f, + 0x63, 0x67, 0x6c, 0x70, 0x75, 0x7a, 0x7f, 0x84, 0x89, 0x8f, 0x94, 0x99, + 0x9f, 0xa5, 0xab, 0xb1, 0xb7, 0xbd, 0xc4, 0xca, 0xd1, 0xd7, 0xde, 0xe5, + 0xec, 0xf4, 0xfb, 0x103, 0x10a, 0x112, 0x11a, 0x122, 0x12a, 0x132, + 0x13b, 0x143, 0x14c, 0x155, 0x15e, 0x167, 0x170, 0x179, 0x183, 0x18d, + 0x196, 0x1a0, 0x1aa, 0x1b5, 0x1bf, 0x1c9, 0x1d4, 0x1df, 0x1ea, 0x1f5, + 0x200, 0x20b, 0x217, 0x223, 0x22e, 0x23a, 0x246, 0x253, 0x25f, 0x26b, + 0x278, 0x285, 0x292, 0x29f, 0x2ac, 0x2ba, 0x2c7, 0x2d5, 0x2e3, 0x2f1, + 0x2ff, 0x30e, 0x31c, 0x32b, 0x33a, 0x349, 0x358, 0x367, 0x376, 0x386, + 0x396, 0x3a6, 0x3b6, 0x3c6, 0x3d6, 0x3e7, 0x3f8, 0x409, 0x41a, 0x42b, + 0x43c, 0x44e, 0x45f, 0x471, 0x483, 0x496, 0x4a8, 0x4bb, 0x4cd, 0x4e0, + 0x4f3, 0x506, 0x51a, 0x52d, 0x541, 0x555, 0x569, 0x57d, 0x592, 0x5a6, + 0x5bb, 0x5d0, 0x5e5, 0x5fa, 0x610, 0x625, 0x63b, 0x651, 0x667, 0x67d, + 0x694, 0x6aa, 0x6c1, 0x6d8, 0x6f0, 0x707, 0x71e, 0x736, 0x74e, 0x766, + 0x77e, 0x797, 0x7af, 0x7c8, 0x7e1, 0x7fa, 0x814, 0x82d, 0x847, 0x861, + 0x87b, 0x895, 0x8b0, 0x8ca, 0x8e5, 0x900, 0x91b, 0x937, 0x952, 0x96e, + 0x98a, 0x9a6, 0x9c3, 0x9df, 0x9fc, 0xa19, 0xa36, 0xa53, 0xa70, 0xa8e, + 0xaac, 0xaca, 0xae8, 0xb07, 0xb25, 0xb44, 0xb63, 0xb82, 0xba2, 0xbc1, + 0xbe1, 0xc01, 0xc21, 0xc41, 0xc62, 0xc83, 0xca4, 0xcc5, 0xce6, 0xd08, + 0xd29, 0xd4b, 0xd6d, 0xd90, 0xdb2, 0xdd5, 0xdf8, 0xe1b, 0xe3e, 0xe62, + 0xe85, 0xea9, 0xece, 0xef2, 0xf16, 0xf3b, 0xf60, 0xf85, 0xfab, 0xfd0, + 0xff6, 0x101c, 0x1042, 0x1068, 0x108f, 0x10b6, 0x10dd, 0x1104, 0x112b, + 0x1153, 0x117b, 0x11a3, 0x11cb, 0x11f3, 0x121c, 0x1245, 0x126e, 0x1297, + 0x12c1, 0x12ea, 0x1314, 0x133e, 0x1369, 0x1393, 0x13be, 0x13e9, 0x1414, + 0x143f, 0x146b, 0x1497, 0x14c3, 0x14ef, 0x151b, 0x1548, 0x1575, 0x15a2, + 0x15cf, 0x15fd, 0x162b, 0x1659, 0x1687, 0x16b5, 0x16e4, 0x1713, 0x1742, + 0x1771, 0x17a0, 0x17d0, 0x1800, 0x1830, 0x1860, 0x1891, 0x18c2, 0x18f3, + 0x1924, 0x1956, 0x1987, 0x19b9, 0x19eb, 0x1a1e, 0x1a50, 0x1a83, 0x1ab6, + 0x1ae9, 0x1b1d, 0x1b50, 0x1b84, 0x1bb8, 0x1bed, 0x1c21, 0x1c56, 0x1c8b, + 0x1cc0, 0x1cf6, 0x1d2b, 0x1d61, 0x1d97, 0x1dce, 0x1e04, 0x1e3b, 0x1e72, + 0x1eaa, 0x1ee1, 0x1f19, 0x1f51, 0x1f89, 0x1fc1, 0x1ffa, 0x2033, 0x206c, + 0x20a5, 0x20df, 0x2119, 0x2153, 0x218d, 0x21c8, 0x2202, 0x223d, 0x2279, + 0x22b4, 0x22f0, 0x232c, 0x2368, 0x23a4, 0x23e1, 0x241d, 0x245a, 0x2498, + 0x24d5, 0x2513, 0x2551, 0x258f, 0x25ce, 0x260d, 0x264b, 0x268b, 0x26ca, + 0x270a, 0x274a, 0x278a, 0x27ca, 0x280b, 0x284c, 0x288d, 0x28ce, 0x2910, + 0x2951, 0x2993, 0x29d6, 0x2a18, 0x2a5b, 0x2a9e, 0x2ae1, 0x2b25, 0x2b68, + 0x2bac, 0x2bf1, 0x2c35, 0x2c7a, 0x2cbf, 0x2d04, 0x2d49, 0x2d8f, 0x2dd5, + 0x2e1b, 0x2e62, 0x2ea8, 0x2eef, 0x2f36, 0x2f7e, 0x2fc5, 0x300d, 0x3055, + 0x309e, 0x30e6, 0x312f, 0x3178, 0x31c1, 0x320b, 0x3255, 0x329f, 0x32e9, + 0x3334, 0x337f, 0x33ca, 0x3415, 0x3461, 0x34ad, 0x34f9, 0x3545, 0x3592, + 0x35de, 0x362b, 0x3679, 0x36c6, 0x3714, 0x3762, 0x37b1, 0x37ff, 0x384e, + 0x389d, 0x38ed, 0x393c, 0x398c, 0x39dc, 0x3a2c, 0x3a7d, 0x3ace, 0x3b1f, + 0x3b70, 0x3bc2, 0x3c14, 0x3c66, 0x3cb8, 0x3d0b, 0x3d5e, 0x3db1, 0x3e05, + 0x3e58, 0x3eac, 0x3f00, 0x3f55, 0x3faa, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x6}, + {0xdab, 0x33c, 0x216a, 0xe8, 0x766, 0x1646, 0x2f44, 0x52, 0x1dc, 0x512, + 0xa41, 0x11aa, 0x1b84, 0x27fd, 0x3743, 0x26, 0x92, 0x155, 0x27e, 0x418, + 0x62b, 0x8c2, 0xbe4, 0xf97, 0x13e4, 0x18d0, 0x1e61, 0x249e, 0x2b8a, + 0x332c, 0x3b89, 0x12, 0x3a, 0x70, 0xba, 0x11c, 0x196, 0x22a, 0x2da, + 0x3a6, 0x491, 0x59a, 0x6c4, 0x810, 0x97d, 0xb0e, 0xcc3, 0xe9c, 0x109c, + 0x12c2, 0x1510, 0x1786, 0x1a25, 0x1ced, 0x1fe0, 0x22fe, 0x2648, 0x29be, + 0x2d61, 0x3132, 0x3532, 0x3960, 0x3dbe, 0x8, 0x1c, 0x30, 0x46, 0x60, + 0x80, 0xa5, 0xd0, 0x101, 0x138, 0x175, 0x1b8, 0x202, 0x253, 0x2ab, + 0x30a, 0x370, 0x3de, 0x453, 0x4d0, 0x555, 0x5e2, 0x677, 0x714, 0x7ba, + 0x868, 0x91e, 0x9de, 0xaa6, 0xb77, 0xc52, 0xd35, 0xe22, 0xf19, 0x1018, + 0x1122, 0x1235, 0x1352, 0x1479, 0x15aa, 0x16e4, 0x182a, 0x1979, 0x1ad3, + 0x1c37, 0x1da6, 0x1f1f, 0x20a4, 0x2233, 0x23cc, 0x2571, 0x2721, 0x28dc, + 0x2aa3, 0x2c74, 0x2e51, 0x303a, 0x322e, 0x342e, 0x3639, 0x3850, 0x3a73, + 0x3ca2, 0x3edd, 0x3, 0xd, 0x17, 0x21, 0x2b, 0x35, 0x40, 0x4c, 0x59, + 0x68, 0x78, 0x89, 0x9c, 0xb0, 0xc5, 0xdc, 0xf4, 0x10e, 0x12a, 0x146, + 0x165, 0x185, 0x1a7, 0x1ca, 0x1ef, 0x216, 0x23e, 0x269, 0x294, 0x2c2, + 0x2f2, 0x323, 0x356, 0x38b, 0x3c2, 0x3fb, 0x435, 0x472, 0x4b0, 0x4f1, + 0x533, 0x577, 0x5be, 0x606, 0x651, 0x69d, 0x6ec, 0x73d, 0x78f, 0x7e4, + 0x83b, 0x895, 0x8f0, 0x94d, 0x9ad, 0xa0f, 0xa73, 0xada, 0xb42, 0xbad, + 0xc1a, 0xc8a, 0xcfc, 0xd70, 0xde6, 0xe5f, 0xeda, 0xf58, 0xfd8, 0x105a, + 0x10df, 0x1166, 0x11ef, 0x127b, 0x130a, 0x139b, 0x142e, 0x14c4, 0x155c, + 0x15f7, 0x1695, 0x1735, 0x17d7, 0x187d, 0x1924, 0x19cf, 0x1a7b, 0x1b2b, + 0x1bdd, 0x1c92, 0x1d49, 0x1e03, 0x1ec0, 0x1f7f, 0x2042, 0x2106, 0x21ce, + 0x2298, 0x2365, 0x2435, 0x2507, 0x25dc, 0x26b4, 0x278f, 0x286d, 0x294d, + 0x2a30, 0x2b16, 0x2bff, 0x2ceb, 0x2dd9, 0x2eca, 0x2fbf, 0x30b6, 0x31b0, + 0x32ad, 0x33ac, 0x34af, 0x35b5, 0x36bd, 0x37c9, 0x38d8, 0x39e9, 0x3afd, + 0x3c15, 0x3d2f, 0x3e4d, 0x3f6d, 0x1, 0x6, 0xb, 0x10, 0x15, 0x1a, 0x1e, + 0x23, 0x28, 0x2d, 0x32, 0x37, 0x3d, 0x43, 0x49, 0x4f, 0x56, 0x5d, 0x64, + 0x6c, 0x74, 0x7c, 0x85, 0x8e, 0x97, 0xa0, 0xaa, 0xb5, 0xc0, 0xcb, 0xd6, + 0xe2, 0xee, 0xfb, 0x108, 0x115, 0x123, 0x131, 0x13f, 0x14e, 0x15d, + 0x16d, 0x17d, 0x18d, 0x19e, 0x1af, 0x1c1, 0x1d3, 0x1e6, 0x1f9, 0x20c, + 0x220, 0x234, 0x249, 0x25e, 0x273, 0x289, 0x2a0, 0x2b7, 0x2ce, 0x2e6, + 0x2fe, 0x316, 0x32f, 0x349, 0x363, 0x37e, 0x398, 0x3b4, 0x3d0, 0x3ec, + 0x409, 0x426, 0x444, 0x462, 0x481, 0x4a0, 0x4c0, 0x4e0, 0x501, 0x522, + 0x544, 0x566, 0x589, 0x5ac, 0x5d0, 0x5f4, 0x619, 0x63e, 0x664, 0x68a, + 0x6b1, 0x6d8, 0x700, 0x728, 0x751, 0x77a, 0x7a4, 0x7cf, 0x7fa, 0x825, + 0x851, 0x87e, 0x8ab, 0x8d9, 0x907, 0x936, 0x965, 0x995, 0x9c5, 0x9f6, + 0xa28, 0xa5a, 0xa8d, 0xac0, 0xaf4, 0xb28, 0xb5d, 0xb92, 0xbc8, 0xbff, + 0xc36, 0xc6e, 0xca6, 0xcdf, 0xd19, 0xd53, 0xd8d, 0xdc8, 0xe04, 0xe41, + 0xe7e, 0xebb, 0xef9, 0xf38, 0xf77, 0xfb7, 0xff8, 0x1039, 0x107b, + 0x10bd, 0x1100, 0x1144, 0x1188, 0x11cd, 0x1212, 0x1258, 0x129f, 0x12e6, + 0x132e, 0x1376, 0x13bf, 0x1409, 0x1453, 0x149e, 0x14ea, 0x1536, 0x1583, + 0x15d0, 0x161e, 0x166d, 0x16bd, 0x170d, 0x175d, 0x17af, 0x1800, 0x1853, + 0x18a6, 0x18fa, 0x194f, 0x19a4, 0x19fa, 0x1a50, 0x1aa7, 0x1aff, 0x1b57, + 0x1bb0, 0x1c0a, 0x1c64, 0x1cbf, 0x1d1b, 0x1d78, 0x1dd5, 0x1e32, 0x1e91, + 0x1ef0, 0x1f4f, 0x1fb0, 0x2011, 0x2072, 0x20d5, 0x2138, 0x219c, 0x2200, + 0x2265, 0x22cb, 0x2331, 0x2399, 0x2401, 0x2469, 0x24d2, 0x253c, 0x25a7, + 0x2612, 0x267e, 0x26eb, 0x2758, 0x27c6, 0x2835, 0x28a4, 0x2915, 0x2985, + 0x29f7, 0x2a69, 0x2adc, 0x2b50, 0x2bc4, 0x2c3a, 0x2caf, 0x2d26, 0x2d9d, + 0x2e15, 0x2e8e, 0x2f07, 0x2f81, 0x2ffc, 0x3078, 0x30f4, 0x3171, 0x31ef, + 0x326d, 0x32ec, 0x336c, 0x33ed, 0x346e, 0x34f0, 0x3573, 0x35f7, 0x367b, + 0x3700, 0x3786, 0x380c, 0x3894, 0x391c, 0x39a4, 0x3a2e, 0x3ab8, 0x3b43, + 0x3bcf, 0x3c5b, 0x3ce8, 0x3d76, 0x3e05, 0x3e95, 0x3f25, 0x3fb6, 0x0, + 0x2, 0x4, 0x7, 0x9, 0xc, 0xe, 0x11, 0x13, 0x16, 0x18, 0x1b, 0x1d, 0x20, + 0x22, 0x25, 0x27, 0x2a, 0x2c, 0x2f, 0x31, 0x34, 0x36, 0x39, 0x3c, 0x3e, + 0x41, 0x44, 0x47, 0x4a, 0x4d, 0x51, 0x54, 0x57, 0x5b, 0x5f, 0x62, 0x66, + 0x6a, 0x6e, 0x72, 0x76, 0x7a, 0x7e, 0x82, 0x87, 0x8b, 0x90, 0x94, 0x99, + 0x9e, 0xa3, 0xa8, 0xad, 0xb2, 0xb7, 0xbd, 0xc2, 0xc8, 0xcd, 0xd3, 0xd9, + 0xdf, 0xe5, 0xeb, 0xf1, 0xf7, 0xfe, 0x104, 0x10b, 0x111, 0x118, 0x11f, + 0x126, 0x12d, 0x134, 0x13b, 0x143, 0x14a, 0x152, 0x159, 0x161, 0x169, + 0x171, 0x179, 0x181, 0x189, 0x192, 0x19a, 0x1a2, 0x1ab, 0x1b4, 0x1bd, + 0x1c6, 0x1cf, 0x1d8, 0x1e1, 0x1ea, 0x1f4, 0x1fe, 0x207, 0x211, 0x21b, + 0x225, 0x22f, 0x239, 0x244, 0x24e, 0x258, 0x263, 0x26e, 0x279, 0x284, + 0x28f, 0x29a, 0x2a5, 0x2b1, 0x2bc, 0x2c8, 0x2d4, 0x2e0, 0x2ec, 0x2f8, + 0x304, 0x310, 0x31d, 0x329, 0x336, 0x343, 0x350, 0x35d, 0x36a, 0x377, + 0x384, 0x392, 0x39f, 0x3ad, 0x3bb, 0x3c9, 0x3d7, 0x3e5, 0x3f3, 0x402, + 0x410, 0x41f, 0x42e, 0x43d, 0x44c, 0x45b, 0x46a, 0x479, 0x489, 0x499, + 0x4a8, 0x4b8, 0x4c8, 0x4d8, 0x4e8, 0x4f9, 0x509, 0x51a, 0x52b, 0x53b, + 0x54c, 0x55e, 0x56f, 0x580, 0x592, 0x5a3, 0x5b5, 0x5c7, 0x5d9, 0x5eb, + 0x5fd, 0x60f, 0x622, 0x635, 0x647, 0x65a, 0x66d, 0x680, 0x694, 0x6a7, + 0x6bb, 0x6ce, 0x6e2, 0x6f6, 0x70a, 0x71e, 0x732, 0x747, 0x75b, 0x770, + 0x785, 0x79a, 0x7af, 0x7c4, 0x7da, 0x7ef, 0x805, 0x81a, 0x830, 0x846, + 0x85d, 0x873, 0x889, 0x8a0, 0x8b7, 0x8cd, 0x8e4, 0x8fb, 0x913, 0x92a, + 0x942, 0x959, 0x971, 0x989, 0x9a1, 0x9b9, 0x9d2, 0x9ea, 0xa03, 0xa1c, + 0xa34, 0xa4d, 0xa67, 0xa80, 0xa99, 0xab3, 0xacd, 0xae7, 0xb01, 0xb1b, + 0xb35, 0xb50, 0xb6a, 0xb85, 0xba0, 0xbbb, 0xbd6, 0xbf1, 0xc0d, 0xc28, + 0xc44, 0xc60, 0xc7c, 0xc98, 0xcb4, 0xcd1, 0xced, 0xd0a, 0xd27, 0xd44, + 0xd61, 0xd7e, 0xd9c, 0xdba, 0xdd7, 0xdf5, 0xe13, 0xe31, 0xe50, 0xe6e, + 0xe8d, 0xeac, 0xecb, 0xeea, 0xf09, 0xf28, 0xf48, 0xf68, 0xf87, 0xfa7, + 0xfc7, 0xfe8, 0x1008, 0x1029, 0x1049, 0x106a, 0x108b, 0x10ad, 0x10ce, + 0x10ef, 0x1111, 0x1133, 0x1155, 0x1177, 0x1199, 0x11bb, 0x11de, 0x1201, + 0x1223, 0x1246, 0x126a, 0x128d, 0x12b0, 0x12d4, 0x12f8, 0x131c, 0x1340, + 0x1364, 0x1388, 0x13ad, 0x13d2, 0x13f6, 0x141c, 0x1441, 0x1466, 0x148b, + 0x14b1, 0x14d7, 0x14fd, 0x1523, 0x1549, 0x1570, 0x1596, 0x15bd, 0x15e4, + 0x160b, 0x1632, 0x1659, 0x1681, 0x16a9, 0x16d1, 0x16f9, 0x1721, 0x1749, + 0x1771, 0x179a, 0x17c3, 0x17ec, 0x1815, 0x183e, 0x1868, 0x1891, 0x18bb, + 0x18e5, 0x190f, 0x1939, 0x1964, 0x198e, 0x19b9, 0x19e4, 0x1a0f, 0x1a3a, + 0x1a66, 0x1a91, 0x1abd, 0x1ae9, 0x1b15, 0x1b41, 0x1b6d, 0x1b9a, 0x1bc7, + 0x1bf4, 0x1c21, 0x1c4e, 0x1c7b, 0x1ca9, 0x1cd6, 0x1d04, 0x1d32, 0x1d60, + 0x1d8f, 0x1dbd, 0x1dec, 0x1e1b, 0x1e4a, 0x1e79, 0x1ea8, 0x1ed8, 0x1f07, + 0x1f37, 0x1f67, 0x1f98, 0x1fc8, 0x1ff8, 0x2029, 0x205a, 0x208b, 0x20bc, + 0x20ee, 0x211f, 0x2151, 0x2183, 0x21b5, 0x21e7, 0x2219, 0x224c, 0x227f, + 0x22b2, 0x22e5, 0x2318, 0x234b, 0x237f, 0x23b3, 0x23e6, 0x241b, 0x244f, + 0x2483, 0x24b8, 0x24ed, 0x2522, 0x2557, 0x258c, 0x25c2, 0x25f7, 0x262d, + 0x2663, 0x2699, 0x26cf, 0x2706, 0x273d, 0x2774, 0x27ab, 0x27e2, 0x2819, + 0x2851, 0x2888, 0x28c0, 0x28f8, 0x2931, 0x2969, 0x29a2, 0x29db, 0x2a14, + 0x2a4d, 0x2a86, 0x2ac0, 0x2af9, 0x2b33, 0x2b6d, 0x2ba7, 0x2be2, 0x2c1c, + 0x2c57, 0x2c92, 0x2ccd, 0x2d08, 0x2d44, 0x2d7f, 0x2dbb, 0x2df7, 0x2e33, + 0x2e70, 0x2eac, 0x2ee9, 0x2f26, 0x2f63, 0x2fa0, 0x2fdd, 0x301b, 0x3059, + 0x3097, 0x30d5, 0x3113, 0x3152, 0x3190, 0x31cf, 0x320e, 0x324e, 0x328d, + 0x32cd, 0x330c, 0x334c, 0x338c, 0x33cd, 0x340d, 0x344e, 0x348f, 0x34d0, + 0x3511, 0x3552, 0x3594, 0x35d6, 0x3618, 0x365a, 0x369c, 0x36df, 0x3721, + 0x3764, 0x37a7, 0x37eb, 0x382e, 0x3872, 0x38b6, 0x38fa, 0x393e, 0x3982, + 0x39c7, 0x3a0b, 0x3a50, 0x3a95, 0x3adb, 0x3b20, 0x3b66, 0x3bac, 0x3bf2, + 0x3c38, 0x3c7e, 0x3cc5, 0x3d0c, 0x3d53, 0x3d9a, 0x3de1, 0x3e29, 0x3e71, + 0x3eb9, 0x3f01, 0x3f49, 0x3f91, 0x3fda, 0x6}, + {0x2e59, 0x255e, 0x3614, 0x1ec7, 0x2a3b, 0x323d, 0x39f4, 0x197e, + 0x2273, 0x2805, 0x2c55, 0x304f, 0x3428, 0x3803, 0x39ff, 0x14cc, 0x1c7b, + 0x20b8, 0x2413, 0x26a5, 0x2907, 0x2b3e, 0x2d52, 0x2f4d, 0x3139, 0x331c, + 0x34fe, 0x36e4, 0x38d1, 0x39ff, 0x39ff, 0x1090, 0x1781, 0x1b11, 0x1dca, + 0x1ff0, 0x21af, 0x2335, 0x24a8, 0x261e, 0x2747, 0x287b, 0x29af, 0x2ab2, + 0x2be5, 0x2cc9, 0x2df4, 0x2ec9, 0x2fe8, 0x30ba, 0x31cd, 0x32a4, 0x33aa, + 0x348a, 0x3587, 0x3673, 0x3769, 0x3861, 0x3955, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0xc70, 0x1300, 0x164d, 0x1886, 0x1a57, 0x1bef, 0x1d16, 0x1e4e, + 0x1f52, 0x2052, 0x212c, 0x2220, 0x22ce, 0x23a7, 0x245a, 0x24fe, 0x25c8, + 0x265e, 0x26f2, 0x27a5, 0x283d, 0x28be, 0x2958, 0x2a07, 0x2a74, 0x2af5, + 0x2b8e, 0x2c21, 0x2c8c, 0x2d0b, 0x2da0, 0x2e28, 0x2e8f, 0x2f09, 0x2f98, + 0x3020, 0x3083, 0x30f7, 0x3180, 0x3210, 0x326e, 0x32dd, 0x3360, 0x33fa, + 0x3457, 0x34c2, 0x3540, 0x35d4, 0x3641, 0x36a9, 0x3724, 0x37b5, 0x3830, + 0x3897, 0x3910, 0x39a1, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x800, 0xed0, 0x120a, 0x1416, 0x15a2, 0x16dd, 0x181d, + 0x18fb, 0x1a07, 0x1ab0, 0x1b7b, 0x1c36, 0x1cc5, 0x1d6d, 0x1e17, 0x1e88, + 0x1f0a, 0x1f9e, 0x2024, 0x2084, 0x20f1, 0x216b, 0x21f6, 0x2248, 0x229f, + 0x2300, 0x236d, 0x23e5, 0x2435, 0x2480, 0x24d2, 0x252d, 0x2592, 0x2600, + 0x263d, 0x2680, 0x26ca, 0x271c, 0x2775, 0x27d7, 0x2821, 0x285b, 0x289c, + 0x28e2, 0x292f, 0x2982, 0x29de, 0x2a21, 0x2a57, 0x2a92, 0x2ad3, 0x2b19, + 0x2b65, 0x2bb9, 0x2c09, 0x2c3b, 0x2c70, 0x2caa, 0x2ce9, 0x2d2e, 0x2d78, + 0x2dc9, 0x2e10, 0x2e40, 0x2e73, 0x2eab, 0x2ee8, 0x2f2a, 0x2f72, 0x2fbf, + 0x3009, 0x3037, 0x3068, 0x309e, 0x30d8, 0x3117, 0x315b, 0x31a5, 0x31f6, + 0x3226, 0x3255, 0x3288, 0x32c0, 0x32fc, 0x333d, 0x3384, 0x33d1, 0x3412, + 0x343f, 0x3470, 0x34a6, 0x34df, 0x351e, 0x3562, 0x35ad, 0x35fd, 0x362a, + 0x365a, 0x368d, 0x36c6, 0x3703, 0x3746, 0x378e, 0x37dd, 0x3819, 0x3848, + 0x387b, 0x38b3, 0x38f0, 0x3932, 0x397a, 0x39c9, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x200, 0xaa0, 0xde0, 0xfe0, 0x1144, + 0x127e, 0x138e, 0x146d, 0x1532, 0x160d, 0x1693, 0x172c, 0x17db, 0x1850, + 0x18bf, 0x193b, 0x19c5, 0x1a2e, 0x1a83, 0x1adf, 0x1b45, 0x1bb4, 0x1c16, + 0x1c58, 0x1c9f, 0x1ced, 0x1d40, 0x1d9b, 0x1dfc, 0x1e32, 0x1e6a, 0x1ea7, + 0x1ee8, 0x1f2d, 0x1f77, 0x1fc6, 0x200d, 0x203a, 0x206a, 0x209e, 0x20d4, + 0x210e, 0x214b, 0x218d, 0x21d2, 0x220d, 0x2234, 0x225d, 0x2289, 0x22b6, + 0x22e7, 0x231a, 0x2350, 0x238a, 0x23c6, 0x2402, 0x2424, 0x2447, 0x246c, + 0x2493, 0x24bd, 0x24e8, 0x2515, 0x2545, 0x2578, 0x25ac, 0x25e4, 0x260f, + 0x262d, 0x264d, 0x266f, 0x2692, 0x26b7, 0x26de, 0x2707, 0x2731, 0x275e, + 0x278d, 0x27bd, 0x27f1, 0x2813, 0x282f, 0x284c, 0x286b, 0x288b, 0x28ad, + 0x28d0, 0x28f4, 0x291b, 0x2943, 0x296d, 0x2998, 0x29c6, 0x29f6, 0x2a14, + 0x2a2e, 0x2a49, 0x2a65, 0x2a83, 0x2aa2, 0x2ac2, 0x2ae4, 0x2b07, 0x2b2b, + 0x2b52, 0x2b7a, 0x2ba3, 0x2bcf, 0x2bfc, 0x2c15, 0x2c2e, 0x2c48, 0x2c62, + 0x2c7e, 0x2c9b, 0x2cb9, 0x2cd9, 0x2cfa, 0x2d1c, 0x2d40, 0x2d65, 0x2d8c, + 0x2db4, 0x2dde, 0x2e05, 0x2e1c, 0x2e34, 0x2e4c, 0x2e66, 0x2e81, 0x2e9d, + 0x2eba, 0x2ed9, 0x2ef8, 0x2f19, 0x2f3c, 0x2f5f, 0x2f85, 0x2fab, 0x2fd4, + 0x2ffe, 0x3014, 0x302b, 0x3043, 0x305c, 0x3075, 0x3090, 0x30ac, 0x30c9, + 0x30e7, 0x3107, 0x3128, 0x314a, 0x316d, 0x3192, 0x31b9, 0x31e1, 0x3205, + 0x321b, 0x3232, 0x3249, 0x3262, 0x327b, 0x3296, 0x32b2, 0x32cf, 0x32ed, + 0x330c, 0x332d, 0x334f, 0x3372, 0x3397, 0x33bd, 0x33e5, 0x3407, 0x341d, + 0x3434, 0x344b, 0x3464, 0x347d, 0x3498, 0x34b4, 0x34d1, 0x34ef, 0x350e, + 0x352f, 0x3551, 0x3574, 0x359a, 0x35c0, 0x35e8, 0x3609, 0x361f, 0x3636, + 0x364d, 0x3666, 0x3680, 0x369b, 0x36b7, 0x36d5, 0x36f3, 0x3713, 0x3734, + 0x3757, 0x377b, 0x37a1, 0x37c9, 0x37f2, 0x380e, 0x3825, 0x383c, 0x3855, + 0x386e, 0x3889, 0x38a5, 0x38c2, 0x38e0, 0x3900, 0x3921, 0x3944, 0x3968, + 0x398e, 0x39b5, 0x39de, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x0, + 0x600, 0x980, 0xba0, 0xd20, 0xe58, 0xf58, 0x103c, 0x10e8, 0x11a8, + 0x1242, 0x12bc, 0x1346, 0x13dc, 0x1441, 0x149b, 0x14fe, 0x1569, 0x15dd, + 0x162c, 0x166f, 0x16b7, 0x1704, 0x1756, 0x17ad, 0x1805, 0x1836, 0x186b, + 0x18a2, 0x18dd, 0x191b, 0x195c, 0x19a1, 0x19e9, 0x1a1b, 0x1a43, 0x1a6d, + 0x1a99, 0x1ac7, 0x1af8, 0x1b2a, 0x1b5f, 0x1b97, 0x1bd1, 0x1c06, 0x1c26, + 0x1c47, 0x1c69, 0x1c8d, 0x1cb2, 0x1cd9, 0x1d01, 0x1d2b, 0x1d56, 0x1d83, + 0x1db2, 0x1de3, 0x1e0a, 0x1e25, 0x1e40, 0x1e5c, 0x1e79, 0x1e97, 0x1eb7, + 0x1ed7, 0x1ef9, 0x1f1b, 0x1f3f, 0x1f64, 0x1f8b, 0x1fb2, 0x1fdb, 0x2002, + 0x2018, 0x202f, 0x2046, 0x205e, 0x2077, 0x2091, 0x20ab, 0x20c6, 0x20e2, + 0x20ff, 0x211d, 0x213c, 0x215b, 0x217c, 0x219d, 0x21c0, 0x21e4, 0x2204, + 0x2217, 0x222a, 0x223e, 0x2253, 0x2268, 0x227d, 0x2294, 0x22ab, 0x22c2, + 0x22db, 0x22f4, 0x230d, 0x2328, 0x2343, 0x235e, 0x237b, 0x2398, 0x23b7, + 0x23d5, 0x23f5, 0x240b, 0x241b, 0x242d, 0x243e, 0x2450, 0x2463, 0x2476, + 0x248a, 0x249e, 0x24b2, 0x24c7, 0x24dd, 0x24f3, 0x250a, 0x2521, 0x2539, + 0x2552, 0x256b, 0x2584, 0x259f, 0x25ba, 0x25d6, 0x25f2, 0x2607, 0x2616, + 0x2626, 0x2635, 0x2645, 0x2656, 0x2666, 0x2678, 0x2689, 0x269b, 0x26ae, + 0x26c1, 0x26d4, 0x26e8, 0x26fc, 0x2711, 0x2726, 0x273c, 0x2753, 0x2769, + 0x2781, 0x2799, 0x27b1, 0x27ca, 0x27e4, 0x27fe, 0x280c, 0x281a, 0x2828, + 0x2836, 0x2845, 0x2854, 0x2863, 0x2873, 0x2883, 0x2893, 0x28a4, 0x28b5, + 0x28c7, 0x28d9, 0x28eb, 0x28fe, 0x2911, 0x2925, 0x2939, 0x294d, 0x2962, + 0x2977, 0x298d, 0x29a4, 0x29bb, 0x29d2, 0x29ea, 0x2a01, 0x2a0d, 0x2a1a, + 0x2a27, 0x2a34, 0x2a42, 0x2a50, 0x2a5e, 0x2a6d, 0x2a7b, 0x2a8a, 0x2a9a, + 0x2aaa, 0x2aba, 0x2aca, 0x2adb, 0x2aec, 0x2afe, 0x2b10, 0x2b22, 0x2b35, + 0x2b48, 0x2b5c, 0x2b6f, 0x2b84, 0x2b99, 0x2bae, 0x2bc4, 0x2bda, 0x2bf0, + 0x2c03, 0x2c0f, 0x2c1b, 0x2c28, 0x2c34, 0x2c41, 0x2c4e, 0x2c5b, 0x2c69, + 0x2c77, 0x2c85, 0x2c94, 0x2ca3, 0x2cb2, 0x2cc1, 0x2cd1, 0x2ce1, 0x2cf2, + 0x2d02, 0x2d13, 0x2d25, 0x2d37, 0x2d49, 0x2d5c, 0x2d6f, 0x2d82, 0x2d96, + 0x2daa, 0x2dbf, 0x2dd4, 0x2de9, 0x2dff, 0x2e0b, 0x2e16, 0x2e22, 0x2e2d, + 0x2e3a, 0x2e46, 0x2e53, 0x2e60, 0x2e6d, 0x2e7a, 0x2e88, 0x2e96, 0x2ea4, + 0x2eb3, 0x2ec2, 0x2ed1, 0x2ee0, 0x2ef0, 0x2f00, 0x2f11, 0x2f22, 0x2f33, + 0x2f44, 0x2f56, 0x2f68, 0x2f7b, 0x2f8e, 0x2fa1, 0x2fb5, 0x2fc9, 0x2fde, + 0x2ff3, 0x3004, 0x300f, 0x301a, 0x3025, 0x3031, 0x303d, 0x3049, 0x3055, + 0x3062, 0x306f, 0x307c, 0x3089, 0x3097, 0x30a5, 0x30b3, 0x30c2, 0x30d1, + 0x30e0, 0x30ef, 0x30ff, 0x310f, 0x311f, 0x3130, 0x3141, 0x3153, 0x3164, + 0x3176, 0x3189, 0x319c, 0x31af, 0x31c3, 0x31d7, 0x31eb, 0x3200, 0x320a, + 0x3215, 0x3220, 0x322c, 0x3237, 0x3243, 0x324f, 0x325b, 0x3268, 0x3275, + 0x3282, 0x328f, 0x329d, 0x32ab, 0x32b9, 0x32c7, 0x32d6, 0x32e5, 0x32f4, + 0x3304, 0x3314, 0x3324, 0x3335, 0x3346, 0x3357, 0x3369, 0x337b, 0x338d, + 0x33a0, 0x33b3, 0x33c7, 0x33db, 0x33ef, 0x3402, 0x340d, 0x3417, 0x3422, + 0x342e, 0x3439, 0x3445, 0x3451, 0x345d, 0x346a, 0x3477, 0x3484, 0x3491, + 0x349f, 0x34ad, 0x34bb, 0x34c9, 0x34d8, 0x34e7, 0x34f6, 0x3506, 0x3516, + 0x3526, 0x3537, 0x3548, 0x355a, 0x356b, 0x357e, 0x3590, 0x35a3, 0x35b6, + 0x35ca, 0x35de, 0x35f3, 0x3604, 0x360e, 0x3619, 0x3624, 0x3630, 0x363b, + 0x3647, 0x3654, 0x3660, 0x366d, 0x367a, 0x3687, 0x3694, 0x36a2, 0x36b0, + 0x36be, 0x36cd, 0x36dc, 0x36eb, 0x36fb, 0x370b, 0x371b, 0x372c, 0x373d, + 0x374e, 0x3760, 0x3772, 0x3785, 0x3798, 0x37ab, 0x37bf, 0x37d3, 0x37e7, + 0x37fd, 0x3809, 0x3814, 0x381f, 0x382a, 0x3836, 0x3842, 0x384e, 0x385b, + 0x3868, 0x3875, 0x3882, 0x3890, 0x389e, 0x38ac, 0x38ba, 0x38c9, 0x38d8, + 0x38e8, 0x38f8, 0x3908, 0x3919, 0x3929, 0x393b, 0x394c, 0x395f, 0x3971, + 0x3984, 0x3997, 0x39ab, 0x39bf, 0x39d4, 0x39e9, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0xc}, + {0x1542, 0x62b, 0x2ed7, 0x230, 0xc78, 0x20ad, 0x3fda, 0xff, 0x3e7, + 0x904, 0x108b, 0x1aa2, 0x2769, 0x36fc, 0x3fff, 0x7d, 0x188, 0x2fa, + 0x4f7, 0x785, 0xaaa, 0xe6d, 0x12d2, 0x17dc, 0x1d92, 0x23f5, 0x2b09, + 0x32d2, 0x3b53, 0x3fff, 0x3fff, 0x3c, 0xbe, 0x140, 0x1d8, 0x291, 0x36c, + 0x46a, 0x58c, 0x6d3, 0x840, 0x9d2, 0xb8c, 0xd6d, 0xf77, 0x11a9, 0x1405, + 0x168a, 0x193a, 0x1c14, 0x1f1a, 0x224b, 0x25a9, 0x2933, 0x2ceb, 0x30cf, + 0x34e1, 0x3922, 0x3d91, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x1c, 0x5d, + 0x9e, 0xdf, 0x120, 0x163, 0x1af, 0x203, 0x260, 0x2c5, 0x332, 0x3a8, + 0x427, 0x4af, 0x540, 0x5db, 0x67e, 0x72b, 0x7e1, 0x8a1, 0x96a, 0xa3d, + 0xb1a, 0xc01, 0xcf1, 0xdec, 0xef1, 0xfff, 0x1119, 0x123c, 0x136a, + 0x14a2, 0x15e5, 0x1732, 0x188a, 0x19ec, 0x1b59, 0x1cd2, 0x1e54, 0x1fe2, + 0x217b, 0x231f, 0x24ce, 0x2687, 0x284d, 0x2a1d, 0x2bf8, 0x2ddf, 0x2fd2, + 0x31cf, 0x33d8, 0x35ed, 0x380d, 0x3a39, 0x3c71, 0x3eb4, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0xc, 0x2c, 0x4d, 0x6d, + 0x8e, 0xae, 0xcf, 0xef, 0x110, 0x12f, 0x151, 0x175, 0x19b, 0x1c3, + 0x1ed, 0x21a, 0x248, 0x278, 0x2ab, 0x2df, 0x316, 0x34f, 0x38a, 0x3c7, + 0x407, 0x449, 0x48d, 0x4d3, 0x51b, 0x566, 0x5b3, 0x603, 0x654, 0x6a8, + 0x6ff, 0x757, 0x7b3, 0x810, 0x870, 0x8d2, 0x937, 0x99e, 0xa07, 0xa73, + 0xae2, 0xb53, 0xbc6, 0xc3c, 0xcb4, 0xd2f, 0xdac, 0xe2c, 0xeae, 0xf33, + 0xfbb, 0x1045, 0x10d1, 0x1160, 0x11f2, 0x1286, 0x131d, 0x13b7, 0x1453, + 0x14f2, 0x1593, 0x1637, 0x16de, 0x1787, 0x1833, 0x18e1, 0x1993, 0x1a47, + 0x1afd, 0x1bb6, 0x1c72, 0x1d31, 0x1df3, 0x1eb7, 0x1f7e, 0x2047, 0x2114, + 0x21e3, 0x22b5, 0x2389, 0x2461, 0x253b, 0x2618, 0x26f8, 0x27da, 0x28c0, + 0x29a8, 0x2a93, 0x2b81, 0x2c71, 0x2d65, 0x2e5b, 0x2f54, 0x3050, 0x314f, + 0x3251, 0x3355, 0x345d, 0x3567, 0x3674, 0x3784, 0x3897, 0x39ad, 0x3ac6, + 0x3be2, 0x3d00, 0x3e22, 0x3f46, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x4, 0x14, 0x24, 0x34, 0x45, 0x55, 0x65, 0x75, 0x86, + 0x96, 0xa6, 0xb6, 0xc7, 0xd7, 0xe7, 0xf7, 0x108, 0x118, 0x127, 0x138, + 0x149, 0x15a, 0x16c, 0x17f, 0x192, 0x1a5, 0x1b9, 0x1ce, 0x1e3, 0x1f8, + 0x20e, 0x225, 0x23c, 0x254, 0x26c, 0x285, 0x29e, 0x2b8, 0x2d2, 0x2ed, + 0x308, 0x324, 0x341, 0x35e, 0x37b, 0x399, 0x3b8, 0x3d7, 0x3f7, 0x417, + 0x438, 0x459, 0x47b, 0x49e, 0x4c1, 0x4e5, 0x509, 0x52e, 0x553, 0x579, + 0x5a0, 0x5c7, 0x5ef, 0x617, 0x640, 0x669, 0x693, 0x6be, 0x6e9, 0x715, + 0x741, 0x76e, 0x79c, 0x7ca, 0x7f8, 0x828, 0x858, 0x888, 0x8b9, 0x8eb, + 0x91d, 0x950, 0x984, 0x9b8, 0x9ed, 0xa22, 0xa58, 0xa8f, 0xac6, 0xafe, + 0xb36, 0xb6f, 0xba9, 0xbe3, 0xc1e, 0xc5a, 0xc96, 0xcd3, 0xd10, 0xd4e, + 0xd8d, 0xdcc, 0xe0c, 0xe4c, 0xe8e, 0xecf, 0xf12, 0xf55, 0xf99, 0xfdd, + 0x1022, 0x1068, 0x10ae, 0x10f5, 0x113c, 0x1185, 0x11ce, 0x1217, 0x1261, + 0x12ac, 0x12f7, 0x1343, 0x1390, 0x13de, 0x142c, 0x147a, 0x14ca, 0x151a, + 0x156a, 0x15bc, 0x160e, 0x1660, 0x16b4, 0x1708, 0x175c, 0x17b2, 0x1808, + 0x185e, 0x18b5, 0x190d, 0x1966, 0x19bf, 0x1a19, 0x1a74, 0x1acf, 0x1b2b, + 0x1b88, 0x1be5, 0x1c43, 0x1ca2, 0x1d01, 0x1d61, 0x1dc2, 0x1e23, 0x1e86, + 0x1ee8, 0x1f4c, 0x1fb0, 0x2015, 0x207a, 0x20e0, 0x2147, 0x21af, 0x2217, + 0x2280, 0x22ea, 0x2354, 0x23bf, 0x242b, 0x2497, 0x2504, 0x2572, 0x25e0, + 0x2650, 0x26c0, 0x2730, 0x27a1, 0x2813, 0x2886, 0x28f9, 0x296d, 0x29e2, + 0x2a58, 0x2ace, 0x2b45, 0x2bbc, 0x2c35, 0x2cae, 0x2d27, 0x2da2, 0x2e1d, + 0x2e99, 0x2f15, 0x2f93, 0x3011, 0x308f, 0x310f, 0x318f, 0x3210, 0x3291, + 0x3314, 0x3397, 0x341a, 0x349f, 0x3524, 0x35aa, 0x3631, 0x36b8, 0x3740, + 0x37c9, 0x3852, 0x38dc, 0x3967, 0x39f3, 0x3a7f, 0x3b0d, 0x3b9a, 0x3c29, + 0x3cb8, 0x3d48, 0x3dd9, 0x3e6b, 0x3efd, 0x3f90, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x0, 0x8, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x41, + 0x49, 0x51, 0x59, 0x61, 0x69, 0x71, 0x79, 0x82, 0x8a, 0x92, 0x9a, 0xa2, + 0xaa, 0xb2, 0xba, 0xc3, 0xcb, 0xd3, 0xdb, 0xe3, 0xeb, 0xf3, 0xfb, + 0x104, 0x10c, 0x114, 0x11c, 0x124, 0x12b, 0x134, 0x13c, 0x144, 0x14d, + 0x156, 0x15f, 0x168, 0x171, 0x17a, 0x183, 0x18d, 0x197, 0x1a0, 0x1aa, + 0x1b4, 0x1be, 0x1c9, 0x1d3, 0x1dd, 0x1e8, 0x1f3, 0x1fe, 0x209, 0x214, + 0x21f, 0x22b, 0x236, 0x242, 0x24e, 0x25a, 0x266, 0x272, 0x27e, 0x28b, + 0x297, 0x2a4, 0x2b1, 0x2be, 0x2cb, 0x2d9, 0x2e6, 0x2f4, 0x301, 0x30f, + 0x31d, 0x32b, 0x339, 0x348, 0x356, 0x365, 0x374, 0x383, 0x392, 0x3a1, + 0x3b0, 0x3c0, 0x3cf, 0x3df, 0x3ef, 0x3ff, 0x40f, 0x41f, 0x430, 0x440, + 0x451, 0x462, 0x473, 0x484, 0x495, 0x4a7, 0x4b8, 0x4ca, 0x4dc, 0x4ee, + 0x500, 0x512, 0x525, 0x537, 0x54a, 0x55d, 0x570, 0x583, 0x596, 0x5a9, + 0x5bd, 0x5d1, 0x5e5, 0x5f9, 0x60d, 0x621, 0x635, 0x64a, 0x65f, 0x674, + 0x689, 0x69e, 0x6b3, 0x6c8, 0x6de, 0x6f4, 0x70a, 0x720, 0x736, 0x74c, + 0x763, 0x779, 0x790, 0x7a7, 0x7be, 0x7d5, 0x7ed, 0x804, 0x81c, 0x834, + 0x84c, 0x864, 0x87c, 0x894, 0x8ad, 0x8c6, 0x8df, 0x8f8, 0x911, 0x92a, + 0x944, 0x95d, 0x977, 0x991, 0x9ab, 0x9c5, 0x9e0, 0x9fa, 0xa15, 0xa30, + 0xa4b, 0xa66, 0xa81, 0xa9d, 0xab8, 0xad4, 0xaf0, 0xb0c, 0xb28, 0xb44, + 0xb61, 0xb7e, 0xb9a, 0xbb7, 0xbd5, 0xbf2, 0xc0f, 0xc2d, 0xc4b, 0xc69, + 0xc87, 0xca5, 0xcc3, 0xce2, 0xd01, 0xd1f, 0xd3f, 0xd5e, 0xd7d, 0xd9c, + 0xdbc, 0xddc, 0xdfc, 0xe1c, 0xe3c, 0xe5d, 0xe7d, 0xe9e, 0xebf, 0xee0, + 0xf01, 0xf23, 0xf44, 0xf66, 0xf88, 0xfaa, 0xfcc, 0xfee, 0x1011, 0x1033, + 0x1056, 0x1079, 0x109c, 0x10c0, 0x10e3, 0x1107, 0x112b, 0x114e, 0x1173, + 0x1197, 0x11bb, 0x11e0, 0x1205, 0x1229, 0x124f, 0x1274, 0x1299, 0x12bf, + 0x12e4, 0x130a, 0x1330, 0x1357, 0x137d, 0x13a3, 0x13ca, 0x13f1, 0x1418, + 0x143f, 0x1467, 0x148e, 0x14b6, 0x14de, 0x1506, 0x152e, 0x1556, 0x157f, + 0x15a7, 0x15d0, 0x15f9, 0x1622, 0x164c, 0x1675, 0x169f, 0x16c9, 0x16f3, + 0x171d, 0x1747, 0x1772, 0x179c, 0x17c7, 0x17f2, 0x181d, 0x1848, 0x1874, + 0x18a0, 0x18cb, 0x18f7, 0x1923, 0x1950, 0x197c, 0x19a9, 0x19d6, 0x1a03, + 0x1a30, 0x1a5d, 0x1a8b, 0x1ab8, 0x1ae6, 0x1b14, 0x1b42, 0x1b71, 0x1b9f, + 0x1bce, 0x1bfd, 0x1c2c, 0x1c5b, 0x1c8a, 0x1cba, 0x1ce9, 0x1d19, 0x1d49, + 0x1d79, 0x1daa, 0x1dda, 0x1e0b, 0x1e3c, 0x1e6d, 0x1e9e, 0x1ed0, 0x1f01, + 0x1f33, 0x1f65, 0x1f97, 0x1fc9, 0x1ffb, 0x202e, 0x2061, 0x2094, 0x20c7, + 0x20fa, 0x212d, 0x2161, 0x2195, 0x21c9, 0x21fd, 0x2231, 0x2266, 0x229a, + 0x22cf, 0x2304, 0x2339, 0x236f, 0x23a4, 0x23da, 0x2410, 0x2446, 0x247c, + 0x24b2, 0x24e9, 0x2520, 0x2556, 0x258e, 0x25c5, 0x25fc, 0x2634, 0x266c, + 0x26a3, 0x26dc, 0x2714, 0x274c, 0x2785, 0x27be, 0x27f7, 0x2830, 0x2869, + 0x28a3, 0x28dc, 0x2916, 0x2950, 0x298b, 0x29c5, 0x2a00, 0x2a3a, 0x2a75, + 0x2ab0, 0x2aec, 0x2b27, 0x2b63, 0x2b9e, 0x2bda, 0x2c17, 0x2c53, 0x2c8f, + 0x2ccc, 0x2d09, 0x2d46, 0x2d83, 0x2dc1, 0x2dfe, 0x2e3c, 0x2e7a, 0x2eb8, + 0x2ef6, 0x2f35, 0x2f73, 0x2fb2, 0x2ff1, 0x3030, 0x3070, 0x30af, 0x30ef, + 0x312f, 0x316f, 0x31af, 0x31f0, 0x3230, 0x3271, 0x32b2, 0x32f3, 0x3334, + 0x3376, 0x33b8, 0x33f9, 0x343b, 0x347e, 0x34c0, 0x3503, 0x3545, 0x3588, + 0x35cb, 0x360f, 0x3652, 0x3696, 0x36da, 0x371e, 0x3762, 0x37a6, 0x37eb, + 0x3830, 0x3875, 0x38ba, 0x38ff, 0x3945, 0x398a, 0x39d0, 0x3a16, 0x3a5c, + 0x3aa3, 0x3ae9, 0x3b30, 0x3b77, 0x3bbe, 0x3c05, 0x3c4d, 0x3c94, 0x3cdc, + 0x3d24, 0x3d6c, 0x3db5, 0x3dfd, 0x3e46, 0x3e8f, 0x3ed8, 0x3f21, 0x3f6b, + 0x3fb5, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, + 0x3fff, 0x3fff, 0x6}, + {0x3394, 0x2f73, 0x37ad, 0x2b6c, 0x31e4, 0x3584, 0x39f8, 0x275f, + 0x2ddf, 0x30b3, 0x32a6, 0x347b, 0x3687, 0x38b1, 0x39ff, 0x2343, 0x29d5, + 0x2caf, 0x2ea3, 0x302f, 0x3145, 0x3249, 0x3312, 0x341a, 0x34f3, 0x361b, + 0x370b, 0x3839, 0x3945, 0x39ff, 0x39ff, 0x1f0e, 0x25c0, 0x28a6, 0x2a9d, + 0x2c2b, 0x2d40, 0x2e46, 0x2f08, 0x2fe6, 0x306f, 0x30fa, 0x3193, 0x321c, + 0x3277, 0x32d9, 0x3350, 0x33e0, 0x3448, 0x34b4, 0x3538, 0x35d8, 0x364e, + 0x36c6, 0x3758, 0x3805, 0x3872, 0x38f7, 0x399a, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x1aaa, 0x2198, 0x2495, 0x2691, 0x2824, 0x2937, 0x2a40, 0x2b01, + 0x2bde, 0x2c6b, 0x2cf6, 0x2d8e, 0x2e1a, 0x2e74, 0x2ed5, 0x2f3d, 0x2fac, + 0x3011, 0x304f, 0x3091, 0x30d6, 0x311f, 0x316b, 0x31bb, 0x3207, 0x3232, + 0x3260, 0x328e, 0x32bf, 0x32f5, 0x3330, 0x3371, 0x33b9, 0x3404, 0x3430, + 0x3461, 0x3497, 0x34d3, 0x3514, 0x355d, 0x35ad, 0x3603, 0x3634, 0x366a, + 0x36a6, 0x36e8, 0x3731, 0x3782, 0x37db, 0x381e, 0x3855, 0x3891, 0x38d4, + 0x391d, 0x396e, 0x39c8, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x15ea, 0x1d4a, 0x2074, 0x227a, 0x2414, 0x2524, 0x2635, + 0x26f5, 0x27d0, 0x2863, 0x28ed, 0x2984, 0x2a14, 0x2a6e, 0x2acf, 0x2b36, + 0x2ba5, 0x2c0d, 0x2c4b, 0x2c8d, 0x2cd2, 0x2d1a, 0x2d67, 0x2db6, 0x2e04, + 0x2e30, 0x2e5d, 0x2e8b, 0x2ebc, 0x2eee, 0x2f22, 0x2f58, 0x2f8f, 0x2fc8, + 0x3001, 0x3020, 0x303f, 0x305f, 0x3080, 0x30a2, 0x30c5, 0x30e8, 0x310d, + 0x3132, 0x3158, 0x317f, 0x31a7, 0x31d0, 0x31f9, 0x3212, 0x3227, 0x323e, + 0x3254, 0x326b, 0x3282, 0x329a, 0x32b3, 0x32cc, 0x32e7, 0x3303, 0x3321, + 0x3340, 0x3360, 0x3383, 0x33a7, 0x33cd, 0x33f5, 0x340f, 0x3425, 0x343c, + 0x3455, 0x346e, 0x3489, 0x34a5, 0x34c3, 0x34e2, 0x3503, 0x3526, 0x354a, + 0x3570, 0x3598, 0x35c2, 0x35ef, 0x360e, 0x3627, 0x3641, 0x365c, 0x3678, + 0x3696, 0x36b6, 0x36d7, 0x36f9, 0x371e, 0x3744, 0x376d, 0x3797, 0x37c4, + 0x37f2, 0x3812, 0x382c, 0x3847, 0x3863, 0x3881, 0x38a1, 0x38c2, 0x38e5, + 0x390a, 0x3931, 0x3959, 0x3984, 0x39b1, 0x39e0, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0xf78, 0x18b7, 0x1c33, 0x1e4c, 0x1fed, + 0x20ff, 0x221f, 0x22db, 0x23b3, 0x2453, 0x24db, 0x2570, 0x260a, 0x2662, + 0x26c2, 0x2729, 0x2796, 0x2805, 0x2843, 0x2884, 0x28c9, 0x2911, 0x295d, + 0x29ac, 0x29ff, 0x2a2a, 0x2a57, 0x2a85, 0x2ab6, 0x2ae8, 0x2b1c, 0x2b51, + 0x2b88, 0x2bc1, 0x2bfc, 0x2c1c, 0x2c3b, 0x2c5b, 0x2c7c, 0x2c9e, 0x2cc0, + 0x2ce4, 0x2d08, 0x2d2d, 0x2d53, 0x2d7a, 0x2da2, 0x2dcb, 0x2df4, 0x2e0f, + 0x2e25, 0x2e3b, 0x2e51, 0x2e68, 0x2e80, 0x2e97, 0x2eb0, 0x2ec8, 0x2ee1, + 0x2efb, 0x2f15, 0x2f2f, 0x2f4a, 0x2f66, 0x2f81, 0x2f9d, 0x2fba, 0x2fd7, + 0x2ff5, 0x3009, 0x3018, 0x3028, 0x3037, 0x3047, 0x3057, 0x3067, 0x3078, + 0x3088, 0x3099, 0x30aa, 0x30bc, 0x30cd, 0x30df, 0x30f1, 0x3103, 0x3116, + 0x3128, 0x313b, 0x314e, 0x3162, 0x3175, 0x3189, 0x319d, 0x31b1, 0x31c5, + 0x31da, 0x31ef, 0x3202, 0x320c, 0x3217, 0x3222, 0x322d, 0x3238, 0x3243, + 0x324e, 0x325a, 0x3265, 0x3271, 0x327d, 0x3288, 0x3294, 0x32a0, 0x32ad, + 0x32b9, 0x32c6, 0x32d3, 0x32e0, 0x32ee, 0x32fc, 0x330a, 0x3319, 0x3328, + 0x3338, 0x3348, 0x3358, 0x3369, 0x337a, 0x338c, 0x339e, 0x33b0, 0x33c3, + 0x33d6, 0x33ea, 0x33ff, 0x340a, 0x3414, 0x341f, 0x342b, 0x3436, 0x3442, + 0x344e, 0x345b, 0x3468, 0x3475, 0x3482, 0x3490, 0x349e, 0x34ad, 0x34bc, + 0x34cb, 0x34da, 0x34ea, 0x34fb, 0x350c, 0x351d, 0x352f, 0x3541, 0x3553, + 0x3566, 0x357a, 0x358e, 0x35a3, 0x35b8, 0x35cd, 0x35e4, 0x35fa, 0x3609, + 0x3614, 0x3621, 0x362d, 0x363a, 0x3647, 0x3655, 0x3663, 0x3671, 0x3680, + 0x368e, 0x369e, 0x36ae, 0x36be, 0x36ce, 0x36df, 0x36f0, 0x3702, 0x3715, + 0x3727, 0x373a, 0x374e, 0x3762, 0x3777, 0x378c, 0x37a2, 0x37b8, 0x37cf, + 0x37e7, 0x37ff, 0x380b, 0x3818, 0x3825, 0x3832, 0x3840, 0x384e, 0x385c, + 0x386b, 0x387a, 0x3889, 0x3899, 0x38a9, 0x38ba, 0x38cb, 0x38dc, 0x38ee, + 0x3901, 0x3913, 0x3927, 0x393b, 0x394f, 0x3964, 0x3979, 0x398f, 0x39a5, + 0x39bc, 0x39d4, 0x39ec, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x0, + 0x137a, 0x177a, 0x19ea, 0x1b7a, 0x1cb7, 0x1dea, 0x1eaa, 0x1f7a, 0x2033, + 0x20b7, 0x214a, 0x21ea, 0x224c, 0x22aa, 0x230e, 0x237a, 0x23ed, 0x2433, + 0x2474, 0x24b7, 0x24ff, 0x254a, 0x2598, 0x25ea, 0x261f, 0x264c, 0x267a, + 0x26aa, 0x26db, 0x270e, 0x2743, 0x277a, 0x27b3, 0x27ed, 0x2814, 0x2833, + 0x2853, 0x2874, 0x2895, 0x28b7, 0x28db, 0x28ff, 0x2924, 0x294a, 0x2970, + 0x2998, 0x29c0, 0x29ea, 0x2a0a, 0x2a1f, 0x2a35, 0x2a4c, 0x2a62, 0x2a7a, + 0x2a91, 0x2aaa, 0x2ac2, 0x2adb, 0x2af5, 0x2b0e, 0x2b29, 0x2b43, 0x2b5f, + 0x2b7a, 0x2b96, 0x2bb3, 0x2bd0, 0x2bed, 0x2c05, 0x2c14, 0x2c24, 0x2c33, + 0x2c43, 0x2c53, 0x2c63, 0x2c74, 0x2c84, 0x2c95, 0x2ca6, 0x2cb7, 0x2cc9, + 0x2cdb, 0x2ced, 0x2cff, 0x2d11, 0x2d24, 0x2d37, 0x2d4a, 0x2d5d, 0x2d70, + 0x2d84, 0x2d98, 0x2dac, 0x2dc0, 0x2dd5, 0x2dea, 0x2dff, 0x2e0a, 0x2e14, + 0x2e1f, 0x2e2a, 0x2e35, 0x2e40, 0x2e4c, 0x2e57, 0x2e62, 0x2e6e, 0x2e7a, + 0x2e85, 0x2e91, 0x2e9d, 0x2eaa, 0x2eb6, 0x2ec2, 0x2ecf, 0x2edb, 0x2ee8, + 0x2ef5, 0x2f01, 0x2f0e, 0x2f1c, 0x2f29, 0x2f36, 0x2f43, 0x2f51, 0x2f5f, + 0x2f6c, 0x2f7a, 0x2f88, 0x2f96, 0x2fa5, 0x2fb3, 0x2fc1, 0x2fd0, 0x2fde, + 0x2fed, 0x2ffc, 0x3005, 0x300d, 0x3014, 0x301c, 0x3024, 0x302b, 0x3033, + 0x303b, 0x3043, 0x304b, 0x3053, 0x305b, 0x3063, 0x306b, 0x3074, 0x307c, + 0x3084, 0x308d, 0x3095, 0x309e, 0x30a6, 0x30af, 0x30b7, 0x30c0, 0x30c9, + 0x30d2, 0x30db, 0x30e4, 0x30ed, 0x30f6, 0x30ff, 0x3108, 0x3111, 0x311a, + 0x3124, 0x312d, 0x3137, 0x3140, 0x314a, 0x3153, 0x315d, 0x3167, 0x3170, + 0x317a, 0x3184, 0x318e, 0x3198, 0x31a2, 0x31ac, 0x31b6, 0x31c0, 0x31cb, + 0x31d5, 0x31df, 0x31ea, 0x31f4, 0x31ff, 0x3204, 0x320a, 0x320f, 0x3214, + 0x321a, 0x321f, 0x3225, 0x322a, 0x3230, 0x3235, 0x323b, 0x3240, 0x3246, + 0x324c, 0x3251, 0x3257, 0x325d, 0x3262, 0x3268, 0x326e, 0x3274, 0x327a, + 0x3280, 0x3285, 0x328b, 0x3291, 0x3297, 0x329d, 0x32a3, 0x32aa, 0x32b0, + 0x32b6, 0x32bc, 0x32c2, 0x32c9, 0x32cf, 0x32d6, 0x32dd, 0x32e4, 0x32ea, + 0x32f1, 0x32f8, 0x32ff, 0x3307, 0x330e, 0x3315, 0x331d, 0x3324, 0x332c, + 0x3334, 0x333c, 0x3344, 0x334c, 0x3354, 0x335c, 0x3365, 0x336d, 0x3376, + 0x337e, 0x3387, 0x3390, 0x3399, 0x33a2, 0x33ab, 0x33b5, 0x33be, 0x33c8, + 0x33d2, 0x33db, 0x33e5, 0x33ef, 0x33fa, 0x3402, 0x3407, 0x340c, 0x3412, + 0x3417, 0x341d, 0x3422, 0x3428, 0x342e, 0x3433, 0x3439, 0x343f, 0x3445, + 0x344b, 0x3451, 0x3458, 0x345e, 0x3464, 0x346b, 0x3471, 0x3478, 0x347f, + 0x3486, 0x348d, 0x3493, 0x349b, 0x34a2, 0x34a9, 0x34b0, 0x34b8, 0x34bf, + 0x34c7, 0x34cf, 0x34d6, 0x34de, 0x34e6, 0x34ee, 0x34f7, 0x34ff, 0x3507, + 0x3510, 0x3519, 0x3521, 0x352a, 0x3533, 0x353c, 0x3545, 0x354f, 0x3558, + 0x3562, 0x356b, 0x3575, 0x357f, 0x3589, 0x3593, 0x359d, 0x35a8, 0x35b2, + 0x35bd, 0x35c8, 0x35d3, 0x35de, 0x35e9, 0x35f4, 0x3600, 0x3606, 0x360b, + 0x3611, 0x3617, 0x361e, 0x3624, 0x362a, 0x3630, 0x3637, 0x363d, 0x3644, + 0x364b, 0x3651, 0x3658, 0x365f, 0x3666, 0x366d, 0x3675, 0x367c, 0x3683, + 0x368b, 0x3692, 0x369a, 0x36a2, 0x36aa, 0x36b2, 0x36ba, 0x36c2, 0x36ca, + 0x36d2, 0x36db, 0x36e3, 0x36ec, 0x36f5, 0x36fe, 0x3707, 0x3710, 0x3719, + 0x3723, 0x372c, 0x3736, 0x373f, 0x3749, 0x3753, 0x375d, 0x3767, 0x3772, + 0x377c, 0x3787, 0x3792, 0x379c, 0x37a7, 0x37b3, 0x37be, 0x37c9, 0x37d5, + 0x37e1, 0x37ec, 0x37f8, 0x3802, 0x3808, 0x380e, 0x3815, 0x381b, 0x3822, + 0x3828, 0x382f, 0x3836, 0x383c, 0x3843, 0x384a, 0x3851, 0x3858, 0x3860, + 0x3867, 0x386e, 0x3876, 0x387e, 0x3885, 0x388d, 0x3895, 0x389d, 0x38a5, + 0x38ad, 0x38b6, 0x38be, 0x38c7, 0x38cf, 0x38d8, 0x38e1, 0x38ea, 0x38f3, + 0x38fc, 0x3905, 0x390f, 0x3918, 0x3922, 0x392c, 0x3936, 0x3940, 0x394a, + 0x3954, 0x395e, 0x3969, 0x3974, 0x397e, 0x3989, 0x3994, 0x39a0, 0x39ab, + 0x39b6, 0x39c2, 0x39ce, 0x39da, 0x39e6, 0x39f2, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, + 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0xc}, +}; + +static const u32 dcss_cscos[8][29] = { + {0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x36c, 0x36c, 0x36c, 0x0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x3ac, 0x3ac, 0x3ac}, + {0x8000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0}, + {0x3, 0x20e0, 0x54d9, 0x76c, 0xffffee22, 0xffffd1e1, 0x4000, 0x4000, + 0xffffc527, 0xfffffadc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x380, 0x380, + 0x380, 0xf, 0x40, 0x200, 0x200, 0x40, 0x40, 0x40, 0x3ac, 0x3c0, 0x3c0}, + {0x3, 0x21a1, 0x56c9, 0x798, 0xffffee22, 0xffffd1e1, 0x4000, 0x4000, + 0xffffc527, 0xfffffadc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff, 0x3ff, + 0x3ff, 0xf, 0x0, 0x200, 0x200, 0x0, 0x0, 0x0, 0x3ff, 0x3ff, 0x3ff}, + {0x3, 0x1a9b, 0x5981, 0x909, 0xfffff157, 0xffffceac, 0x4000, 0x4000, + 0xffffc5e0, 0xfffffa23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x380, 0x380, + 0x380, 0xf, 0x40, 0x200, 0x200, 0x40, 0x40, 0x40, 0x3ac, 0x3c0, 0x3c0}, + {0x3, 0x256b, 0x4976, 0xe45, 0xffffea68, 0xffffd59b, 0x4000, 0x4000, + 0xffffca6a, 0xfffff599, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x380, 0x380, + 0x380, 0xf, 0x40, 0x200, 0x200, 0x40, 0x40, 0x40, 0x3ac, 0x3c0, 0x3c0}, + {0x3, 0x1b37, 0x5b8c, 0x93e, 0xfffff157, 0xffffceac, 0x4000, 0x4000, + 0xffffc5e0, 0xfffffa23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff, 0x3ff, + 0x3ff, 0xf, 0x0, 0x200, 0x200, 0x0, 0x0, 0x0, 0x3ff, 0x3ff, 0x3ff}, + {0x3, 0x2646, 0x4b23, 0xe98, 0xffffea68, 0xffffd59b, 0x4000, 0x4000, + 0xffffca6a, 0xfffff599, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff, 0x3ff, + 0x3ff, 0xf, 0x0, 0x200, 0x200, 0x0, 0x0, 0x0, 0x3ff, 0x3ff, 0x3ff}, +}; + +struct dcss_pipe_cfg { + u32 id; + u8 idx[5]; +}; + +static const struct dcss_pipe_cfg dcss_cfg_table[226] = { + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a160a16, {0, 0, 0, 0, 0}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a160a1a, {1, 0, 0, 0, 0}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a160a86, {0, 4, 0, 0, 0}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a160a8a, {1, 4, 0, 0, 0}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a160c16, {0, 1, 0, 0, 0}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a160c1a, {2, 0, 0, 0, 0}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a160c86, {0, 5, 0, 0, 0}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a160c8a, {2, 4, 0, 0, 0}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a161226, {0, 2, 1, 0, 0}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a16122a, {3, 2, 1, 0, 0}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a161306, {0, 6, 1, 0, 0}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a161426, {0, 3, 1, 0, 0}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a16142a, {5, 2, 1, 0, 0}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a161506, {0, 7, 1, 0, 0}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a16222a, {4, 2, 2, 0, 0}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2084 */ + {0x0a16242a, {6, 2, 2, 0, 0}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a0a16, {0, 0, 0, 8, 2}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a0a1a, {14, 8, 0, 8, 2}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a0a86, {0, 4, 0, 8, 2}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a0a8a, {14, 10, 0, 8, 2}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a0c16, {0, 1, 0, 8, 2}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a0c1a, {15, 8, 0, 8, 2}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a0c86, {0, 5, 0, 8, 2}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a0c8a, {15, 10, 0, 8, 2}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a1226, {0, 2, 1, 8, 2}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a122a, {16, 9, 1, 8, 2}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a1306, {0, 6, 1, 8, 2}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a1426, {0, 3, 1, 8, 2}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a142a, {18, 9, 1, 8, 2}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a1506, {0, 7, 1, 8, 2}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a222a, {17, 9, 2, 8, 2}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */ + {0x0a1a242a, {19, 9, 2, 8, 2}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a860a16, {0, 0, 0, 4, 0}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a860a1a, {1, 0, 0, 4, 0}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a860a86, {0, 4, 0, 4, 0}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a860a8a, {1, 4, 0, 4, 0}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a860c16, {0, 1, 0, 4, 0}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a860c1a, {2, 0, 0, 4, 0}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a860c86, {0, 5, 0, 4, 0}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a860c8a, {2, 4, 0, 4, 0}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a861226, {0, 2, 1, 4, 0}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a86122a, {3, 2, 1, 4, 0}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a861306, {0, 6, 1, 4, 0}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a861426, {0, 3, 1, 4, 0}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a86142a, {5, 2, 1, 4, 0}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a861506, {0, 7, 1, 4, 0}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a86222a, {4, 2, 2, 4, 0}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */ + {0x0a86242a, {6, 2, 2, 4, 0}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a0a16, {0, 0, 0, 10, 2}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a0a1a, {14, 8, 0, 10, 2}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a0a86, {0, 4, 0, 10, 2}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a0a8a, {14, 10, 0, 10, 2}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a0c16, {0, 1, 0, 10, 2}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a0c1a, {15, 8, 0, 10, 2}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a0c86, {0, 5, 0, 10, 2}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a0c8a, {15, 10, 0, 10, 2}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a1226, {0, 2, 1, 10, 2}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a122a, {16, 9, 1, 10, 2}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a1306, {0, 6, 1, 10, 2}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a1426, {0, 3, 1, 10, 2}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a142a, {18, 9, 1, 10, 2}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a1506, {0, 7, 1, 10, 2}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a222a, {17, 9, 2, 10, 2}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */ + {0x0a8a242a, {19, 9, 2, 10, 2}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c160a16, {7, 1, 0, 1, 1}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c160a1a, {8, 1, 0, 1, 1}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c160a86, {7, 5, 0, 1, 1}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c160a8a, {8, 5, 0, 1, 1}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c160c16, {0, 1, 0, 1, 1}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c160c1a, {9, 1, 0, 1, 1}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c160c86, {0, 5, 0, 1, 1}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c160c8a, {9, 5, 0, 1, 1}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c161226, {7, 3, 1, 1, 1}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c16122a, {10, 3, 1, 1, 1}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c161306, {7, 7, 1, 1, 1}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c161426, {0, 3, 1, 1, 1}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c16142a, {12, 3, 1, 1, 1}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c161506, {0, 7, 1, 1, 1}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c16222a, {11, 3, 2, 1, 1}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC2020,REC2084 */ + {0x0c16242a, {13, 3, 2, 1, 1}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a0a16, {7, 1, 0, 1, 3}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a0a1a, {8, 1, 0, 1, 3}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a0a86, {7, 5, 0, 1, 3}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a0a8a, {8, 5, 0, 1, 3}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a0c16, {0, 1, 0, 1, 3}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a0c1a, {9, 1, 0, 1, 3}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a0c86, {0, 5, 0, 1, 3}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a0c8a, {9, 5, 0, 1, 3}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a1226, {7, 3, 1, 1, 3}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a122a, {10, 3, 1, 1, 3}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a1306, {7, 7, 1, 1, 3}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a1426, {0, 3, 1, 1, 3}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a142a, {12, 3, 1, 1, 3}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a1506, {0, 7, 1, 1, 3}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a222a, {11, 3, 2, 1, 3}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */ + {0x0c1a242a, {13, 3, 2, 1, 3}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c860a16, {7, 1, 0, 5, 1}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c860a1a, {8, 1, 0, 5, 1}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c860a86, {7, 5, 0, 5, 1}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c860a8a, {8, 5, 0, 5, 1}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c860c16, {0, 1, 0, 5, 1}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c860c1a, {9, 1, 0, 5, 1}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c860c86, {0, 5, 0, 5, 1}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c860c8a, {9, 5, 0, 5, 1}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c861226, {7, 3, 1, 5, 1}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c86122a, {10, 3, 1, 5, 1}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c861306, {7, 7, 1, 5, 1}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c861426, {0, 3, 1, 5, 1}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c86142a, {12, 3, 1, 5, 1}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c861506, {0, 7, 1, 5, 1}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c86222a, {11, 3, 2, 5, 1}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */ + {0x0c86242a, {13, 3, 2, 5, 1}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a0a16, {7, 1, 0, 5, 3}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a0a1a, {8, 1, 0, 5, 3}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a0a86, {7, 5, 0, 5, 3}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a0a8a, {8, 5, 0, 5, 3}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a0c16, {0, 1, 0, 5, 3}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a0c1a, {9, 1, 0, 5, 3}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a0c86, {0, 5, 0, 5, 3}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a0c8a, {9, 5, 0, 5, 3}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a1226, {7, 3, 1, 5, 3}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a122a, {10, 3, 1, 5, 3}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a1306, {7, 7, 1, 5, 3}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a1426, {0, 3, 1, 5, 3}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a142a, {12, 3, 1, 5, 3}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a1506, {0, 7, 1, 5, 3}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a222a, {11, 3, 2, 5, 3}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */ + {0x0c8a242a, {13, 3, 2, 5, 3}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC709,REC709 */ + {0x12260a16, {0, 0, 0, 2, 0}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC709,REC709 */ + {0x12260a86, {0, 4, 0, 2, 0}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,REC709 */ + {0x12261226, {0, 2, 0, 2, 0}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,REC709 */ + {0x1226122a, {3, 2, 0, 2, 0}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Limited,REC709,REC709 */ + {0x12261306, {0, 6, 0, 2, 0}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,REC709 */ + {0x12261426, {0, 3, 0, 2, 0}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,REC709 */ + {0x1226142a, {5, 2, 0, 2, 0}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Limited,REC709,REC709 */ + {0x12261506, {0, 7, 0, 2, 0}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC709,REC709 */ + {0x1226222a, {4, 2, 3, 2, 0}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC709,REC709 */ + {0x1226242a, {6, 2, 3, 2, 0}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC709,REC709 */ + {0x122a0a16, {0, 0, 0, 9, 4}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC709,REC709 */ + {0x122a0a86, {0, 4, 0, 9, 4}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC709,REC709 */ + {0x122a1226, {0, 2, 0, 9, 4}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC709,REC709 */ + {0x122a122a, {16, 9, 0, 9, 4}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC709,REC709 */ + {0x122a1306, {0, 6, 0, 9, 4}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC709,REC709 */ + {0x122a1426, {0, 3, 0, 9, 4}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC709,REC709 */ + {0x122a142a, {18, 9, 0, 9, 4}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC709,REC709 */ + {0x122a1506, {0, 7, 0, 9, 4}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC709,REC709 */ + {0x122a222a, {17, 9, 3, 9, 4}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC709,REC709 */ + {0x122a242a, {19, 9, 3, 9, 4}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x13060a16, {0, 0, 0, 6, 0}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x13060a1a, {1, 0, 0, 6, 0}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x13060a86, {0, 4, 0, 6, 0}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x13060a8a, {1, 4, 0, 6, 0}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x13060c16, {0, 1, 0, 6, 0}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x13060c1a, {2, 0, 0, 6, 0}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x13060c86, {0, 5, 0, 6, 0}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x13060c8a, {2, 4, 0, 6, 0}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x13061226, {0, 2, 0, 6, 0}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x1306122a, {3, 2, 0, 6, 0}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x13061306, {0, 6, 0, 6, 0}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x13061426, {0, 3, 0, 6, 0}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x1306142a, {5, 2, 0, 6, 0}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x13061506, {0, 7, 0, 6, 0}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x1306222a, {4, 2, 3, 6, 0}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC709,SRGB */ + {0x1306242a, {6, 2, 3, 6, 0}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Full,REC709,REC709 */ + {0x14260a16, {7, 1, 0, 3, 1}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC709,REC709 */ + {0x14260a86, {7, 5, 0, 3, 1}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC709,REC709 */ + {0x14261226, {7, 3, 0, 3, 1}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC709,REC709 */ + {0x1426122a, {10, 3, 0, 3, 1}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Full,REC709,REC709 */ + {0x14261306, {7, 7, 0, 3, 1}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC709,REC709 */ + {0x14261426, {0, 3, 0, 3, 1}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC709,REC709 */ + {0x1426142a, {12, 3, 0, 3, 1}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Full,REC709,REC709 */ + {0x14261506, {0, 7, 0, 3, 1}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC709,REC709 */ + {0x1426222a, {11, 3, 3, 3, 1}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC709,REC709 */ + {0x1426242a, {13, 3, 3, 3, 1}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC709,REC709 */ + {0x142a0a16, {7, 1, 0, 3, 6}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC709,REC709 */ + {0x142a0a86, {7, 5, 0, 3, 6}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC709,REC709 */ + {0x142a1226, {7, 3, 0, 3, 6}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC709,REC709 */ + {0x142a122a, {10, 3, 0, 3, 6}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC709,REC709 */ + {0x142a1306, {7, 7, 0, 3, 6}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC709,REC709 */ + {0x142a1426, {0, 3, 0, 3, 6}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC709,REC709 */ + {0x142a142a, {12, 3, 0, 3, 6}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC709,REC709 */ + {0x142a1506, {0, 7, 0, 3, 6}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC709,REC709 */ + {0x142a222a, {11, 3, 3, 3, 6}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC709,REC709 */ + {0x142a242a, {13, 3, 3, 3, 6}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Full,REC709,SRGB */ + {0x15060a16, {7, 1, 0, 7, 1}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC709,SRGB */ + {0x15060a86, {7, 5, 0, 7, 1}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC709,SRGB */ + {0x15061226, {7, 3, 0, 7, 1}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC709,SRGB */ + {0x1506122a, {10, 3, 0, 7, 1}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Full,REC709,SRGB */ + {0x15061306, {7, 7, 0, 7, 1}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC709,SRGB */ + {0x15061426, {0, 3, 0, 7, 1}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC709,SRGB */ + {0x1506142a, {12, 3, 0, 7, 1}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Full,REC709,SRGB */ + {0x15061506, {0, 7, 0, 7, 1}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC709,SRGB */ + {0x1506222a, {11, 3, 3, 7, 1}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC709,SRGB */ + {0x1506242a, {13, 3, 3, 7, 1}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a0a16, {0, 0, 0, 9, 5}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a0a1a, {14, 8, 0, 9, 5}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a0a86, {0, 4, 0, 9, 5}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a0a8a, {14, 10, 0, 9, 5}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a0c16, {0, 1, 0, 9, 5}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a0c1a, {15, 8, 0, 9, 5}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a0c86, {0, 5, 0, 9, 5}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a0c8a, {15, 10, 0, 9, 5}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a1226, {0, 2, 4, 9, 5}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a122a, {16, 9, 4, 9, 5}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a1306, {0, 6, 4, 9, 5}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a1426, {0, 3, 4, 9, 5}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a142a, {18, 9, 4, 9, 5}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a1506, {0, 7, 4, 9, 5}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a222a, {17, 9, 0, 9, 5}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */ + {0x222a242a, {19, 9, 0, 9, 5}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a0a16, {7, 1, 0, 3, 7}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a0a1a, {8, 1, 0, 3, 7}}, + /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a0a86, {7, 5, 0, 3, 7}}, + /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a0a8a, {8, 5, 0, 3, 7}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a0c16, {0, 1, 0, 3, 7}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a0c1a, {9, 1, 0, 3, 7}}, + /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a0c86, {0, 5, 0, 3, 7}}, + /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a0c8a, {9, 5, 0, 3, 7}}, + /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a1226, {7, 3, 4, 3, 7}}, + /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a122a, {10, 3, 4, 3, 7}}, + /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a1306, {7, 7, 4, 3, 7}}, + /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a1426, {0, 3, 4, 3, 7}}, + /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a142a, {12, 3, 4, 3, 7}}, + /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a1506, {0, 7, 4, 3, 7}}, + /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a222a, {11, 3, 0, 3, 7}}, + /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */ + {0x242a242a, {13, 3, 0, 3, 7}}, +}; + +#endif /* __DCSS_HDR10_TABLES_H__ */ diff --git a/drivers/gpu/drm/imx/dcss/dcss-hdr10.c b/drivers/gpu/drm/imx/dcss/dcss-hdr10.c new file mode 100644 index 000000000000..45d3c3125833 --- /dev/null +++ b/drivers/gpu/drm/imx/dcss/dcss-hdr10.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 NXP. + */ + +#include <linux/device.h> +#include <linux/bitops.h> +#include <linux/bsearch.h> +#include <linux/io.h> +#include <drm/drm_fourcc.h> + +#include "dcss-dev.h" +#include "dcss-hdr10-tables.h" + +#define DCSS_HDR10_A0_LUT 0x0000 +#define DCSS_HDR10_A1_LUT 0x1000 +#define DCSS_HDR10_A2_LUT 0x2000 +/* one CSCA and CSCB for each channel(pipe) */ +#define DCSS_HDR10_CSCA_BASE 0x3000 +#define DCSS_HDR10_CSCB_BASE 0x3800 + +/* one CSCO for all channels(pipes) */ +#define DCSS_HDR10_CSCO_BASE 0x3000 + +#define DCSS_HDR10_LUT_CONTROL (DCSS_HDR10_CSCA_BASE + 0x80) +#define LUT_ENABLE BIT(0) +#define LUT_EN_FOR_ALL_PELS BIT(1) +#define LUT_BYPASS BIT(15) +#define DCSS_HDR10_FL2FX (DCSS_HDR10_CSCB_BASE + 0x74) +#define DCSS_HDR10_LTNL (DCSS_HDR10_CSCO_BASE + 0x74) +#define LTNL_PASS_THRU BIT(0) +#define FIX2FLT_DISABLE BIT(1) +#define LTNL_EN_FOR_ALL_PELS BIT(2) +#define FIX2FLT_EN_FOR_ALL_PELS BIT(3) + +/* following offsets are relative to CSC(A|B|O)_BASE */ +#define DCSS_HDR10_CSC_CONTROL 0x00 +#define CSC_EN BIT(0) +#define CSC_ALL_PIX_EN BIT(1) +#define CSC_BYPASS BIT(15) +#define DCSS_HDR10_CSC_H00 0x04 +#define DCSS_HDR10_CSC_H10 0x08 +#define DCSS_HDR10_CSC_H20 0x0C +#define DCSS_HDR10_CSC_H01 0x10 +#define DCSS_HDR10_CSC_H11 0x14 +#define DCSS_HDR10_CSC_H21 0x18 +#define DCSS_HDR10_CSC_H02 0x1C +#define DCSS_HDR10_CSC_H12 0x20 +#define DCSS_HDR10_CSC_H22 0x24 +#define H_COEF_MASK GENMASK(15, 0) +#define DCSS_HDR10_CSC_IO0 0x28 +#define DCSS_HDR10_CSC_IO1 0x2C +#define DCSS_HDR10_CSC_IO2 0x30 +#define PRE_OFFSET_MASK GENMASK(9, 0) +#define DCSS_HDR10_CSC_IO_MIN0 0x34 +#define DCSS_HDR10_CSC_IO_MIN1 0x38 +#define DCSS_HDR10_CSC_IO_MIN2 0x3C +#define DCSS_HDR10_CSC_IO_MAX0 0x40 +#define DCSS_HDR10_CSC_IO_MAX1 0x44 +#define DCSS_HDR10_CSC_IO_MAX2 0x48 +#define IO_CLIP_MASK GENMASK(9, 0) +#define DCSS_HDR10_CSC_NORM 0x4C +#define NORM_MASK GENMASK(4, 0) +#define DCSS_HDR10_CSC_OO0 0x50 +#define DCSS_HDR10_CSC_OO1 0x54 +#define DCSS_HDR10_CSC_OO2 0x58 +#define POST_OFFSET_MASK GENMASK(27, 0) +#define DCSS_HDR10_CSC_OMIN0 0x5C +#define DCSS_HDR10_CSC_OMIN1 0x60 +#define DCSS_HDR10_CSC_OMIN2 0x64 +#define DCSS_HDR10_CSC_OMAX0 0x68 +#define DCSS_HDR10_CSC_OMAX1 0x6C +#define DCSS_HDR10_CSC_OMAX2 0x70 +#define POST_CLIP_MASK GENMASK(9, 0) + +#define HDR10_IPIPE_LUT_MAX_ENTRIES 1024 +#define HDR10_OPIPE_LUT_MAX_ENTRIES 1023 +#define HDR10_CSC_MAX_REGS 29 + +#define OPIPE_CH_NO 3 + +/* Pipe config descriptor */ + +/* bits per component */ +#define HDR10_BPC_POS 0 +#define HDR10_BPC_MASK GENMASK(1, 0) +/* colorspace */ +#define HDR10_CS_POS 2 +#define HDR10_CS_MASK GENMASK(3, 2) +/* nonlinearity type */ +#define HDR10_NL_POS 4 +#define HDR10_NL_MASK GENMASK(8, 4) +/* pixel range */ +#define HDR10_PR_POS 9 +#define HDR10_PR_MASK GENMASK(10, 9) +/* gamut type */ +#define HDR10_G_POS 11 +#define HDR10_G_MASK GENMASK(15, 11) + +/* FW Table Type Descriptor */ +#define HDR10_TT_LUT BIT(0) +#define HDR10_TT_CSCA BIT(1) +#define HDR10_TT_CSCB BIT(2) +/* Pipe type */ +#define HDR10_PT_OUTPUT BIT(3) +/* Output pipe config descriptor */ +#define HDR10_IPIPE_DESC_POS 4 +#define HDR10_IPIPE_DESC_MASK GENMASK(19, 4) +/* Input pipe config descriptor */ +#define HDR10_OPIPE_DESC_POS 20 +#define HDR10_OPIPE_DESC_MASK GENMASK(35, 20) + +/* config invalid */ +#define HDR10_DESC_INVALID BIT(63) + +enum dcss_hdr10_csc { + HDR10_CSCA, + HDR10_CSCB, +}; + +struct dcss_hdr10_ch { + struct dcss_hdr10 *hdr10; + void __iomem *base_reg; + u32 base_ofs; + + u64 old_cfg_desc; + + u32 id; +}; + +struct dcss_hdr10 { + struct device *dev; + struct dcss_ctxld *ctxld; + + u32 ctx_id; + + struct dcss_hdr10_ch ch[4]; /* 4th channel is, actually, OPIPE */ +}; + +static void dcss_hdr10_write(struct dcss_hdr10_ch *ch, u32 val, u32 ofs) +{ + struct dcss_hdr10 *hdr10 = ch->hdr10; + + dcss_ctxld_write(hdr10->ctxld, hdr10->ctx_id, val, ch->base_ofs + ofs); +} + +static void dcss_hdr10_csc_fill(struct dcss_hdr10_ch *ch, + enum dcss_hdr10_csc csc_to_use, + const u32 *map) +{ + int i; + u32 csc_base_ofs[] = { + DCSS_HDR10_CSCA_BASE + DCSS_HDR10_CSC_CONTROL, + DCSS_HDR10_CSCB_BASE + DCSS_HDR10_CSC_CONTROL, + }; + + for (i = 0; i < HDR10_CSC_MAX_REGS; i++) { + u32 reg_ofs = csc_base_ofs[csc_to_use] + i * sizeof(u32); + + dcss_hdr10_write(ch, map[i], reg_ofs); + } +} + +static void dcss_hdr10_lut_fill(struct dcss_hdr10_ch *ch, const u16 *map) +{ + int i, comp; + u32 lut_base_ofs, ctrl_ofs, lut_entries; + + if (ch->id == OPIPE_CH_NO) { + ctrl_ofs = DCSS_HDR10_LTNL; + lut_entries = HDR10_OPIPE_LUT_MAX_ENTRIES; + } else { + ctrl_ofs = DCSS_HDR10_LUT_CONTROL; + lut_entries = HDR10_IPIPE_LUT_MAX_ENTRIES; + } + + if (ch->id != OPIPE_CH_NO) + dcss_hdr10_write(ch, *map++, ctrl_ofs); + + for (comp = 0; comp < 3; comp++) { + lut_base_ofs = DCSS_HDR10_A0_LUT + comp * 0x1000; + + if (ch->id == OPIPE_CH_NO) { + dcss_hdr10_write(ch, map[0], lut_base_ofs); + lut_base_ofs += 4; + } + + for (i = 0; i < lut_entries; i++) { + u32 reg_ofs = lut_base_ofs + i * sizeof(u32); + + dcss_hdr10_write(ch, map[i], reg_ofs); + } + } + + map += lut_entries; + + if (ch->id != OPIPE_CH_NO) + dcss_hdr10_write(ch, *map, DCSS_HDR10_FL2FX); + else + dcss_hdr10_write(ch, *map, ctrl_ofs); +} + +static int dcss_hdr10_ch_init_all(struct dcss_hdr10 *hdr10, + unsigned long hdr10_base) +{ + struct dcss_hdr10_ch *ch; + int i; + + for (i = 0; i < 4; i++) { + ch = &hdr10->ch[i]; + + ch->base_ofs = hdr10_base + i * 0x4000; + + ch->base_reg = ioremap(ch->base_ofs, SZ_16K); + if (!ch->base_reg) { + dev_err(hdr10->dev, "hdr10: unable to remap ch base\n"); + return -ENOMEM; + } + + ch->old_cfg_desc = HDR10_DESC_INVALID; + + ch->id = i; + ch->hdr10 = hdr10; + } + + return 0; +} + +static int dcss_hdr10_id_compare(const void *a, const void *b) +{ + const u32 id = *(const u32 *)a; + const u32 tbl_id = *(const u32 *)b; + + if (id == tbl_id) + return 0; + + if (id > tbl_id) + return 1; + + return -1; +} + +static struct dcss_pipe_cfg *dcss_hdr10_get_pipe_cfg(struct dcss_hdr10 *hdr10, + u32 desc) +{ + struct dcss_pipe_cfg *res; + + res = bsearch(&desc, dcss_cfg_table, ARRAY_SIZE(dcss_cfg_table), + sizeof(dcss_cfg_table[0]), dcss_hdr10_id_compare); + if (!res) + dev_dbg(hdr10->dev, + "hdr10 cfg table doesn't support desc(0x%08x)\n", desc); + + return res; +} + +static int dcss_hdr10_get_tbls(struct dcss_hdr10 *hdr10, u32 desc, + const u16 **ilut, const u32 **csca, + const u32 **cscb, const u16 **olut, + const u32 **csco) +{ + struct dcss_pipe_cfg *pipe_cfg; + + pipe_cfg = dcss_hdr10_get_pipe_cfg(hdr10, desc); + if (!pipe_cfg) { + dev_err(hdr10->dev, "failed to get hdr10 pipe configurations\n"); + return -EINVAL; + } + + dev_dbg(hdr10->dev, "found tbl_id = 0x%08x: (%d, %d, %d, %d, %d)", + pipe_cfg->id, pipe_cfg->idx[0], pipe_cfg->idx[1], + pipe_cfg->idx[2], pipe_cfg->idx[3], pipe_cfg->idx[4]); + + *csca = dcss_cscas[pipe_cfg->idx[0]]; + *ilut = dcss_iluts[pipe_cfg->idx[1]]; + *cscb = dcss_cscbs[pipe_cfg->idx[2]]; + *olut = dcss_oluts[pipe_cfg->idx[3]]; + *csco = dcss_cscos[pipe_cfg->idx[4]]; + + return 0; +} + +static void dcss_hdr10_write_pipe_tbls(struct dcss_hdr10_ch *ch, + const u16 *lut, const u32 *csca, + const u32 *cscb) +{ + if (csca) + dcss_hdr10_csc_fill(ch, HDR10_CSCA, csca); + + if (ch->id != OPIPE_CH_NO && cscb) + dcss_hdr10_csc_fill(ch, HDR10_CSCB, cscb); + + if (lut) + dcss_hdr10_lut_fill(ch, lut); +} + +int dcss_hdr10_init(struct dcss_dev *dcss, unsigned long hdr10_base) +{ + int ret; + struct dcss_hdr10 *hdr10; + + hdr10 = kzalloc(sizeof(*hdr10), GFP_KERNEL); + if (!hdr10) + return -ENOMEM; + + dcss->hdr10 = hdr10; + hdr10->dev = dcss->dev; + hdr10->ctx_id = CTX_SB_HP; + hdr10->ctxld = dcss->ctxld; + + ret = dcss_hdr10_ch_init_all(hdr10, hdr10_base); + if (ret) { + int i; + + for (i = 0; i < 4; i++) { + if (hdr10->ch[i].base_reg) + iounmap(hdr10->ch[i].base_reg); + } + + goto cleanup; + } + + return 0; + +cleanup: + kfree(hdr10); + + return ret; +} + +void dcss_hdr10_exit(struct dcss_hdr10 *hdr10) +{ + int i; + + for (i = 0; i < 4; i++) { + if (hdr10->ch[i].base_reg) + iounmap(hdr10->ch[i].base_reg); + } + + kfree(hdr10); +} + +static u32 dcss_hdr10_pipe_desc(struct dcss_hdr10_pipe_cfg *pipe_cfg) +{ + u32 desc; + + desc = 2 << HDR10_BPC_POS; + desc |= pipe_cfg->is_yuv ? 2 << HDR10_CS_POS : 1 << HDR10_CS_POS; + desc |= ((1 << pipe_cfg->nl) << HDR10_NL_POS) & HDR10_NL_MASK; + desc |= ((1 << pipe_cfg->pr) << HDR10_PR_POS) & HDR10_PR_MASK; + desc |= ((1 << pipe_cfg->g) << HDR10_G_POS) & HDR10_G_MASK; + + return desc; +} + +static u64 dcss_hdr10_get_desc(struct dcss_hdr10_pipe_cfg *ipipe_cfg, + struct dcss_hdr10_pipe_cfg *opipe_cfg) +{ + u32 ipipe_desc, opipe_desc; + + ipipe_desc = dcss_hdr10_pipe_desc(ipipe_cfg); + opipe_desc = dcss_hdr10_pipe_desc(opipe_cfg); + + return (ipipe_desc & 0xFFFF) | ((opipe_desc & 0xFFFF) << 16); +} + +bool dcss_hdr10_pipe_cfg_is_supported(struct dcss_hdr10 *hdr10, + struct dcss_hdr10_pipe_cfg *ipipe_cfg, + struct dcss_hdr10_pipe_cfg *opipe_cfg) +{ + u32 desc = dcss_hdr10_get_desc(ipipe_cfg, opipe_cfg); + + return !!dcss_hdr10_get_pipe_cfg(hdr10, desc); +} + +void dcss_hdr10_setup(struct dcss_hdr10 *hdr10, int ch_num, + struct dcss_hdr10_pipe_cfg *ipipe_cfg, + struct dcss_hdr10_pipe_cfg *opipe_cfg) +{ + const u16 *ilut, *olut; + const u32 *csca, *cscb, *csco; + u32 desc = dcss_hdr10_get_desc(ipipe_cfg, opipe_cfg); + + if (hdr10->ch[ch_num].old_cfg_desc == desc) + return; + + if (dcss_hdr10_get_tbls(hdr10, desc, &ilut, &csca, &cscb, &olut, &csco)) + return; + + dcss_hdr10_write_pipe_tbls(&hdr10->ch[ch_num], ilut, csca, cscb); + + hdr10->ch[ch_num].old_cfg_desc = desc; + + dcss_hdr10_write_pipe_tbls(&hdr10->ch[OPIPE_CH_NO], olut, csco, NULL); +} diff --git a/drivers/gpu/drm/imx/dcss/dcss-kms.c b/drivers/gpu/drm/imx/dcss/dcss-kms.c index 8cf3352d8858..eeba8688b339 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-kms.c +++ b/drivers/gpu/drm/imx/dcss/dcss-kms.c @@ -3,6 +3,7 @@ * Copyright 2019 NXP. */ +#include <drm/bridge/cdns-mhdp.h> #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_bridge_connector.h> @@ -13,22 +14,44 @@ #include <drm/drm_of.h> #include <drm/drm_probe_helper.h> #include <drm/drm_vblank.h> +#include <linux/component.h> #include "dcss-dev.h" #include "dcss-kms.h" DEFINE_DRM_GEM_CMA_FOPS(dcss_cma_fops); +static int dcss_kms_atomic_check(struct drm_device *dev, + struct drm_atomic_state *state) +{ + int ret; + + ret = drm_atomic_helper_check_modeset(dev, state); + if (ret) + return ret; + + ret = drm_atomic_normalize_zpos(dev, state); + if (ret) + return ret; + + ret = dcss_crtc_setup_opipe(dev, state); + if (ret) + return ret; + + return drm_atomic_helper_check_planes(dev, state); +} + static const struct drm_mode_config_funcs dcss_drm_mode_config_funcs = { .fb_create = drm_gem_fb_create, .output_poll_changed = drm_fb_helper_output_poll_changed, - .atomic_check = drm_atomic_helper_check, + .atomic_check = dcss_kms_atomic_check, .atomic_commit = drm_atomic_helper_commit, }; static const struct drm_driver dcss_kms_driver = { .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, DRM_GEM_CMA_DRIVER_OPS, + .gem_prime_import = drm_gem_prime_import, .fops = &dcss_cma_fops, .name = "imx-dcss", .desc = "i.MX8MQ Display Subsystem", @@ -107,7 +130,7 @@ static int dcss_kms_bridge_connector_init(struct dcss_kms_dev *kms) return 0; } -struct dcss_kms_dev *dcss_kms_attach(struct dcss_dev *dcss) +struct dcss_kms_dev *dcss_kms_attach(struct dcss_dev *dcss, bool componentized) { struct dcss_kms_dev *kms; struct drm_device *drm; @@ -130,16 +153,26 @@ struct dcss_kms_dev *dcss_kms_attach(struct dcss_dev *dcss) if (ret) goto cleanup_mode_config; - ret = dcss_kms_bridge_connector_init(kms); - if (ret) - goto cleanup_mode_config; + if (!componentized) { + ret = dcss_kms_bridge_connector_init(kms); + if (ret) + goto cleanup_mode_config; + } ret = dcss_crtc_init(crtc, drm); if (ret) goto cleanup_mode_config; + if (componentized) { + ret = component_bind_all(dcss->dev, kms); + if (ret) + goto cleanup_crtc; + } + drm_mode_config_reset(drm); + dcss_crtc_attach_color_mgmt_properties(crtc); + drm_kms_helper_poll_init(drm); ret = drm_dev_register(drm, 0); @@ -151,7 +184,8 @@ struct dcss_kms_dev *dcss_kms_attach(struct dcss_dev *dcss) return kms; cleanup_crtc: - drm_bridge_connector_disable_hpd(kms->connector); + if (!componentized) + drm_bridge_connector_disable_hpd(kms->connector); drm_kms_helper_poll_fini(drm); dcss_crtc_deinit(crtc, drm); @@ -162,16 +196,20 @@ cleanup_mode_config: return ERR_PTR(ret); } -void dcss_kms_detach(struct dcss_kms_dev *kms) +void dcss_kms_detach(struct dcss_kms_dev *kms, bool componentized) { struct drm_device *drm = &kms->base; + struct dcss_dev *dcss = drm->dev_private; drm_dev_unregister(drm); - drm_bridge_connector_disable_hpd(kms->connector); + if (!componentized) + drm_bridge_connector_disable_hpd(kms->connector); drm_kms_helper_poll_fini(drm); drm_atomic_helper_shutdown(drm); drm_crtc_vblank_off(&kms->crtc.base); drm_mode_config_cleanup(drm); dcss_crtc_deinit(&kms->crtc, drm); + if (componentized) + component_unbind_all(dcss->dev, drm); drm->dev_private = NULL; } diff --git a/drivers/gpu/drm/imx/dcss/dcss-kms.h b/drivers/gpu/drm/imx/dcss/dcss-kms.h index dfe5dd99eea3..ded51fe64cb4 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-kms.h +++ b/drivers/gpu/drm/imx/dcss/dcss-kms.h @@ -6,23 +6,40 @@ #ifndef _DCSS_KMS_H_ #define _DCSS_KMS_H_ +#include <linux/kernel.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> #include <drm/drm_encoder.h> +#include "dcss-dev.h" + struct dcss_plane { struct drm_plane base; + uint64_t dtrc_table_ofs_val; + struct drm_property *dtrc_table_ofs_prop; + int ch_num; + + enum drm_plane_type type; + bool use_dtrc; }; struct dcss_crtc { struct drm_crtc base; - struct drm_crtc_state *state; - struct dcss_plane *plane[3]; - int irq; + bool disable_ctxld_kick_irq; +}; - bool disable_ctxld_kick_irq; +struct dcss_crtc_state { + struct drm_crtc_state base; + enum dcss_pixel_pipe_output output_encoding; + enum dcss_hdr10_nonlinearity opipe_nl; + enum dcss_hdr10_gamut opipe_g; + enum dcss_hdr10_pixel_range opipe_pr; }; struct dcss_kms_dev { @@ -32,13 +49,27 @@ struct dcss_kms_dev { struct drm_connector *connector; }; -struct dcss_kms_dev *dcss_kms_attach(struct dcss_dev *dcss); -void dcss_kms_detach(struct dcss_kms_dev *kms); +static inline struct dcss_crtc *to_dcss_crtc(struct drm_crtc *crtc) +{ + return container_of(crtc, struct dcss_crtc, base); +} + +static inline struct dcss_crtc_state * +to_dcss_crtc_state(struct drm_crtc_state *state) +{ + return container_of(state, struct dcss_crtc_state, base); +} + +struct dcss_kms_dev *dcss_kms_attach(struct dcss_dev *dcss, bool componetized); +void dcss_kms_detach(struct dcss_kms_dev *kms, bool componetized); +int dcss_crtc_setup_opipe(struct drm_device *dev, + struct drm_atomic_state *state); int dcss_crtc_init(struct dcss_crtc *crtc, struct drm_device *drm); void dcss_crtc_deinit(struct dcss_crtc *crtc, struct drm_device *drm); struct dcss_plane *dcss_plane_init(struct drm_device *drm, unsigned int possible_crtcs, enum drm_plane_type type, unsigned int zpos); +void dcss_crtc_attach_color_mgmt_properties(struct dcss_crtc *crtc); #endif diff --git a/drivers/gpu/drm/imx/dcss/dcss-plane.c b/drivers/gpu/drm/imx/dcss/dcss-plane.c index ac45d54acd4e..5de3c2367673 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-plane.c +++ b/drivers/gpu/drm/imx/dcss/dcss-plane.c @@ -1,10 +1,12 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright 2019 NXP. + * Copyright 2019-2022 NXP. */ #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> +#include <linux/dma-buf.h> +#include <drm/drm_drv.h> #include <drm/drm_fb_cma_helper.h> #include <drm/drm_gem_atomic_helper.h> #include <drm/drm_gem_cma_helper.h> @@ -30,16 +32,36 @@ static const u32 dcss_common_formats[] = { DRM_FORMAT_ABGR2101010, DRM_FORMAT_RGBA1010102, DRM_FORMAT_BGRA1010102, + + /* YUV444 */ + DRM_FORMAT_AYUV, + + /* YUV422 */ + DRM_FORMAT_UYVY, + DRM_FORMAT_VYUY, + DRM_FORMAT_YUYV, + DRM_FORMAT_YVYU, + + /* YUV420 */ + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, + DRM_FORMAT_NV15, }; -static const u64 dcss_video_format_modifiers[] = { +static const u64 dcss_overlay_format_modifiers[] = { + DRM_FORMAT_MOD_VSI_G1_TILED, + DRM_FORMAT_MOD_VSI_G2_TILED, + DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED, + DRM_FORMAT_MOD_VIVANTE_TILED, + DRM_FORMAT_MOD_VIVANTE_SUPER_TILED, DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID, }; -static const u64 dcss_graphics_format_modifiers[] = { +static const u64 dcss_primary_format_modifiers[] = { DRM_FORMAT_MOD_VIVANTE_TILED, DRM_FORMAT_MOD_VIVANTE_SUPER_TILED, + DRM_FORMAT_MOD_VIVANTE_SUPER_TILED_FC, DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID, }; @@ -65,6 +87,36 @@ static void dcss_plane_destroy(struct drm_plane *plane) kfree(dcss_plane); } +static int dcss_plane_atomic_set_property(struct drm_plane *plane, + struct drm_plane_state *state, + struct drm_property *property, + uint64_t val) +{ + struct dcss_plane *dcss_plane = to_dcss_plane(plane); + + if (property == dcss_plane->dtrc_table_ofs_prop) + dcss_plane->dtrc_table_ofs_val = val; + else + return -EINVAL; + + return 0; +} + +static int dcss_plane_atomic_get_property(struct drm_plane *plane, + const struct drm_plane_state *state, + struct drm_property *property, + uint64_t *val) +{ + struct dcss_plane *dcss_plane = to_dcss_plane(plane); + + if (property == dcss_plane->dtrc_table_ofs_prop) + *val = dcss_plane->dtrc_table_ofs_val; + else + return -EINVAL; + + return 0; +} + static bool dcss_plane_format_mod_supported(struct drm_plane *plane, u32 format, u64 modifier) @@ -77,16 +129,36 @@ static bool dcss_plane_format_mod_supported(struct drm_plane *plane, case DRM_FORMAT_ARGB2101010: return modifier == DRM_FORMAT_MOD_LINEAR || modifier == DRM_FORMAT_MOD_VIVANTE_TILED || - modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED; + modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED || + modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED_FC; default: return modifier == DRM_FORMAT_MOD_LINEAR; } break; case DRM_PLANE_TYPE_OVERLAY: - return modifier == DRM_FORMAT_MOD_LINEAR; + switch (format) { + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV15: + return modifier == DRM_FORMAT_MOD_LINEAR || + modifier == DRM_FORMAT_MOD_VSI_G1_TILED || + modifier == DRM_FORMAT_MOD_VSI_G2_TILED || + modifier == DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB2101010: + return modifier == DRM_FORMAT_MOD_LINEAR || + modifier == DRM_FORMAT_MOD_VIVANTE_TILED || + modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED; + default: + return modifier == DRM_FORMAT_MOD_LINEAR; + } + break; default: return false; } + + return false; } static const struct drm_plane_funcs dcss_plane_funcs = { @@ -96,6 +168,8 @@ static const struct drm_plane_funcs dcss_plane_funcs = { .reset = drm_atomic_helper_plane_reset, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .atomic_set_property = dcss_plane_atomic_set_property, + .atomic_get_property = dcss_plane_atomic_get_property, .format_mod_supported = dcss_plane_format_mod_supported, }; @@ -111,7 +185,8 @@ static bool dcss_plane_can_rotate(const struct drm_format_info *format, DRM_MODE_REFLECT_MASK; else if (!format->is_yuv && (modifier == DRM_FORMAT_MOD_VIVANTE_TILED || - modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED)) + modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED || + modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED_FC)) supported_rotation = DRM_MODE_ROTATE_MASK | DRM_MODE_REFLECT_MASK; else if (format->is_yuv && linear_format && @@ -119,14 +194,79 @@ static bool dcss_plane_can_rotate(const struct drm_format_info *format, format->format == DRM_FORMAT_NV21)) supported_rotation = DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180 | DRM_MODE_REFLECT_MASK; + else if (format->is_yuv && linear_format && + format->format == DRM_FORMAT_NV15) + supported_rotation = DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y; return !!(rotation & supported_rotation); } +static void dcss_plane_get_hdr10_pipe_cfg(struct drm_plane_state *plane_state, + struct drm_crtc_state *crtc_state, + struct dcss_hdr10_pipe_cfg *ipipe_cfg, + struct dcss_hdr10_pipe_cfg *opipe_cfg) +{ + struct dcss_crtc_state *dcss_crtc_state = to_dcss_crtc_state(crtc_state); + struct drm_framebuffer *fb = plane_state->fb; + + opipe_cfg->is_yuv = + dcss_crtc_state->output_encoding != DCSS_PIPE_OUTPUT_RGB; + opipe_cfg->g = dcss_crtc_state->opipe_g; + opipe_cfg->nl = dcss_crtc_state->opipe_nl; + opipe_cfg->pr = dcss_crtc_state->opipe_pr; + + ipipe_cfg->is_yuv = fb->format->is_yuv; + + if (!fb->format->is_yuv) { + ipipe_cfg->pr = PR_FULL; + if (fb->format->depth == 30) { + ipipe_cfg->nl = NL_REC2084; + ipipe_cfg->g = G_REC2020; + } else { + ipipe_cfg->nl = NL_REC709; + ipipe_cfg->g = G_REC709; + } + return; + } + + switch (plane_state->color_encoding) { + case DRM_COLOR_YCBCR_BT709: + ipipe_cfg->nl = NL_REC709; + ipipe_cfg->g = G_REC709; + break; + case DRM_COLOR_YCBCR_BT2020: + ipipe_cfg->nl = NL_REC2084; + ipipe_cfg->g = G_REC2020; + break; + default: + ipipe_cfg->nl = NL_REC709; + ipipe_cfg->g = G_REC709; + break; + } + + ipipe_cfg->pr = plane_state->color_range == DRM_COLOR_YCBCR_FULL_RANGE ? + PR_FULL : PR_LIMITED; +} + +static bool +dcss_plane_hdr10_pipe_cfg_is_supported(struct drm_plane_state *plane_state, + struct drm_crtc_state *crtc_state) +{ + struct dcss_dev *dcss = plane_state->plane->dev->dev_private; + struct dcss_hdr10_pipe_cfg ipipe_cfg, opipe_cfg; + + dcss_plane_get_hdr10_pipe_cfg(plane_state, crtc_state, + &ipipe_cfg, &opipe_cfg); + + return dcss_hdr10_pipe_cfg_is_supported(dcss->hdr10, + &ipipe_cfg, &opipe_cfg); +} + static bool dcss_plane_is_source_size_allowed(u16 src_w, u16 src_h, u32 pix_fmt) { if (src_w < 64 && - (pix_fmt == DRM_FORMAT_NV12 || pix_fmt == DRM_FORMAT_NV21)) + (pix_fmt == DRM_FORMAT_NV12 || pix_fmt == DRM_FORMAT_NV21 || + pix_fmt == DRM_FORMAT_NV15)) return false; else if (src_w < 32 && (pix_fmt == DRM_FORMAT_UYVY || pix_fmt == DRM_FORMAT_VYUY || @@ -136,6 +276,18 @@ static bool dcss_plane_is_source_size_allowed(u16 src_w, u16 src_h, u32 pix_fmt) return src_w >= 16 && src_h >= 8; } +static inline bool dcss_plane_use_dtrc(struct drm_framebuffer *fb, + enum drm_plane_type type) +{ + u64 pix_format = fb->format->format; + + return !dcss_plane_fb_is_linear(fb) && + type == DRM_PLANE_TYPE_OVERLAY && + (pix_format == DRM_FORMAT_NV12 || + pix_format == DRM_FORMAT_NV21 || + pix_format == DRM_FORMAT_NV15); +} + static int dcss_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state) { @@ -144,7 +296,6 @@ static int dcss_plane_atomic_check(struct drm_plane *plane, struct dcss_plane *dcss_plane = to_dcss_plane(plane); struct dcss_dev *dcss = plane->dev->dev_private; struct drm_framebuffer *fb = new_plane_state->fb; - bool is_primary_plane = plane->type == DRM_PLANE_TYPE_PRIMARY; struct drm_gem_cma_object *cma_obj; struct drm_crtc_state *crtc_state; int hdisplay, vdisplay; @@ -174,8 +325,7 @@ static int dcss_plane_atomic_check(struct drm_plane *plane, &min, &max); ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state, - min, max, !is_primary_plane, - false); + min, max, true, false); if (ret) return ret; @@ -190,11 +340,9 @@ static int dcss_plane_atomic_check(struct drm_plane *plane, return -EINVAL; } - if ((new_plane_state->crtc_x < 0 || new_plane_state->crtc_y < 0 || - new_plane_state->crtc_x + new_plane_state->crtc_w > hdisplay || - new_plane_state->crtc_y + new_plane_state->crtc_h > vdisplay) && - !dcss_plane_fb_is_linear(fb)) { - DRM_DEBUG_KMS("requested cropping operation is not allowed!\n"); + if (!dcss_plane_hdr10_pipe_cfg_is_supported(new_plane_state, + crtc_state)) { + DRM_DEBUG_KMS("requested hdr10 pipe cfg is not supported!\n"); return -EINVAL; } @@ -206,9 +354,100 @@ static int dcss_plane_atomic_check(struct drm_plane *plane, return -EINVAL; } + if (fb->modifier == DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED && + dcss_plane->dtrc_table_ofs_val == 0) { + DRM_ERROR_RATELIMITED("No DTRC decompression table offset set, reject plane.\n"); + return -EINVAL; + } + + dcss_plane->use_dtrc = dcss_plane_use_dtrc(fb, plane->type); + return 0; } +static struct drm_gem_object *dcss_plane_gem_import(struct drm_device *dev, + struct dma_buf *dma_buf) +{ + struct drm_gem_object *obj; + + if (IS_ERR(dma_buf)) + return ERR_CAST(dma_buf); + + mutex_lock(&dev->object_name_lock); + + obj = dev->driver->gem_prime_import(dev, dma_buf); + + mutex_unlock(&dev->object_name_lock); + + return obj; +} + +static void dcss_plane_set_primary_base(struct dcss_plane *dcss_plane, + u32 baddr) +{ + struct drm_plane *plane = &dcss_plane->base; + struct dcss_dev *dcss = plane->dev->dev_private; + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0); + struct dma_buf *dma_buf = cma_obj->base.dma_buf; + struct drm_gem_object *gem_obj; + dma_addr_t caddr; + bool compressed = true; + u32 compressed_format = _VIV_CFMT_ARGB8; + _VIV_VIDMEM_METADATA *mdata; + + if (dcss_plane_fb_is_linear(fb) || + ((fb->flags & DRM_MODE_FB_MODIFIERS) && + (fb->modifier == DRM_FORMAT_MOD_VIVANTE_TILED || + fb->modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED))) { + dcss_dec400d_bypass(dcss->dec400d); + return; + } + + if (!dma_buf) { + caddr = cma_obj->paddr + ALIGN(fb->height, 64) * fb->pitches[0]; + } else { + mdata = dma_buf->priv; + if (!mdata || mdata->magic != VIV_VIDMEM_METADATA_MAGIC) + return; + + gem_obj = dcss_plane_gem_import(plane->dev, mdata->ts_dma_buf); + if (IS_ERR(gem_obj)) + return; + + caddr = to_drm_gem_cma_obj(gem_obj)->paddr; + + /* release gem_obj */ + drm_gem_object_put(gem_obj); + + dcss_dec400d_fast_clear_config(dcss->dec400d, mdata->fc_value, + mdata->fc_enabled); + + compressed = !!mdata->compressed; + compressed_format = mdata->compress_format; + } + + dcss_dec400d_read_config(dcss->dec400d, 0, compressed, + compressed_format); + dcss_dec400d_addr_set(dcss->dec400d, baddr, caddr); +} + +static void dcss_plane_set_dtrc_base(struct dcss_plane *dcss_plane, + u32 p1_ba, u32 p2_ba) +{ + struct drm_plane *plane = &dcss_plane->base; + struct dcss_dev *dcss = plane->dev->dev_private; + + if (!dcss_plane->use_dtrc) { + dcss_dtrc_bypass(dcss->dtrc, dcss_plane->ch_num); + return; + } + + dcss_dtrc_addr_set(dcss->dtrc, dcss_plane->ch_num, + p1_ba, p2_ba, dcss_plane->dtrc_table_ofs_val); +} + static void dcss_plane_atomic_set_base(struct dcss_plane *dcss_plane) { struct drm_plane *plane = &dcss_plane->base; @@ -218,26 +457,45 @@ static void dcss_plane_atomic_set_base(struct dcss_plane *dcss_plane) const struct drm_format_info *format = fb->format; struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0); unsigned long p1_ba = 0, p2_ba = 0; + u16 x1, y1; + + x1 = state->src.x1 >> 16; + y1 = state->src.y1 >> 16; if (!format->is_yuv || format->format == DRM_FORMAT_NV12 || format->format == DRM_FORMAT_NV21) p1_ba = cma_obj->paddr + fb->offsets[0] + - fb->pitches[0] * (state->src.y1 >> 16) + - format->char_per_block[0] * (state->src.x1 >> 16); + fb->pitches[0] * y1 + + format->char_per_block[0] * x1; + else if (format->format == DRM_FORMAT_NV15) + p1_ba = cma_obj->paddr + fb->offsets[0] + + fb->pitches[0] * y1 + + format->char_per_block[0] * (x1 >> 2); else if (format->format == DRM_FORMAT_UYVY || format->format == DRM_FORMAT_VYUY || format->format == DRM_FORMAT_YUYV || format->format == DRM_FORMAT_YVYU) p1_ba = cma_obj->paddr + fb->offsets[0] + - fb->pitches[0] * (state->src.y1 >> 16) + - 2 * format->char_per_block[0] * (state->src.x1 >> 17); + fb->pitches[0] * y1 + + 2 * format->char_per_block[0] * (x1 >> 1); if (format->format == DRM_FORMAT_NV12 || format->format == DRM_FORMAT_NV21) p2_ba = cma_obj->paddr + fb->offsets[1] + - (((fb->pitches[1] >> 1) * (state->src.y1 >> 17) + - (state->src.x1 >> 17)) << 1); + (((fb->pitches[1] >> 1) * (y1 >> 1) + + (x1 >> 1)) << 1); + else if (format->format == DRM_FORMAT_NV15) + p2_ba = cma_obj->paddr + fb->offsets[1] + + (((fb->pitches[1] >> 1) * (y1 >> 1)) << 1) + + format->char_per_block[1] * (x1 >> 2); + + if (plane->type == DRM_PLANE_TYPE_PRIMARY) + dcss_plane_set_primary_base(dcss_plane, p1_ba); + else + dcss_plane_set_dtrc_base(dcss_plane, + cma_obj->paddr + fb->offsets[0], + cma_obj->paddr + fb->offsets[1]); dcss_dpr_addr_set(dcss->dpr, dcss_plane->ch_num, p1_ba, p2_ba, fb->pitches[0]); @@ -263,6 +521,20 @@ static bool dcss_plane_needs_setup(struct drm_plane_state *state, state->scaling_filter != old_state->scaling_filter; } +static void dcss_plane_setup_hdr10_pipes(struct drm_plane *plane) +{ + struct dcss_dev *dcss = plane->dev->dev_private; + struct dcss_plane *dcss_plane = to_dcss_plane(plane); + struct dcss_hdr10_pipe_cfg ipipe_cfg, opipe_cfg; + + dcss_plane_get_hdr10_pipe_cfg(plane->state, + plane->state->crtc->state, + &ipipe_cfg, &opipe_cfg); + + dcss_hdr10_setup(dcss->hdr10, dcss_plane->ch_num, + &ipipe_cfg, &opipe_cfg); +} + static void dcss_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state) { @@ -287,8 +559,12 @@ static void dcss_plane_atomic_update(struct drm_plane *plane, modifiers_present = !!(fb->flags & DRM_MODE_FB_MODIFIERS); if (old_state->fb && !drm_atomic_crtc_needs_modeset(crtc_state) && - !dcss_plane_needs_setup(new_state, old_state)) { + !dcss_plane_needs_setup(new_state, old_state) && + !dcss_dtg_global_alpha_changed(dcss->dtg, dcss_plane->ch_num, + new_state->alpha >> 8)) { dcss_plane_atomic_set_base(dcss_plane); + if (plane->type == DRM_PLANE_TYPE_PRIMARY) + dcss_dec400d_shadow_trig(dcss->dec400d); return; } @@ -303,15 +579,21 @@ static void dcss_plane_atomic_update(struct drm_plane *plane, dst_w = drm_rect_width(&dst); dst_h = drm_rect_height(&dst); - if (plane->type == DRM_PLANE_TYPE_OVERLAY && - modifiers_present && fb->modifier == DRM_FORMAT_MOD_LINEAR) - modifiers_present = false; - dcss_dpr_format_set(dcss->dpr, dcss_plane->ch_num, new_state->fb->format, modifiers_present ? fb->modifier : DRM_FORMAT_MOD_LINEAR); - dcss_dpr_set_res(dcss->dpr, dcss_plane->ch_num, src_w, src_h); + + if (dcss_plane->use_dtrc) { + u32 dtrc_w, dtrc_h; + + dcss_dtrc_set_res(dcss->dtrc, dcss_plane->ch_num, new_state, + &dtrc_w, &dtrc_h); + dcss_dpr_set_res(dcss->dpr, dcss_plane->ch_num, dtrc_w, dtrc_h); + } else { + dcss_dpr_set_res(dcss->dpr, dcss_plane->ch_num, src_w, src_h); + } + dcss_dpr_set_rotation(dcss->dpr, dcss_plane->ch_num, new_state->rotation); @@ -330,11 +612,18 @@ static void dcss_plane_atomic_update(struct drm_plane *plane, dst_w, dst_h, drm_mode_vrefresh(&crtc_state->mode)); + dcss_plane_setup_hdr10_pipes(plane); + dcss_dtg_plane_pos_set(dcss->dtg, dcss_plane->ch_num, dst.x1, dst.y1, dst_w, dst_h); dcss_dtg_plane_alpha_set(dcss->dtg, dcss_plane->ch_num, fb->format, new_state->alpha >> 8); + if (plane->type == DRM_PLANE_TYPE_PRIMARY) + dcss_dec400d_enable(dcss->dec400d); + else if (dcss_plane->use_dtrc) + dcss_dtrc_enable(dcss->dtrc, dcss_plane->ch_num, true); + if (!dcss_plane->ch_num && (new_state->alpha >> 8) == 0) enable = false; @@ -354,6 +643,8 @@ static void dcss_plane_atomic_disable(struct drm_plane *plane, struct dcss_plane *dcss_plane = to_dcss_plane(plane); struct dcss_dev *dcss = plane->dev->dev_private; + if (dcss_plane->use_dtrc) + dcss_dtrc_enable(dcss->dtrc, dcss_plane->ch_num, false); dcss_dpr_enable(dcss->dpr, dcss_plane->ch_num, false); dcss_scaler_ch_enable(dcss->scaler, dcss_plane->ch_num, false); dcss_dtg_plane_pos_set(dcss->dtg, dcss_plane->ch_num, 0, 0, 0, 0); @@ -372,7 +663,8 @@ struct dcss_plane *dcss_plane_init(struct drm_device *drm, unsigned int zpos) { struct dcss_plane *dcss_plane; - const u64 *format_modifiers = dcss_video_format_modifiers; + const u64 *format_modifiers = dcss_overlay_format_modifiers; + struct drm_property *prop; int ret; if (zpos > 2) @@ -385,7 +677,7 @@ struct dcss_plane *dcss_plane_init(struct drm_device *drm, } if (type == DRM_PLANE_TYPE_PRIMARY) - format_modifiers = dcss_graphics_format_modifiers; + format_modifiers = dcss_primary_format_modifiers; ret = drm_universal_plane_init(drm, &dcss_plane->base, possible_crtcs, &dcss_plane_funcs, dcss_common_formats, @@ -416,7 +708,21 @@ struct dcss_plane *dcss_plane_init(struct drm_device *drm, DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y); - dcss_plane->ch_num = zpos; + dcss_plane->ch_num = 2 - zpos; + dcss_plane->type = type; + + if (type == DRM_PLANE_TYPE_PRIMARY) + return dcss_plane; + + prop = drm_property_create_range(drm, 0, "dtrc_table_ofs", + 0, ULLONG_MAX); + if (!prop) { + DRM_ERROR("cannot create dtrc_table_ofs property\n"); + return ERR_PTR(-ENOMEM); + } + + dcss_plane->dtrc_table_ofs_prop = prop; + drm_object_attach_property(&dcss_plane->base.base, prop, 0); return dcss_plane; } diff --git a/drivers/gpu/drm/imx/dcss/dcss-rdsrc.c b/drivers/gpu/drm/imx/dcss/dcss-rdsrc.c new file mode 100644 index 000000000000..ef695e584acf --- /dev/null +++ b/drivers/gpu/drm/imx/dcss/dcss-rdsrc.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 NXP. + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/dma-mapping.h> +#include <linux/seq_file.h> + +#include "dcss-dev.h" + +#define DCSS_RDSRC_CTRL_STATUS 0x00 +#define RDSRC_RD_ERR BIT(31) +#define RDSRC_FRAME_COMP BIT(30) +#define RDSRC_FIFO_SIZE_POS 16 +#define RDSRC_FIFO_SIZE_MASK GENMASK(22, 16) +#define RDSRC_RD_ERR_EN BIT(15) +#define RDSRC_FRAME_COMP_EN BIT(14) +#define RDSRC_P_SIZE_POS 7 +#define RDSRC_P_SIZE_MASK GENMASK(9, 7) +#define RDSRC_T_SIZE_POS 5 +#define RDSRC_T_SIZE_MASK GENMASK(6, 5) +#define RDSRC_BPP_POS 2 +#define RDSRC_BPP_MASK GENMASK(4, 2) +#define RDSRC_ENABLE BIT(0) +#define DCSS_RDSRC_BASE_ADDR 0x10 +#define DCSS_RDSRC_PITCH 0x14 +#define DCSS_RDSRC_WIDTH 0x18 +#define DCSS_RDSRC_HEIGHT 0x1C + +struct dcss_rdsrc { + struct device *dev; + + void __iomem *base_reg; + u32 base_ofs; + + struct dcss_ctxld *ctxld; + u32 ctx_id; + + u32 buf_addr; + + u32 ctrl_status; +}; + +static void dcss_rdsrc_write(struct dcss_rdsrc *rdsrc, u32 val, u32 ofs) +{ + dcss_ctxld_write(rdsrc->ctxld, rdsrc->ctx_id, val, + rdsrc->base_ofs + ofs); +} + +int dcss_rdsrc_init(struct dcss_dev *dcss, unsigned long rdsrc_base) +{ + struct dcss_rdsrc *rdsrc; + + rdsrc = devm_kzalloc(dcss->dev, sizeof(*rdsrc), GFP_KERNEL); + if (!rdsrc) + return -ENOMEM; + + rdsrc->base_reg = devm_ioremap(dcss->dev, rdsrc_base, SZ_4K); + if (!rdsrc->base_reg) { + dev_err(dcss->dev, "rdsrc: unable to remap base\n"); + devm_kfree(dcss->dev, rdsrc); + return -ENOMEM; + } + + dcss->rdsrc = rdsrc; + + rdsrc->dev = dcss->dev; + rdsrc->base_ofs = rdsrc_base; + rdsrc->ctxld = dcss->ctxld; + rdsrc->ctx_id = CTX_SB_HP; + + return 0; +} + +void dcss_rdsrc_exit(struct dcss_rdsrc *rdsrc) +{ + devm_iounmap(rdsrc->dev, rdsrc->base_reg); + devm_kfree(rdsrc->dev, rdsrc); +} + +void dcss_rdsrc_setup(struct dcss_rdsrc *rdsrc, u32 pix_format, u32 dst_xres, + u32 dst_yres, u32 base_addr) +{ + u32 buf_size, pitch, bpp; + + /* since the scaler output is YUV444, the RDSRC output has to match */ + bpp = 4; + + rdsrc->ctrl_status = FIFO_512 << RDSRC_FIFO_SIZE_POS; + rdsrc->ctrl_status |= PSIZE_256 << RDSRC_P_SIZE_POS; + rdsrc->ctrl_status |= TSIZE_256 << RDSRC_T_SIZE_POS; + rdsrc->ctrl_status |= BPP_32_10BIT_OUTPUT << RDSRC_BPP_POS; + + buf_size = dst_xres * dst_yres * bpp; + pitch = dst_xres * bpp; + + rdsrc->buf_addr = base_addr; + + dcss_rdsrc_write(rdsrc, rdsrc->buf_addr, DCSS_RDSRC_BASE_ADDR); + dcss_rdsrc_write(rdsrc, pitch, DCSS_RDSRC_PITCH); + dcss_rdsrc_write(rdsrc, dst_xres, DCSS_RDSRC_WIDTH); + dcss_rdsrc_write(rdsrc, dst_yres, DCSS_RDSRC_HEIGHT); +} + +void dcss_rdsrc_enable(struct dcss_rdsrc *rdsrc) +{ + dcss_rdsrc_write(rdsrc, rdsrc->ctrl_status, DCSS_RDSRC_CTRL_STATUS); +} + +void dcss_rdsrc_disable(struct dcss_rdsrc *rdsrc) +{ + /* RDSRC is turned off by setting the width and height to 0 */ + dcss_rdsrc_write(rdsrc, 0, DCSS_RDSRC_WIDTH); + dcss_rdsrc_write(rdsrc, 0, DCSS_RDSRC_HEIGHT); + + dcss_rdsrc_write(rdsrc, rdsrc->ctrl_status, DCSS_RDSRC_CTRL_STATUS); +} diff --git a/drivers/gpu/drm/imx/dcss/dcss-scaler.c b/drivers/gpu/drm/imx/dcss/dcss-scaler.c index 47852b9dd5ea..8eaf42eae9cf 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-scaler.c +++ b/drivers/gpu/drm/imx/dcss/dcss-scaler.c @@ -79,6 +79,7 @@ struct dcss_scaler_ch { u32 c_hstart; bool use_nn_interpolation; + int ch_num; }; struct dcss_scaler { @@ -88,6 +89,10 @@ struct dcss_scaler { u32 ctx_id; struct dcss_scaler_ch ch[3]; + + struct dcss_wrscl *wrscl; + struct dcss_rdsrc *rdsrc; + int ch_using_wrscl; }; /* scaler coefficients generator */ @@ -309,6 +314,7 @@ static int dcss_scaler_ch_init_all(struct dcss_scaler *scl, } ch->scl = scl; + ch->ch_num = i; } return 0; @@ -326,6 +332,9 @@ int dcss_scaler_init(struct dcss_dev *dcss, unsigned long scaler_base) scaler->dev = dcss->dev; scaler->ctxld = dcss->ctxld; scaler->ctx_id = CTX_SB_HP; + scaler->wrscl = dcss->wrscl; + scaler->rdsrc = dcss->rdsrc; + scaler->ch_using_wrscl = -1; if (dcss_scaler_ch_init_all(scaler, scaler_base)) { int i; @@ -364,7 +373,19 @@ void dcss_scaler_ch_enable(struct dcss_scaler *scl, int ch_num, bool en) struct dcss_scaler_ch *ch = &scl->ch[ch_num]; u32 scaler_ctrl; - scaler_ctrl = en ? SCALER_EN | REPEAT_EN : 0; + if (scl->ch_using_wrscl == ch_num) { + if (en) { + scaler_ctrl = SCALE2MEM_EN | MEM2OFIFO_EN | REPEAT_EN; + } else { + dcss_wrscl_disable(scl->wrscl); + dcss_rdsrc_disable(scl->rdsrc); + + scl->ch_using_wrscl = -1; + scaler_ctrl = 0; + } + } else { + scaler_ctrl = en ? SCALER_EN | REPEAT_EN : 0; + } if (en) dcss_scaler_write(ch, ch->sdata_ctrl, DCSS_SCALER_SDATA_CTRL); @@ -445,7 +466,8 @@ static void dcss_scaler_res_set(struct dcss_scaler_ch *ch, csrc_xres >>= 1; src_is_444 = false; } else if (pix_format == DRM_FORMAT_NV12 || - pix_format == DRM_FORMAT_NV21) { + pix_format == DRM_FORMAT_NV21 || + pix_format == DRM_FORMAT_NV15) { csrc_xres >>= 1; csrc_yres >>= 1; src_is_444 = false; @@ -486,7 +508,11 @@ static const struct dcss_scaler_factors dcss_scaler_factors[] = { {3, 8}, {5, 8}, {5, 8}, }; -static void dcss_scaler_fractions_set(struct dcss_scaler_ch *ch, +static const struct dcss_scaler_factors dcss_scaler_wrscl_factors[] = { + {5, 8}, {7, 8}, {7, 8}, +}; + +static bool dcss_scaler_fractions_set(struct dcss_scaler_ch *ch, int src_xres, int src_yres, int dst_xres, int dst_yres, u32 src_format, u32 dst_format, @@ -495,6 +521,7 @@ static void dcss_scaler_fractions_set(struct dcss_scaler_ch *ch, int src_c_xres, src_c_yres, dst_c_xres, dst_c_yres; u32 l_vinc, l_hinc, c_vinc, c_hinc; u32 c_vstart, c_hstart; + u8 upscale_factor, downscale_factor; src_c_xres = src_xres; src_c_yres = src_yres; @@ -571,13 +598,27 @@ static void dcss_scaler_fractions_set(struct dcss_scaler_ch *ch, dcss_scaler_write(ch, c_hstart, DCSS_SCALER_H_CHR_START); dcss_scaler_write(ch, c_hinc, DCSS_SCALER_H_CHR_INC); + + downscale_factor = dcss_scaler_factors[ch->ch_num].downscale; + upscale_factor = dcss_scaler_factors[ch->ch_num].upscale; + + /* return if WR_SCL/RD_SRC is needed to scale */ + return l_vinc > downscale_fp(downscale_factor, 13) || + l_vinc < upscale_fp(upscale_factor, 13) || + l_hinc > downscale_fp(downscale_factor, 13) || + l_hinc < upscale_fp(upscale_factor, 13); } int dcss_scaler_get_min_max_ratios(struct dcss_scaler *scl, int ch_num, int *min, int *max) { - *min = upscale_fp(dcss_scaler_factors[ch_num].upscale, 16); - *max = downscale_fp(dcss_scaler_factors[ch_num].downscale, 16); + const struct dcss_scaler_factors *factors_map = dcss_scaler_factors; + + if (scl->ch_using_wrscl == -1 || scl->ch_using_wrscl == ch_num) + factors_map = dcss_scaler_wrscl_factors; + + *min = upscale_fp(factors_map[ch_num].upscale, 16); + *max = downscale_fp(factors_map[ch_num].downscale, 16); return 0; } @@ -780,6 +821,43 @@ void dcss_scaler_set_filter(struct dcss_scaler *scl, int ch_num, ch->use_nn_interpolation = scaling_filter == DRM_SCALING_FILTER_NEAREST_NEIGHBOR; } +static void dcss_scaler_setup_path(struct dcss_scaler_ch *ch, + u32 pix_format, int dst_xres, + int dst_yres, u32 vrefresh_hz, + bool wrscl_needed) +{ + struct dcss_scaler *scl = ch->scl; + u32 base_addr; + + /* nothing to do if WRSCL path is needed but it's already used */ + if (wrscl_needed && scl->ch_using_wrscl != -1 && + scl->ch_using_wrscl != ch->ch_num) + return; + + if (!wrscl_needed) { + /* Channel has finished using WRSCL. Release WRSCL/RDSRC. */ + if (scl->ch_using_wrscl == ch->ch_num) { + dcss_wrscl_disable(scl->wrscl); + dcss_rdsrc_disable(scl->rdsrc); + + scl->ch_using_wrscl = -1; + } + + return; + } + + base_addr = dcss_wrscl_setup(scl->wrscl, pix_format, vrefresh_hz, + dst_xres, dst_yres); + + dcss_rdsrc_setup(scl->rdsrc, pix_format, dst_xres, dst_yres, + base_addr); + + dcss_wrscl_enable(scl->wrscl); + dcss_rdsrc_enable(scl->rdsrc); + + scl->ch_using_wrscl = ch->ch_num; +} + void dcss_scaler_setup(struct dcss_scaler *scl, int ch_num, const struct drm_format_info *format, int src_xres, int src_yres, int dst_xres, int dst_yres, @@ -792,12 +870,14 @@ void dcss_scaler_setup(struct dcss_scaler *scl, int ch_num, enum buffer_format src_format = BUF_FMT_ARGB8888_YUV444; enum buffer_format dst_format = BUF_FMT_ARGB8888_YUV444; u32 pix_format = format->format; + bool use_wrscl; if (format->is_yuv) { dcss_scaler_yuv_enable(ch, true); if (pix_format == DRM_FORMAT_NV12 || - pix_format == DRM_FORMAT_NV21) { + pix_format == DRM_FORMAT_NV21 || + pix_format == DRM_FORMAT_NV15) { rtr_8line_en = true; src_format = BUF_FMT_YUV420; } else if (pix_format == DRM_FORMAT_UYVY || @@ -808,15 +888,18 @@ void dcss_scaler_setup(struct dcss_scaler *scl, int ch_num, } use_5_taps = !rtr_8line_en; + + if (pix_format == DRM_FORMAT_NV15) + pixel_depth = 30; } else { dcss_scaler_yuv_enable(ch, false); pixel_depth = format->depth; } - dcss_scaler_fractions_set(ch, src_xres, src_yres, dst_xres, - dst_yres, src_format, dst_format, - PSC_LOC_HORZ_0_VERT_1_OVER_4); + use_wrscl = dcss_scaler_fractions_set(ch, src_xres, src_yres, dst_xres, + dst_yres, src_format, dst_format, + PSC_LOC_HORZ_0_VERT_1_OVER_4); if (format->is_yuv) dcss_scaler_yuv_coef_set(ch, src_format, dst_format, @@ -832,6 +915,9 @@ void dcss_scaler_setup(struct dcss_scaler *scl, int ch_num, dcss_scaler_format_set(ch, src_format, dst_format); dcss_scaler_res_set(ch, src_xres, src_yres, dst_xres, dst_yres, pix_format, dst_format); + + dcss_scaler_setup_path(ch, pix_format, dst_xres, + dst_yres, vrefresh_hz, use_wrscl); } /* This function will be called from interrupt context. */ diff --git a/drivers/gpu/drm/imx/dcss/dcss-ss.c b/drivers/gpu/drm/imx/dcss/dcss-ss.c index 8ddf08da911b..79061d4d51b4 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-ss.c +++ b/drivers/gpu/drm/imx/dcss/dcss-ss.c @@ -115,12 +115,29 @@ void dcss_ss_exit(struct dcss_ss *ss) kfree(ss); } -void dcss_ss_subsam_set(struct dcss_ss *ss) +void dcss_ss_subsam_set(struct dcss_ss *ss, + enum dcss_pixel_pipe_output output_encoding) { - dcss_ss_write(ss, 0x41614161, DCSS_SS_COEFF); - dcss_ss_write(ss, 0, DCSS_SS_MODE); - dcss_ss_write(ss, 0x03ff0000, DCSS_SS_CLIP_CB); - dcss_ss_write(ss, 0x03ff0000, DCSS_SS_CLIP_CR); + u32 ss_coeff = 0x41614161; + u32 ss_mode = 0; + u32 ss_clip = 0x03ff0000; + + if (output_encoding == DCSS_PIPE_OUTPUT_YUV420) { + ss_coeff = 0x21612161; + ss_mode = 2; + ss_clip = 0x03c00040; + } else if (output_encoding == DCSS_PIPE_OUTPUT_YUV422) { + ss_coeff = 0x33a333a3; + ss_mode = 1; + ss_clip = 0x03c00040; + } else if (output_encoding == DCSS_PIPE_OUTPUT_YUV444) { + ss_clip = 0x03c00040; + } + + dcss_ss_write(ss, ss_coeff, DCSS_SS_COEFF); + dcss_ss_write(ss, ss_mode, DCSS_SS_MODE); + dcss_ss_write(ss, ss_clip, DCSS_SS_CLIP_CB); + dcss_ss_write(ss, ss_clip, DCSS_SS_CLIP_CR); } void dcss_ss_sync_set(struct dcss_ss *ss, struct videomode *vm, diff --git a/drivers/gpu/drm/imx/dcss/dcss-wrscl.c b/drivers/gpu/drm/imx/dcss/dcss-wrscl.c new file mode 100644 index 000000000000..8228f8f46cae --- /dev/null +++ b/drivers/gpu/drm/imx/dcss/dcss-wrscl.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 NXP. + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/seq_file.h> + +#include "dcss-dev.h" + +#define DCSS_WRSCL_CTRL_STATUS 0x00 +#define WRSCL_ERR BIT(31) +#define WRSCL_ERR_EN BIT(30) +#define WRSCL_FRAME_COMP BIT(29) +#define WRSCL_FRAME_COMP_EN BIT(28) +#define WRSCL_FIFO_SIZE_POS 18 +#define WRSCL_FIFO_SIZE_MASK GENMAK(24, 18) +#define WRSCL_P_FREQ_POS 10 +#define WRSCL_P_FREQ_MASK GENMASK(17, 10) +#define WRSCL_P_SIZE_POS 7 +#define WRSCL_P_SIZE_MASK GENMASK(9, 7) +#define WRSCL_T_SIZE_POS 5 +#define WRSCL_T_SIZE_MASK GENMASK(6, 5) +#define WRSCL_BPP_POS 2 +#define WRSCL_BPP_MASK GENMASK(4, 2) +#define WRSCL_REPEAT BIT(1) +#define WRSCL_ENABLE BIT(0) +#define DCSS_WRSCL_BASE_ADDR 0x10 +#define DCSS_WRSCL_PITCH 0x14 + +struct dcss_wrscl { + struct device *dev; + + void __iomem *base_reg; + u32 base_ofs; + + struct dcss_ctxld *ctxld; + u32 ctx_id; + + u32 buf_size; + u32 buf_addr; + void *buf_vaddr; + + struct clk *bclk; + + u32 ctrl_status; +}; + +static void dcss_wrscl_write(struct dcss_wrscl *wrscl, u32 val, u32 ofs) +{ + dcss_ctxld_write(wrscl->ctxld, wrscl->ctx_id, + val, wrscl->base_ofs + ofs); +} + +int dcss_wrscl_init(struct dcss_dev *dcss, unsigned long wrscl_base) +{ + struct dcss_wrscl *wrscl; + + wrscl = devm_kzalloc(dcss->dev, sizeof(*wrscl), GFP_KERNEL); + if (!wrscl) + return -ENOMEM; + + wrscl->base_reg = devm_ioremap(dcss->dev, wrscl_base, SZ_4K); + if (!wrscl->base_reg) { + dev_err(dcss->dev, "wrscl: unable to remap base\n"); + devm_kfree(dcss->dev, wrscl); + return -ENOMEM; + } + + dcss->wrscl = wrscl; + + wrscl->dev = dcss->dev; + wrscl->base_ofs = wrscl_base; + wrscl->ctxld = dcss->ctxld; + wrscl->ctx_id = CTX_SB_HP; + wrscl->bclk = dcss->axi_clk; + + return 0; +} + +void dcss_wrscl_exit(struct dcss_wrscl *wrscl) +{ + devm_iounmap(wrscl->dev, wrscl->base_reg); + devm_kfree(wrscl->dev, wrscl); +} + +static const u16 dcss_wrscl_psize_map[] = {64, 128, 256, 512, 1024, 2048, 4096}; + +u32 dcss_wrscl_setup(struct dcss_wrscl *wrscl, u32 pix_format, u32 vrefresh_hz, + u32 dst_xres, u32 dst_yres) +{ + u32 pitch, p_size, p_freq, bpp; + dma_addr_t dma_handle; + u32 bclk_rate = clk_get_rate(wrscl->bclk); + + /* we'd better release the old buffer */ + if (wrscl->buf_addr) + dmam_free_coherent(wrscl->dev, wrscl->buf_size, + wrscl->buf_vaddr, wrscl->buf_addr); + + p_size = PSIZE_256; + + /* scaler output is YUV444 */ + bpp = 4; + + /* spread the load over the entire frame */ + p_freq = ((u64)bclk_rate * dcss_wrscl_psize_map[p_size]) / + ((u64)dst_xres * dst_yres * vrefresh_hz * bpp * 8); + + /* choose a slightly smaller p_freq */ + p_freq = p_freq - 3 > 255 ? 255 : p_freq - 3; + + wrscl->ctrl_status = FIFO_512 << WRSCL_FIFO_SIZE_POS; + wrscl->ctrl_status |= p_size << WRSCL_P_SIZE_POS; + wrscl->ctrl_status |= TSIZE_256 << WRSCL_T_SIZE_POS; + wrscl->ctrl_status |= BPP_32_10BIT_OUTPUT << WRSCL_BPP_POS; + wrscl->ctrl_status |= p_freq << WRSCL_P_FREQ_POS; + + wrscl->buf_size = dst_xres * dst_yres * bpp; + pitch = dst_xres * bpp; + + wrscl->buf_vaddr = dmam_alloc_coherent(wrscl->dev, wrscl->buf_size, + &dma_handle, GFP_KERNEL); + if (!wrscl->buf_vaddr) { + dev_err(wrscl->dev, "wrscl: cannot alloc buf mem\n"); + return 0; + } + + wrscl->buf_addr = dma_handle; + + dcss_wrscl_write(wrscl, wrscl->buf_addr, DCSS_WRSCL_BASE_ADDR); + dcss_wrscl_write(wrscl, pitch, DCSS_WRSCL_PITCH); + + return wrscl->buf_addr; +} + +void dcss_wrscl_enable(struct dcss_wrscl *wrscl) +{ + wrscl->ctrl_status |= WRSCL_ENABLE | WRSCL_REPEAT; + + dcss_wrscl_write(wrscl, wrscl->ctrl_status, DCSS_WRSCL_CTRL_STATUS); +} + +void dcss_wrscl_disable(struct dcss_wrscl *wrscl) +{ + wrscl->ctrl_status &= ~(WRSCL_ENABLE | WRSCL_REPEAT); + + dcss_wrscl_write(wrscl, wrscl->ctrl_status, DCSS_WRSCL_CTRL_STATUS); + + if (wrscl->buf_addr) { + dmam_free_coherent(wrscl->dev, wrscl->buf_size, + wrscl->buf_vaddr, wrscl->buf_addr); + wrscl->buf_addr = 0; + } +} diff --git a/drivers/gpu/drm/imx/dpu/Kconfig b/drivers/gpu/drm/imx/dpu/Kconfig new file mode 100644 index 000000000000..ad480cdbe503 --- /dev/null +++ b/drivers/gpu/drm/imx/dpu/Kconfig @@ -0,0 +1,6 @@ +config DRM_IMX_DPU + tristate "Freescale i.MX DPU DRM support" + depends on DRM_IMX + depends on IMX_DPU_CORE + default y if DRM_IMX=y + default m if DRM_IMX=m diff --git a/drivers/gpu/drm/imx/dpu/Makefile b/drivers/gpu/drm/imx/dpu/Makefile new file mode 100644 index 000000000000..22ff7e916b6f --- /dev/null +++ b/drivers/gpu/drm/imx/dpu/Makefile @@ -0,0 +1,8 @@ +ccflags-y += -I $(srctree)/$(src)/../ + +imx-dpu-crtc-objs := dpu-crtc.o dpu-kms.o dpu-plane.o +imx-dpu-crtc-$(CONFIG_DEBUG_FS) := dpu-crc.o +obj-$(CONFIG_DRM_IMX_DPU) += imx-dpu-crtc.o + +imx-dpu-render-objs := dpu-blit.o +obj-$(CONFIG_DRM_IMX_DPU) += imx-dpu-render.o diff --git a/drivers/gpu/drm/imx/dpu/dpu-blit.c b/drivers/gpu/drm/imx/dpu/dpu-blit.c new file mode 100644 index 000000000000..35d8429a400f --- /dev/null +++ b/drivers/gpu/drm/imx/dpu/dpu-blit.c @@ -0,0 +1,346 @@ +/* + * Copyright 2017,2021-2022 NXP + * + * 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 <drm/drm_vblank.h> +#include <drm/drm_print.h> +#include <drm/drm_drv.h> +#include <drm/drm_ioctl.h> +#include <drm/imx_drm.h> +#include <linux/component.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/export.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <video/dpu.h> + +#include "imx-drm.h" + +struct imx_drm_dpu_bliteng { + struct dpu_bliteng *dpu_be; + struct list_head list; +}; + +static DEFINE_MUTEX(imx_drm_dpu_bliteng_lock); +static LIST_HEAD(imx_drm_dpu_bliteng_list); + +static int imx_dpu_num; + +int dpu_be_get(struct dpu_bliteng *dpu_be); +void dpu_be_put(struct dpu_bliteng *dpu_be); +s32 dpu_bliteng_get_id(struct dpu_bliteng *dpu_be); +void dpu_be_configure_prefetch(struct dpu_bliteng *dpu_be, + u32 width, u32 height, + u32 x_offset, u32 y_offset, + u32 stride, u32 format, u64 modifier, + u64 baddr, u64 uv_addr); +u32 *dpu_bliteng_get_cmd_list(struct dpu_bliteng *dpu_be); +void dpu_be_wait(struct dpu_bliteng *dpu_be); +int dpu_bliteng_get_empty_instance(struct dpu_bliteng **dpu_be, + struct device *dev); +void dpu_bliteng_set_id(struct dpu_bliteng *dpu_be, int id); +void dpu_bliteng_set_dev(struct dpu_bliteng *dpu_be, struct device *dev); +int dpu_bliteng_init(struct dpu_bliteng *dpu_bliteng); +void dpu_bliteng_fini(struct dpu_bliteng *dpu_bliteng); +int dpu_be_blit(struct dpu_bliteng *dpu_be, + u32 *cmdlist, u32 cmdnum); +int dpu_be_get_fence(struct dpu_bliteng *dpu_be); +int dpu_be_set_fence(struct dpu_bliteng *dpu_be, int fd); + +static struct imx_drm_dpu_bliteng *imx_drm_dpu_bliteng_find_by_id(s32 id) +{ + struct imx_drm_dpu_bliteng *bliteng; + + mutex_lock(&imx_drm_dpu_bliteng_lock); + + list_for_each_entry(bliteng, &imx_drm_dpu_bliteng_list, list) { + if (id == dpu_bliteng_get_id(bliteng->dpu_be)) { + mutex_unlock(&imx_drm_dpu_bliteng_lock); + return bliteng; + } + } + + mutex_unlock(&imx_drm_dpu_bliteng_lock); + + return NULL; +} + +static int imx_drm_dpu_set_cmdlist_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file) +{ + struct drm_imx_dpu_set_cmdlist *req; + struct imx_drm_dpu_bliteng *bliteng; + struct dpu_bliteng *dpu_be; + u32 cmd_nr, *cmd, *cmd_list; + void *user_data; + s32 id = 0; + struct drm_imx_dpu_frame_info frame_info; + int ret; + + req = data; + user_data = (void *)(unsigned long)req->user_data; + if (copy_from_user(&id, (void __user *)user_data, + sizeof(id))) { + return -EFAULT; + } + + if (id != 0 && id != 1) + return -EINVAL; + + user_data += sizeof(id); + if (copy_from_user(&frame_info, (void __user *)user_data, + sizeof(frame_info))) { + return -EFAULT; + } + + bliteng = imx_drm_dpu_bliteng_find_by_id(id); + if (!bliteng) { + DRM_ERROR("Failed to get dpu_bliteng\n"); + return -ENODEV; + } + + dpu_be = bliteng->dpu_be; + + ret = dpu_be_get(dpu_be); + + cmd_nr = req->cmd_nr; + cmd = (u32 *)(unsigned long)req->cmd; + cmd_list = dpu_bliteng_get_cmd_list(dpu_be); + + if (copy_from_user(cmd_list, (void __user *)cmd, + sizeof(*cmd) * cmd_nr)) { + ret = -EFAULT; + goto err; + } + + dpu_be_configure_prefetch(dpu_be, frame_info.width, frame_info.height, + frame_info.x_offset, frame_info.y_offset, + frame_info.stride, frame_info.format, + frame_info.modifier, frame_info.baddr, + frame_info.uv_addr); + + ret = dpu_be_blit(dpu_be, cmd_list, cmd_nr); + +err: + dpu_be_put(dpu_be); + + return ret; +} + +static int imx_drm_dpu_wait_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file) +{ + struct drm_imx_dpu_wait *wait; + struct imx_drm_dpu_bliteng *bliteng; + struct dpu_bliteng *dpu_be; + void *user_data; + s32 id = 0; + int ret; + + wait = data; + user_data = (void *)(unsigned long)wait->user_data; + if (copy_from_user(&id, (void __user *)user_data, + sizeof(id))) { + return -EFAULT; + } + + if (id != 0 && id != 1) + return -EINVAL; + + bliteng = imx_drm_dpu_bliteng_find_by_id(id); + if (!bliteng) { + DRM_ERROR("Failed to get dpu_bliteng\n"); + return -ENODEV; + } + + dpu_be = bliteng->dpu_be; + + ret = dpu_be_get(dpu_be); + + dpu_be_wait(dpu_be); + + dpu_be_put(dpu_be); + + return ret; +} + +static int imx_drm_dpu_get_param_ioctl(struct drm_device *drm_dev, void *data, + struct drm_file *file) +{ + enum drm_imx_dpu_param *param = data; + struct imx_drm_dpu_bliteng *bliteng; + struct dpu_bliteng *dpu_be; + int ret, id, fd = -1; + + switch (*param) { + case (DRM_IMX_MAX_DPUS): + ret = imx_dpu_num; + break; + case DRM_IMX_GET_FENCE: + for (id = 0; id < imx_dpu_num; id++) { + bliteng = imx_drm_dpu_bliteng_find_by_id(id); + if (!bliteng) { + DRM_ERROR("Failed to get dpu_bliteng\n"); + return -ENODEV; + } + + dpu_be = bliteng->dpu_be; + ret = dpu_be_get(dpu_be); + + if (fd == -1) + fd = dpu_be_get_fence(dpu_be); + + dpu_be_set_fence(dpu_be, fd); + dpu_be_put(dpu_be); + } + ret = fd; + break; + default: + ret = -EINVAL; + DRM_ERROR("Unknown param![%d]\n", *param); + break; + } + + return ret; +} + +const struct drm_ioctl_desc imx_drm_dpu_ioctls[3] = { + DRM_IOCTL_DEF_DRV(IMX_DPU_SET_CMDLIST, imx_drm_dpu_set_cmdlist_ioctl, + DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(IMX_DPU_WAIT, imx_drm_dpu_wait_ioctl, + DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(IMX_DPU_GET_PARAM, imx_drm_dpu_get_param_ioctl, + DRM_RENDER_ALLOW), +}; + +static int dpu_bliteng_bind(struct device *dev, struct device *master, + void *data) +{ + struct imx_drm_dpu_bliteng *bliteng; + struct dpu_bliteng *dpu_bliteng = NULL; + int ret; + + bliteng = devm_kzalloc(dev, sizeof(*bliteng), GFP_KERNEL); + if (!bliteng) + return -ENOMEM; + + INIT_LIST_HEAD(&bliteng->list); + + ret = dpu_bliteng_get_empty_instance(&dpu_bliteng, dev); + if (ret) + return ret; + + dpu_bliteng_set_id(dpu_bliteng, imx_dpu_num); + dpu_bliteng_set_dev(dpu_bliteng, dev); + + ret = dpu_bliteng_init(dpu_bliteng); + if (ret) + return ret; + + mutex_lock(&imx_drm_dpu_bliteng_lock); + bliteng->dpu_be = dpu_bliteng; + list_add_tail(&bliteng->list, &imx_drm_dpu_bliteng_list); + mutex_unlock(&imx_drm_dpu_bliteng_lock); + + dev_set_drvdata(dev, dpu_bliteng); + + imx_dpu_num++; + + return 0; +} + +static void dpu_bliteng_unbind(struct device *dev, struct device *master, + void *data) +{ + struct imx_drm_dpu_bliteng *bliteng; + struct dpu_bliteng *dpu_bliteng = dev_get_drvdata(dev); + s32 id = dpu_bliteng_get_id(dpu_bliteng); + + bliteng = imx_drm_dpu_bliteng_find_by_id(id); + list_del(&bliteng->list); + + dpu_bliteng_fini(dpu_bliteng); + dev_set_drvdata(dev, NULL); + + imx_dpu_num--; +} + +static const struct component_ops dpu_bliteng_ops = { + .bind = dpu_bliteng_bind, + .unbind = dpu_bliteng_unbind, +}; + +static int dpu_bliteng_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + if (!dev->platform_data) + return -EINVAL; + + return component_add(dev, &dpu_bliteng_ops); +} + +static int dpu_bliteng_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dpu_bliteng_ops); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int dpu_bliteng_suspend(struct device *dev) +{ + struct dpu_bliteng *dpu_bliteng = dev_get_drvdata(dev); + int ret; + + if (dpu_bliteng == NULL) + return 0; + + ret = dpu_be_get(dpu_bliteng); + + dpu_be_wait(dpu_bliteng); + + dpu_be_put(dpu_bliteng); + + dpu_bliteng_fini(dpu_bliteng); + + return 0; +} + +static int dpu_bliteng_resume(struct device *dev) +{ + struct dpu_bliteng *dpu_bliteng = dev_get_drvdata(dev); + + if (dpu_bliteng != NULL) + dpu_bliteng_init(dpu_bliteng); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(dpu_bliteng_pm_ops, + dpu_bliteng_suspend, dpu_bliteng_resume); + +struct platform_driver dpu_bliteng_driver = { + .driver = { + .name = "imx-drm-dpu-bliteng", + .pm = &dpu_bliteng_pm_ops, + }, + .probe = dpu_bliteng_probe, + .remove = dpu_bliteng_remove, +}; + +module_platform_driver(dpu_bliteng_driver); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_DESCRIPTION("i.MX DRM DPU BLITENG"); diff --git a/drivers/gpu/drm/imx/dpu/dpu-blit.h b/drivers/gpu/drm/imx/dpu/dpu-blit.h new file mode 100644 index 000000000000..cf429086cdf4 --- /dev/null +++ b/drivers/gpu/drm/imx/dpu/dpu-blit.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +/* + * Copyright 2021 NXP + */ + +#ifndef _DPU_DRM_BLIT_H_ +#define _DPU_DRM_BLIT_H_ + +#include <drm/drm_ioctl.h> + +#ifdef CONFIG_DRM_IMX_DPU +extern const struct drm_ioctl_desc imx_drm_dpu_ioctls[3]; +#else +const struct drm_ioctl_desc imx_drm_dpu_ioctls[] = {}; +#endif + +#endif /* _DPU_DRM_BLIT_H_ */ diff --git a/drivers/gpu/drm/imx/dpu/dpu-crc.c b/drivers/gpu/drm/imx/dpu/dpu-crc.c new file mode 100644 index 000000000000..cba4e1705e1c --- /dev/null +++ b/drivers/gpu/drm/imx/dpu/dpu-crc.c @@ -0,0 +1,385 @@ +/* + * Copyright 2019-2021 NXP + * + * 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 <drm/drm_atomic.h> +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_plane.h> +#include <linux/interrupt.h> +#include <linux/types.h> +#include <video/dpu.h> +#include "dpu-crc.h" +#include "dpu-crtc.h" + +static inline void get_left(struct drm_rect *r, struct drm_display_mode *m) +{ + r->x1 = 0; + r->y1 = 0; + r->x2 = m->hdisplay >> 1; + r->y2 = m->vdisplay; +} + +static inline void get_right(struct drm_rect *r, struct drm_display_mode *m) +{ + r->x1 = m->hdisplay >> 1; + r->y1 = 0; + r->x2 = m->hdisplay; + r->y2 = m->vdisplay; +} + +static void +dpu_enable_signature_roi(struct dpu_signature *sig, struct drm_rect *roi) +{ + signature_continuous_mode(sig, true); + signature_win(sig, 0, roi->x1, roi->y1, roi->x2, roi->y2); + signature_eval_win(sig, 0, true); + signature_shdldreq(sig, 0x1); +} + +static void dpu_disable_signature(struct dpu_signature *sig) +{ + signature_continuous_mode(sig, false); + signature_wait_for_idle(sig); + signature_eval_win(sig, 0, false); +} + +/* + * Supported modes and source names: + * 1) auto mode: + * "auto" should be selected as the source name. + * The evaluation window is the same to the display region as + * indicated by drm_crtc_state->adjusted_mode. + * + * 2) region of interest(ROI) mode: + * "roi:x1,y1,x2,y2" should be selected as the source name. + * The region of interest is defined by the inclusive upper left + * position at (x1, y1) and the exclusive lower right position + * at (x2, y2), see struct drm_rect for the same idea. + * The evaluation window is the region of interest. + */ +static int +dpu_crc_parse_source(const char *source_name, enum dpu_crc_source *s, + struct drm_rect *roi) +{ + static const char roi_prefix[] = "roi:"; + + if (!source_name) { + *s = DPU_CRC_SRC_NONE; + } else if (!strcmp(source_name, "auto")) { + *s = DPU_CRC_SRC_FRAMEGEN; + } else if (strstarts(source_name, roi_prefix)) { + char *options, *opt; + int len = strlen(roi_prefix); + int params[4]; + int i = 0, ret; + + options = kstrdup(source_name + len, GFP_KERNEL); + + while ((opt = strsep(&options, ",")) != NULL) { + if (i > 3) + return -EINVAL; + + ret = kstrtouint(opt, 10, ¶ms[i]); + if (ret < 0) + return ret; + + if (params[i] < 0) + return -EINVAL; + + i++; + } + + if (i != 4) + return -EINVAL; + + roi->x1 = params[0]; + roi->y1 = params[1]; + roi->x2 = params[2]; + roi->y2 = params[3]; + + if (!drm_rect_visible(roi)) + return -EINVAL; + + *s = DPU_CRC_SRC_FRAMEGEN_ROI; + } else { + return -EINVAL; + } + + return 0; +} + +int dpu_crtc_verify_crc_source(struct drm_crtc *crtc, const char *source_name, + size_t *values_cnt) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct imx_crtc_state *imx_crtc_state; + struct dpu_crtc_state *dcstate; + struct drm_rect roi; + enum dpu_crc_source source; + int ret; + + if (dpu_crc_parse_source(source_name, &source, &roi) < 0) { + dev_dbg(dpu_crtc->dev, "unknown source %s\n", source_name); + return -EINVAL; + } + + ret = drm_modeset_lock_single_interruptible(&crtc->mutex); + if (ret) + return ret; + + imx_crtc_state = to_imx_crtc_state(crtc->state); + dcstate = to_dpu_crtc_state(imx_crtc_state); + *values_cnt = dcstate->use_pc ? 6 : 3; + + drm_modeset_unlock(&crtc->mutex); + + return ret; +} + +int dpu_crtc_set_crc_source(struct drm_crtc *crtc, const char *source_name) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct drm_modeset_acquire_ctx ctx; + struct drm_crtc_state *crtc_state; + struct drm_atomic_state *state; + struct drm_rect roi = {0, 0, 0, 0}; + enum dpu_crc_source source; + int ret; + + if (dpu_crc_parse_source(source_name, &source, &roi) < 0) { + dev_dbg(dpu_crtc->dev, "unknown source %s\n", source_name); + return -EINVAL; + } + + /* Perform an atomic commit to set the CRC source. */ + drm_modeset_acquire_init(&ctx, 0); + + state = drm_atomic_state_alloc(crtc->dev); + if (!state) { + ret = -ENOMEM; + goto unlock; + } + + state->acquire_ctx = &ctx; + +retry: + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (!IS_ERR(crtc_state)) { + struct imx_crtc_state *imx_crtc_state; + struct dpu_crtc_state *dcstate; + + imx_crtc_state = to_imx_crtc_state(crtc_state); + dcstate = to_dpu_crtc_state(imx_crtc_state); + + if ((dcstate->use_pc && crtc->crc.values_cnt != 6) || + (!dcstate->use_pc && crtc->crc.values_cnt != 3)) { + ret = -EINVAL; + goto put; + } + + dcstate->crc.source = source; + dpu_copy_roi(&roi, &dcstate->crc.roi); + dpu_crtc->use_dual_crc = dcstate->use_pc; + + ret = drm_atomic_commit(state); + } else { + ret = PTR_ERR(crtc_state); + } + + if (ret == -EDEADLK) { + drm_atomic_state_clear(state); + drm_modeset_backoff(&ctx); + goto retry; + } + +put: + drm_atomic_state_put(state); + +unlock: + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + return ret; +} + +irqreturn_t dpu_crc_valid_irq_threaded_handler(int irq, void *dev_id) +{ + struct dpu_crtc *dpu_crtc = dev_id; + struct dpu_signature *sig = dpu_crtc->sig; + struct dpu_crtc *aux_dpu_crtc = dpu_crtc_get_aux_dpu_crtc(dpu_crtc); + bool dual_crc = dpu_crtc->use_dual_crc; + unsigned long ret; + uint32_t crcs[6] = {0, 0, 0, 0, 0, 0}; + + dev_dbg(dpu_crtc->dev, "CRC valid irq threaded handler\n"); + + signature_crc_value(sig, 0, &dpu_crtc->crc_red, + &dpu_crtc->crc_green, + &dpu_crtc->crc_blue); + + if (dual_crc && dpu_crtc->stream_id == 1) { + complete(&aux_dpu_crtc->aux_crc_done); + return IRQ_HANDLED; + } + + if (!dual_crc || + (dual_crc && dpu_crtc->dual_crc_flag != DPU_DUAL_CRC_FLAG_RIGHT)) { + crcs[2] = dpu_crtc->crc_red; + crcs[1] = dpu_crtc->crc_green; + crcs[0] = dpu_crtc->crc_blue; + } + + if (dual_crc && dpu_crtc->stream_id == 0) { + ret = wait_for_completion_timeout(&dpu_crtc->aux_crc_done, + HZ / 20); + if (ret == 0) + dev_warn(dpu_crtc->dev, + "wait for auxiliary CRC done timeout\n"); + + if (dpu_crtc->dual_crc_flag != DPU_DUAL_CRC_FLAG_LEFT) { + crcs[5] = aux_dpu_crtc->crc_red; + crcs[4] = aux_dpu_crtc->crc_green; + crcs[3] = aux_dpu_crtc->crc_blue; + } + } + + drm_crtc_add_crc_entry(&dpu_crtc->base, false, 0, crcs); + + return IRQ_HANDLED; +} + +void dpu_crtc_enable_crc_source(struct drm_crtc *crtc, + enum dpu_crc_source source, + struct drm_rect *roi) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct dpu_crtc *aux_dpu_crtc = dpu_crtc_get_aux_dpu_crtc(dpu_crtc); + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc->state); + struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state); + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct completion *shdld_done; + struct drm_rect left, right; + struct drm_rect r, aux_r, clip; + bool dual_crc = dpu_crtc->use_dual_crc; + bool use_left, use_right; + int half_hdisplay; + unsigned long ret; + + if (source == DPU_CRC_SRC_NONE) + return; + + if (dual_crc != dcstate->use_pc) + return; + + if (dpu_crtc->crc_is_enabled) + return; + + if (dual_crc) { + half_hdisplay = mode->hdisplay >> 1; + + get_left(&left, mode); + get_right(&right, mode); + + dpu_copy_roi(&left, &clip); + if (drm_rect_intersect(&clip, roi)) { + dpu_copy_roi(&clip, &r); + use_left = true; + } else { + dpu_copy_roi(&left, &r); + use_left = false; + } + + if (drm_rect_intersect(&right, roi)) { + right.x1 -= half_hdisplay; + right.x2 -= half_hdisplay; + dpu_copy_roi(&right, &aux_r); + use_right = true; + } else { + dpu_copy_roi(&left, &aux_r); + use_right = false; + } + + if (use_left && !use_right) { + dpu_crtc->dual_crc_flag = DPU_DUAL_CRC_FLAG_LEFT; + } else if (!use_left && use_right) { + dpu_crtc->dual_crc_flag = DPU_DUAL_CRC_FLAG_RIGHT; + } else if (use_left && use_right) { + dpu_crtc->dual_crc_flag = DPU_DUAL_CRC_FLAG_DUAL; + } else { + dpu_crtc->dual_crc_flag = DPU_DUAL_CRC_FLAG_ERR_NONE; + dev_err(dpu_crtc->dev, "error flag for dual CRC\n"); + return; + } + } else { + dpu_copy_roi(roi, &r); + } + + enable_irq(dpu_crtc->crc_valid_irq); + enable_irq(dpu_crtc->crc_shdld_irq); + disengcfg_sig_select(dpu_crtc->dec, DEC_SIG_SEL_FRAMEGEN); + dpu_enable_signature_roi(dpu_crtc->sig, &r); + + if (dual_crc) { + aux_dpu_crtc->use_dual_crc = dual_crc; + enable_irq(aux_dpu_crtc->crc_valid_irq); + enable_irq(aux_dpu_crtc->crc_shdld_irq); + disengcfg_sig_select(dpu_crtc->aux_dec, DEC_SIG_SEL_FRAMEGEN); + dpu_enable_signature_roi(dpu_crtc->aux_sig, &aux_r); + } + + shdld_done = &dpu_crtc->crc_shdld_done; + ret = wait_for_completion_timeout(shdld_done, HZ); + if (ret == 0) + dev_warn(dpu_crtc->dev, "wait for CRC shdld done timeout\n"); + + if (dual_crc) { + shdld_done = &aux_dpu_crtc->crc_shdld_done; + ret = wait_for_completion_timeout(shdld_done, HZ); + if (ret == 0) + dev_warn(dpu_crtc->dev, + "wait for auxiliary CRC shdld done timeout\n"); + } + + disable_irq(dpu_crtc->crc_shdld_irq); + if (dual_crc) + disable_irq(aux_dpu_crtc->crc_shdld_irq); + + dpu_crtc->crc_is_enabled = true; + + dev_dbg(dpu_crtc->dev, "enable CRC source %d, ROI:" DRM_RECT_FMT "\n", + source, DRM_RECT_ARG(roi)); +} + +void dpu_crtc_disable_crc_source(struct drm_crtc *crtc, bool dual_crc) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct dpu_crtc *aux_dpu_crtc = dpu_crtc_get_aux_dpu_crtc(dpu_crtc); + + if (!dpu_crtc->crc_is_enabled) + return; + + dpu_disable_signature(dpu_crtc->sig); + if (dual_crc) + dpu_disable_signature(dpu_crtc->aux_sig); + + disable_irq(dpu_crtc->crc_valid_irq); + if (dual_crc) { + disable_irq(aux_dpu_crtc->crc_valid_irq); + reinit_completion(&dpu_crtc->aux_crc_done); + } + + dpu_crtc->crc_is_enabled = false; + + dev_dbg(dpu_crtc->dev, "disable CRC source\n"); +} diff --git a/drivers/gpu/drm/imx/dpu/dpu-crc.h b/drivers/gpu/drm/imx/dpu/dpu-crc.h new file mode 100644 index 000000000000..25c03937470e --- /dev/null +++ b/drivers/gpu/drm/imx/dpu/dpu-crc.h @@ -0,0 +1,75 @@ +/* + * Copyright 2019,2020 NXP + * + * 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. + */ + +#ifndef _DPU_CRC_H_ +#define _DPU_CRC_H_ + +#include "dpu-crtc.h" + +enum { + DPU_DUAL_CRC_FLAG_DUAL, + DPU_DUAL_CRC_FLAG_LEFT, + DPU_DUAL_CRC_FLAG_RIGHT, + DPU_DUAL_CRC_FLAG_ERR_NONE, +}; + +static inline bool to_enable_dpu_crc(struct dpu_crtc_state *new_dcstate, + struct dpu_crtc_state *old_dcstate) +{ + return old_dcstate->crc.source == DPU_CRC_SRC_NONE && + new_dcstate->crc.source != DPU_CRC_SRC_NONE; +} + +static inline bool to_disable_dpu_crc(struct dpu_crtc_state *new_dcstate, + struct dpu_crtc_state *old_dcstate) +{ + return old_dcstate->crc.source != DPU_CRC_SRC_NONE && + new_dcstate->crc.source == DPU_CRC_SRC_NONE; +} + +static inline void dpu_copy_roi(struct drm_rect *from, struct drm_rect *to) +{ + to->x1 = from->x1; + to->y1 = from->y1; + to->x2 = from->x2; + to->y2 = from->y2; +} + +#ifdef CONFIG_DEBUG_FS +int dpu_crtc_verify_crc_source(struct drm_crtc *crtc, const char *source_name, + size_t *values_cnt); +int dpu_crtc_set_crc_source(struct drm_crtc *crtc, const char *source_name); +irqreturn_t dpu_crc_valid_irq_threaded_handler(int irq, void *dev_id); +void dpu_crtc_enable_crc_source(struct drm_crtc *crtc, + enum dpu_crc_source source, + struct drm_rect *roi); +void dpu_crtc_disable_crc_source(struct drm_crtc *crtc, bool dual_crc); +#else +#define dpu_crtc_verify_crc_source NULL +#define dpu_crtc_set_crc_source NULL +irqreturn_t dpu_crc_valid_irq_threaded_handler(int irq, void *dev_id) +{ + return IRQ_HANDLED; +} +void dpu_crtc_enable_crc_source(struct drm_crtc *crtc, + enum dpu_crc_source source, + struct drm_rect *roi) +{ +} +void dpu_crtc_disable_crc_source(struct drm_crtc *crtc, bool dual_crc) +{ +} +#endif + +#endif diff --git a/drivers/gpu/drm/imx/dpu/dpu-crtc.c b/drivers/gpu/drm/imx/dpu/dpu-crtc.c new file mode 100644 index 000000000000..f0a388bdffac --- /dev/null +++ b/drivers/gpu/drm/imx/dpu/dpu-crtc.c @@ -0,0 +1,1466 @@ +/* + * Copyright 2017-2022 NXP + * + * 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 <drm/drm_vblank.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <linux/component.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/export.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <video/dpu.h> +#include <video/imx8-pc.h> +#include <video/imx8-prefetch.h> +#include "dpu-crc.h" +#include "dpu-crtc.h" +#include "dpu-kms.h" +#include "dpu-plane.h" +#include "../imx-drm.h" + +static inline struct dpu_plane_state ** +alloc_dpu_plane_states(struct dpu_crtc *dpu_crtc) +{ + struct dpu_plane_state **states; + + states = kcalloc(dpu_crtc->hw_plane_num, sizeof(*states), GFP_KERNEL); + if (!states) + return ERR_PTR(-ENOMEM); + + return states; +} + +static void dpu_crtc_queue_state_event(struct drm_crtc *crtc) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event) { + WARN_ON(drm_crtc_vblank_get(crtc)); + WARN_ON(dpu_crtc->event); + dpu_crtc->event = crtc->state->event; + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); +} + +struct dpu_plane_state ** +crtc_state_get_dpu_plane_states(struct drm_crtc_state *state) +{ + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(state); + struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state); + + return dcstate->dpu_plane_states; +} + +struct dpu_crtc *dpu_crtc_get_aux_dpu_crtc(struct dpu_crtc *dpu_crtc) +{ + struct drm_crtc *crtc = &dpu_crtc->base, *tmp_crtc; + struct drm_device *dev = crtc->dev; + struct dpu_crtc *aux_dpu_crtc = NULL; + + drm_for_each_crtc(tmp_crtc, dev) { + if (tmp_crtc == crtc) + continue; + + aux_dpu_crtc = to_dpu_crtc(tmp_crtc); + + if (dpu_crtc->crtc_grp_id == aux_dpu_crtc->crtc_grp_id) + break; + } + + BUG_ON(!aux_dpu_crtc); + + return aux_dpu_crtc; +} + +static void dpu_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct dpu_crtc *aux_dpu_crtc = dpu_crtc_get_aux_dpu_crtc(dpu_crtc); + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc->state); + struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state); + struct dpu_plane *dplane = to_dpu_plane(crtc->primary); + struct dpu_plane_res *res = &dplane->grp->res; + struct dpu_extdst *plane_ed = res->ed[dplane->stream_id]; + struct dpu_extdst *aux_plane_ed = dpu_aux_ed_peek(plane_ed); + struct dpu_extdst *m_plane_ed = NULL, *s_plane_ed; + struct completion *shdld_done; + struct completion *m_safety_shdld_done, *s_safety_shdld_done; + struct completion *m_content_shdld_done, *s_content_shdld_done; + struct completion *m_dec_shdld_done, *s_dec_shdld_done; + unsigned long ret, flags; + + drm_crtc_vblank_on(crtc); + + if (dcstate->use_pc) { + tcon_enable_pc(dpu_crtc->tcon); + + if (extdst_is_master(plane_ed)) { + m_plane_ed = plane_ed; + s_plane_ed = aux_plane_ed; + } else { + m_plane_ed = aux_plane_ed; + s_plane_ed = plane_ed; + } + extdst_pixengcfg_syncmode_master(m_plane_ed, true); + extdst_pixengcfg_syncmode_master(s_plane_ed, false); + } else { + extdst_pixengcfg_syncmode_master(plane_ed, false); + } + + enable_irq(dpu_crtc->safety_shdld_irq); + enable_irq(dpu_crtc->content_shdld_irq); + enable_irq(dpu_crtc->dec_shdld_irq); + if (dcstate->use_pc) { + enable_irq(aux_dpu_crtc->safety_shdld_irq); + enable_irq(aux_dpu_crtc->content_shdld_irq); + enable_irq(aux_dpu_crtc->dec_shdld_irq); + } + + if (dcstate->use_pc) { + framegen_enable_clock(dpu_crtc->stream_id ? + dpu_crtc->aux_fg : dpu_crtc->fg); + extdst_pixengcfg_sync_trigger(m_plane_ed); + framegen_shdtokgen(dpu_crtc->m_fg); + + /* don't relinquish CPU until TCONs are set to operation mode */ + local_irq_save(flags); + preempt_disable(); + /* First turn on the slave stream, second the master stream. */ + framegen_enable(dpu_crtc->s_fg); + framegen_enable(dpu_crtc->m_fg); + /* + * TKT320590: + * Turn TCONs into operation mode as soon as the first dumb + * frame is generated by DPU from the master stream(we don't + * relinquish CPU to ensure this). This makes DPRs/PRGs of + * the dual stream be able to evade the dumb frames of the + * dual stream respectively. + */ + framegen_wait_for_frame_counter_moving(dpu_crtc->m_fg); + /* again, slave first, then master */ + tcon_set_operation_mode(dpu_crtc->s_tcon); + tcon_set_operation_mode(dpu_crtc->m_tcon); + local_irq_restore(flags); + preempt_enable(); + + framegen_enable_pixel_link(dpu_crtc->s_fg); + framegen_enable_pixel_link(dpu_crtc->m_fg); + + if (dpu_crtc->aux_is_master) { + m_safety_shdld_done = &aux_dpu_crtc->safety_shdld_done; + m_content_shdld_done = &aux_dpu_crtc->content_shdld_done; + m_dec_shdld_done = &aux_dpu_crtc->dec_shdld_done; + s_safety_shdld_done = &dpu_crtc->safety_shdld_done; + s_content_shdld_done = &dpu_crtc->content_shdld_done; + s_dec_shdld_done = &dpu_crtc->dec_shdld_done; + } else { + m_safety_shdld_done = &dpu_crtc->safety_shdld_done; + m_content_shdld_done = &dpu_crtc->content_shdld_done; + m_dec_shdld_done = &dpu_crtc->dec_shdld_done; + s_safety_shdld_done = &aux_dpu_crtc->safety_shdld_done; + s_content_shdld_done = &aux_dpu_crtc->content_shdld_done; + s_dec_shdld_done = &aux_dpu_crtc->dec_shdld_done; + } + + ret = wait_for_completion_timeout(m_safety_shdld_done, HZ); + if (ret == 0) + DRM_WARN("[CRTC:%d:%s] %s: wait for master safety shdld done timeout\n", + crtc->base.id, crtc->name, __func__); + ret = wait_for_completion_timeout(m_content_shdld_done, HZ); + if (ret == 0) + DRM_WARN("[CRTC:%d:%s] %s: wait for master content shdld done timeout\n", + crtc->base.id, crtc->name, __func__); + ret = wait_for_completion_timeout(m_dec_shdld_done, HZ); + if (ret == 0) + DRM_WARN("[CRTC:%d:%s] %s: wait for master DEC shdld done timeout\n", + crtc->base.id, crtc->name, __func__); + + ret = wait_for_completion_timeout(s_safety_shdld_done, HZ); + if (ret == 0) + DRM_WARN("[CRTC:%d:%s] %s: wait for slave safety shdld done timeout\n", + crtc->base.id, crtc->name, __func__); + ret = wait_for_completion_timeout(s_content_shdld_done, HZ); + if (ret == 0) + DRM_WARN("[CRTC:%d:%s] %s: wait for slave content shdld done timeout\n", + crtc->base.id, crtc->name, __func__); + ret = wait_for_completion_timeout(s_dec_shdld_done, HZ); + if (ret == 0) + DRM_WARN("[CRTC:%d:%s] %s: wait for slave DEC shdld done timeout\n", + crtc->base.id, crtc->name, __func__); + } else { + framegen_enable_clock(dpu_crtc->fg); + extdst_pixengcfg_sync_trigger(plane_ed); + extdst_pixengcfg_sync_trigger(dpu_crtc->ed); + framegen_shdtokgen(dpu_crtc->fg); + + /* don't relinquish CPU until TCON is set to operation mode */ + local_irq_save(flags); + preempt_disable(); + framegen_enable(dpu_crtc->fg); + /* + * TKT320590: + * Turn TCON into operation mode as soon as the first dumb + * frame is generated by DPU(we don't relinquish CPU to ensure + * this). This makes DPR/PRG be able to evade the frame. + */ + framegen_wait_for_frame_counter_moving(dpu_crtc->fg); + tcon_set_operation_mode(dpu_crtc->tcon); + local_irq_restore(flags); + preempt_enable(); + + framegen_enable_pixel_link(dpu_crtc->fg); + + shdld_done = &dpu_crtc->safety_shdld_done; + ret = wait_for_completion_timeout(shdld_done, HZ); + if (ret == 0) + DRM_WARN("[CRTC:%d:%s] %s: wait for safety shdld done timeout\n", + crtc->base.id, crtc->name, __func__); + shdld_done = &dpu_crtc->content_shdld_done; + ret = wait_for_completion_timeout(shdld_done, HZ); + if (ret == 0) + DRM_WARN("[CRTC:%d:%s] %s: wait for content shdld done timeout\n", + crtc->base.id, crtc->name, __func__); + shdld_done = &dpu_crtc->dec_shdld_done; + ret = wait_for_completion_timeout(shdld_done, HZ); + if (ret == 0) + DRM_WARN("[CRTC:%d:%s] %s: wait for DEC shdld done timeout\n", + crtc->base.id, crtc->name, __func__); + } + + disable_irq(dpu_crtc->safety_shdld_irq); + disable_irq(dpu_crtc->content_shdld_irq); + disable_irq(dpu_crtc->dec_shdld_irq); + if (dcstate->use_pc) { + disable_irq(aux_dpu_crtc->safety_shdld_irq); + disable_irq(aux_dpu_crtc->content_shdld_irq); + disable_irq(aux_dpu_crtc->dec_shdld_irq); + } + + dpu_crtc_queue_state_event(crtc); + + if (dcstate->use_pc) { + framegen_wait_for_secondary_syncup(dpu_crtc->m_fg); + framegen_wait_for_secondary_syncup(dpu_crtc->s_fg); + + if (framegen_secondary_requests_to_read_empty_fifo(dpu_crtc->m_fg)) { + framegen_secondary_clear_channel_status(dpu_crtc->m_fg); + DRM_WARN("[CRTC:%d:%s] %s: master FrameGen requests to read empty FIFO\n", + crtc->base.id, crtc->name, __func__); + } + if (framegen_secondary_requests_to_read_empty_fifo(dpu_crtc->s_fg)) { + framegen_secondary_clear_channel_status(dpu_crtc->s_fg); + DRM_WARN("[CRTC:%d:%s] %s: slave FrameGen requests to read empty FIFO\n", + crtc->base.id, crtc->name, __func__); + } + } else { + framegen_wait_for_secondary_syncup(dpu_crtc->fg); + + if (framegen_secondary_requests_to_read_empty_fifo(dpu_crtc->fg)) { + framegen_secondary_clear_channel_status(dpu_crtc->fg); + DRM_WARN("[CRTC:%d:%s] %s: FrameGen requests to read empty FIFO\n", + crtc->base.id, crtc->name, __func__); + } + } + + if (dcstate->crc.source != DPU_CRC_SRC_NONE) + dpu_crtc_enable_crc_source(crtc, + dcstate->crc.source, &dcstate->crc.roi); +} + +static void dpu_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct drm_crtc_state *old_crtc_state = + drm_atomic_get_old_crtc_state(state, crtc); + struct imx_crtc_state *imx_crtc_state = + to_imx_crtc_state(old_crtc_state); + struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state); + struct drm_display_mode *adjusted_mode = &old_crtc_state->adjusted_mode; + struct dpu_plane *dplane = to_dpu_plane(crtc->primary); + struct dpu_plane_res *res = &dplane->grp->res; + struct dpu_plane_state *dpstate; + struct dpu_fetchunit *fu; + unsigned long flags; + int i; + + if (dcstate->crc.source != DPU_CRC_SRC_NONE) + dpu_crtc_disable_crc_source(crtc, dcstate->use_pc); + + if (dcstate->use_pc) { + tcon_disable_pc(dpu_crtc->tcon); + + framegen_disable_pixel_link(dpu_crtc->m_fg); + framegen_disable_pixel_link(dpu_crtc->s_fg); + + /* don't relinquish CPU until DPRC repeat_en is disabled */ + local_irq_save(flags); + preempt_disable(); + /* + * Sync to FrameGen frame counter moving so that + * FrameGen can be disabled in the next frame. + */ + framegen_wait_for_frame_counter_moving(dpu_crtc->m_fg); + + /* First turn off the master stream, second the slave stream. */ + framegen_disable(dpu_crtc->m_fg); + framegen_disable(dpu_crtc->s_fg); + + /* + * There is one frame leftover after FrameGen disablement. + * Sync to FrameGen frame counter moving so that + * DPRC repeat_en can be disabled in the next frame. + */ + framegen_wait_for_frame_counter_moving(dpu_crtc->m_fg); + + for (i = 0; i < dpu_crtc->hw_plane_num; i++) { + lb_sec_sel_t source; + bool aux_source_flag; + bool use_prefetch; + + dpstate = dcstate->dpu_plane_states[i]; + if (!dpstate) + continue; + + aux_source_flag = false; +again: + source = aux_source_flag ? dpstate->aux_source : + dpstate->source; + use_prefetch = aux_source_flag ? + dpstate->use_aux_prefetch : + dpstate->use_prefetch; + fu = source_to_fu(res, source); + if (!fu) { + local_irq_restore(flags); + preempt_enable(); + return; + } + + if (fu->dprc && use_prefetch) + dprc_disable_repeat_en(fu->dprc); + + if (dpstate->need_aux_source && !aux_source_flag) { + aux_source_flag = true; + goto again; + } + } + local_irq_restore(flags); + preempt_enable(); + + framegen_wait_done(dpu_crtc->m_fg, adjusted_mode); + framegen_wait_done(dpu_crtc->s_fg, adjusted_mode); + + framegen_disable_clock(dpu_crtc->stream_id ? + dpu_crtc->aux_fg : dpu_crtc->fg); + } else { + framegen_disable_pixel_link(dpu_crtc->fg); + + /* don't relinquish CPU until DPRC repeat_en is disabled */ + local_irq_save(flags); + preempt_disable(); + /* + * Sync to FrameGen frame counter moving so that + * FrameGen can be disabled in the next frame. + */ + framegen_wait_for_frame_counter_moving(dpu_crtc->fg); + framegen_disable(dpu_crtc->fg); + /* + * There is one frame leftover after FrameGen disablement. + * Sync to FrameGen frame counter moving so that + * DPRC repeat_en can be disabled in the next frame. + */ + framegen_wait_for_frame_counter_moving(dpu_crtc->fg); + + for (i = 0; i < dpu_crtc->hw_plane_num; i++) { + dpstate = dcstate->dpu_plane_states[i]; + if (!dpstate) + continue; + + fu = source_to_fu(res, dpstate->source); + if (!fu) { + local_irq_restore(flags); + preempt_enable(); + return; + } + + if (fu->dprc && dpstate->use_prefetch) + dprc_disable_repeat_en(fu->dprc); + } + local_irq_restore(flags); + preempt_enable(); + + framegen_wait_done(dpu_crtc->fg, adjusted_mode); + framegen_disable_clock(dpu_crtc->fg); + } + + drm_crtc_vblank_off(crtc); + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event && !crtc->state->active) { + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); +} + +static void dpu_drm_crtc_reset(struct drm_crtc *crtc) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct imx_crtc_state *imx_crtc_state; + struct dpu_crtc_state *state; + + if (crtc->state) { + __drm_atomic_helper_crtc_destroy_state(crtc->state); + + imx_crtc_state = to_imx_crtc_state(crtc->state); + state = to_dpu_crtc_state(imx_crtc_state); + kfree(state->dpu_plane_states); + kfree(state); + crtc->state = NULL; + } + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) { + state->crc.source = DPU_CRC_SRC_NONE; + state->crc.roi.x1 = 0; + state->crc.roi.y1 = 0; + state->crc.roi.x2 = 0; + state->crc.roi.y2 = 0; + + crtc->state = &state->imx_crtc_state.base; + crtc->state->crtc = crtc; + + state->dpu_plane_states = alloc_dpu_plane_states(dpu_crtc); + if (IS_ERR(state->dpu_plane_states)) + kfree(state); + } +} + +static struct drm_crtc_state * +dpu_drm_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct imx_crtc_state *imx_crtc_state; + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct dpu_crtc_state *state, *copy; + + if (WARN_ON(!crtc->state)) + return NULL; + + copy = kzalloc(sizeof(*copy), GFP_KERNEL); + if (!copy) + return NULL; + + copy->dpu_plane_states = alloc_dpu_plane_states(dpu_crtc); + if (IS_ERR(copy->dpu_plane_states)) { + kfree(copy); + return NULL; + } + + __drm_atomic_helper_crtc_duplicate_state(crtc, + ©->imx_crtc_state.base); + imx_crtc_state = to_imx_crtc_state(crtc->state); + state = to_dpu_crtc_state(imx_crtc_state); + copy->use_pc = state->use_pc; + copy->crc.source = state->crc.source; + dpu_copy_roi(&state->crc.roi, ©->crc.roi); + + return ©->imx_crtc_state.base; +} + +static void dpu_drm_crtc_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(state); + struct dpu_crtc_state *dcstate; + + if (state) { + __drm_atomic_helper_crtc_destroy_state(state); + dcstate = to_dpu_crtc_state(imx_crtc_state); + kfree(dcstate->dpu_plane_states); + kfree(dcstate); + } +} + +static int dpu_enable_vblank(struct drm_crtc *crtc) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + + enable_irq(dpu_crtc->vbl_irq); + + return 0; +} + +static void dpu_disable_vblank(struct drm_crtc *crtc) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + + disable_irq_nosync(dpu_crtc->vbl_irq); +} + +static const struct drm_crtc_funcs dpu_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .destroy = drm_crtc_cleanup, + .page_flip = drm_atomic_helper_page_flip, + .reset = dpu_drm_crtc_reset, + .atomic_duplicate_state = dpu_drm_crtc_duplicate_state, + .atomic_destroy_state = dpu_drm_crtc_destroy_state, + .enable_vblank = dpu_enable_vblank, + .disable_vblank = dpu_disable_vblank, + .set_crc_source = dpu_crtc_set_crc_source, + .verify_crc_source = dpu_crtc_verify_crc_source, +}; + +static irqreturn_t dpu_vbl_irq_handler(int irq, void *dev_id) +{ + struct dpu_crtc *dpu_crtc = dev_id; + struct drm_crtc *crtc = &dpu_crtc->base; + unsigned long flags; + + drm_crtc_handle_vblank(crtc); + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + if (dpu_crtc->event) { + drm_crtc_send_vblank_event(crtc, dpu_crtc->event); + dpu_crtc->event = NULL; + drm_crtc_vblank_put(crtc); + } + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + + return IRQ_HANDLED; +} + +static irqreturn_t dpu_safety_shdld_irq_handler(int irq, void *dev_id) +{ + struct dpu_crtc *dpu_crtc = dev_id; + + complete(&dpu_crtc->safety_shdld_done); + + return IRQ_HANDLED; +} + +static irqreturn_t dpu_content_shdld_irq_handler(int irq, void *dev_id) +{ + struct dpu_crtc *dpu_crtc = dev_id; + + complete(&dpu_crtc->content_shdld_done); + + return IRQ_HANDLED; +} + +static irqreturn_t dpu_dec_shdld_irq_handler(int irq, void *dev_id) +{ + struct dpu_crtc *dpu_crtc = dev_id; + + complete(&dpu_crtc->dec_shdld_done); + + return IRQ_HANDLED; +} + +static irqreturn_t dpu_crc_shdld_irq_handler(int irq, void *dev_id) +{ + struct dpu_crtc *dpu_crtc = dev_id; + + complete(&dpu_crtc->crc_shdld_done); + + return IRQ_HANDLED; +} + +static int dpu_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_device *dev = crtc->dev; + struct drm_encoder *encoder; + struct drm_plane *plane; + struct drm_plane_state *plane_state; + struct dpu_plane_state *dpstate; + struct drm_crtc_state *crtc_state = + drm_atomic_get_new_crtc_state(state, crtc); + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); + struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state); + struct imx_crtc_state *old_imx_crtc_state = + to_imx_crtc_state(crtc->state); + struct dpu_crtc_state *old_dcstate = + to_dpu_crtc_state(old_imx_crtc_state); + struct drm_display_mode *mode = &crtc_state->adjusted_mode; + struct videomode vm; + unsigned long encoder_type = DRM_MODE_ENCODER_NONE; + u32 encoder_mask; + int i = 0; + + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + encoder_mask = 1 << drm_encoder_index(encoder); + + if (!(crtc_state->encoder_mask & encoder_mask)) + continue; + + encoder_type = encoder->encoder_type; + } + + if (crtc_state->enable && dcstate->use_pc) { + if (encoder_type != DRM_MODE_ENCODER_TMDS) { + DRM_DEBUG_KMS("[CRTC:%d:%s] enc type %lu doesn't support pc\n", + crtc->base.id, crtc->name, encoder_type); + return -EINVAL; + } + + drm_display_mode_to_videomode(mode, &vm); + if ((vm.hactive % 2) || (vm.hfront_porch % 2) || + (vm.hsync_len % 2) || (vm.hback_porch % 2)) { + DRM_DEBUG_KMS("[CRTC:%d:%s] video mode is invalid\n", + crtc->base.id, crtc->name); + return -EINVAL; + } + } + + /* disallow to enable CRC when CRTC keeps at inactive status */ + if (!crtc->state->active && !crtc_state->enable && + to_enable_dpu_crc(dcstate, old_dcstate)) + return -EINVAL; + + if (crtc_state->enable && dcstate->crc.source == DPU_CRC_SRC_FRAMEGEN) { + dcstate->crc.roi.x1 = 0; + dcstate->crc.roi.y1 = 0; + dcstate->crc.roi.x2 = mode->hdisplay; + dcstate->crc.roi.y2 = mode->vdisplay; + } + + if (crtc_state->enable && dcstate->crc.source != DPU_CRC_SRC_NONE) { + if (dcstate->crc.roi.x1 < 0 || dcstate->crc.roi.y1 < 0) + return -EINVAL; + + if (dcstate->crc.roi.x2 > mode->hdisplay || + dcstate->crc.roi.y2 > mode->vdisplay) + return -EINVAL; + + if (!drm_rect_visible(&dcstate->crc.roi)) + return -EINVAL; + } + + /* + * cache the plane states so that the planes can be disabled in + * ->atomic_begin. + */ + drm_for_each_plane_mask(plane, crtc->dev, crtc_state->plane_mask) { + plane_state = + drm_atomic_get_plane_state(crtc_state->state, plane); + if (IS_ERR(plane_state)) + return PTR_ERR(plane_state); + + dpstate = to_dpu_plane_state(plane_state); + dcstate->dpu_plane_states[i++] = dpstate; + } + + return 0; +} + +static void dpu_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct drm_crtc_state *old_crtc_state = + drm_atomic_get_old_crtc_state(state, crtc); + struct imx_crtc_state *old_imx_crtc_state = + to_imx_crtc_state(old_crtc_state); + struct dpu_crtc_state *old_dcstate = + to_dpu_crtc_state(old_imx_crtc_state); + int i; + + /* + * Disable all planes' resources in SHADOW only. + * Whether any of them would be disabled or kept running depends + * on new plane states' commit. + */ + for (i = 0; i < dpu_crtc->hw_plane_num; i++) { + struct dpu_plane_state *old_dpstate; + struct drm_plane_state *plane_state; + struct dpu_plane *dplane; + struct dpu_plane_res *res; + struct dpu_fetchunit *fu; + struct dpu_fetchunit *fe = NULL; + struct dpu_hscaler *hs = NULL; + struct dpu_vscaler *vs = NULL; + struct dpu_layerblend *lb; + struct dpu_extdst *ed; + extdst_src_sel_t ed_src; + dpu_block_id_t blend; + lb_sec_sel_t source; + unsigned int stream_id; + int lb_id; + bool release_aux_source; + + old_dpstate = old_dcstate->dpu_plane_states[i]; + if (!old_dpstate) + continue; + + plane_state = &old_dpstate->base; + dplane = to_dpu_plane(plane_state->plane); + res = &dplane->grp->res; + + release_aux_source = false; +again: + if (old_dcstate->use_pc) { + if (release_aux_source) { + source = old_dpstate->aux_source; + blend = old_dpstate->aux_blend; + stream_id = 1; + } else { + source = old_dpstate->source; + blend = old_dpstate->blend; + stream_id = old_dpstate->left_src_w ? 0 : 1; + } + } else { + source = old_dpstate->source; + blend = old_dpstate->blend; + stream_id = dplane->stream_id; + } + + fu = source_to_fu(res, source); + if (!fu) + return; + + lb_id = blend_to_id(blend); + if (lb_id < 0) + return; + + lb = res->lb[lb_id]; + + layerblend_pixengcfg_clken(lb, CLKEN__DISABLE); + if (fetchunit_is_fetchdecode(fu)) { + fe = fetchdecode_get_fetcheco(fu); + hs = fetchdecode_get_hscaler(fu); + vs = fetchdecode_get_vscaler(fu); + hscaler_pixengcfg_clken(hs, CLKEN__DISABLE); + vscaler_pixengcfg_clken(vs, CLKEN__DISABLE); + hscaler_mode(hs, SCALER_NEUTRAL); + vscaler_mode(vs, SCALER_NEUTRAL); + } + if ((!old_dcstate->use_pc && old_dpstate->is_top) || + (old_dcstate->use_pc && + ((!stream_id && old_dpstate->is_left_top) || + (stream_id && old_dpstate->is_right_top)))) { + ed = res->ed[stream_id]; + ed_src = stream_id ? + ED_SRC_CONSTFRAME1 : ED_SRC_CONSTFRAME0; + extdst_pixengcfg_src_sel(ed, ed_src); + } + + fu->ops->disable_src_buf(fu); + if (fetchunit_is_fetchdecode(fu)) { + fetchdecode_pixengcfg_dynamic_src_sel(fu, + FD_SRC_DISABLE); + fe->ops->disable_src_buf(fe); + } + + if (old_dpstate->need_aux_source && !release_aux_source) { + release_aux_source = true; + goto again; + } + } +} + +static void dpu_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc), *aux_dpu_crtc = NULL; + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc->state); + struct drm_crtc_state *old_crtc_state = + drm_atomic_get_old_crtc_state(state, crtc); + struct imx_crtc_state *old_imx_crtc_state = + to_imx_crtc_state(old_crtc_state); + struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state); + struct dpu_crtc_state *old_dcstate = + to_dpu_crtc_state(old_imx_crtc_state); + struct dpu_plane *dplane = to_dpu_plane(crtc->primary); + struct dpu_plane_state *old_dpstate; + struct dpu_plane_res *res = &dplane->grp->res; + struct dpu_extdst *ed = res->ed[dplane->stream_id], *aux_ed; + struct dpu_fetchunit *fu; + lb_sec_sel_t source; + struct completion *shdld_done; + struct completion *m_content_shdld_done = NULL; + struct completion *s_content_shdld_done = NULL; + unsigned long ret, flags; + int i; + bool need_modeset = drm_atomic_crtc_needs_modeset(crtc->state); + bool need_wait4fgfcm = false, need_aux_wait4fgfcm = false; + bool use_prefetch; + + if (!crtc->state->active && !old_crtc_state->active) + return; + + if (!need_modeset && to_disable_dpu_crc(dcstate, old_dcstate)) + dpu_crtc_disable_crc_source(crtc, old_dcstate->use_pc); + + /* + * Scan over old plane fetchunits to determine if we + * need to wait for FrameGen frame counter moving in + * the next loop prior to DPRC repeat_en disablement + * or not. + */ + for (i = 0; i < dpu_crtc->hw_plane_num; i++) { + bool aux_source_flag; + + old_dpstate = old_dcstate->dpu_plane_states[i]; + if (!old_dpstate) + continue; + + aux_source_flag = false; +again1: + source = aux_source_flag ? + old_dpstate->aux_source : old_dpstate->source; + use_prefetch = aux_source_flag ? + old_dpstate->use_aux_prefetch : + old_dpstate->use_prefetch; + fu = source_to_fu(res, source); + if (!fu) + return; + + if (!fu->ops->is_enabled(fu) && use_prefetch && !need_modeset) { + if (aux_source_flag) + need_aux_wait4fgfcm = true; + else + need_wait4fgfcm = true; + } + + if (old_dpstate->need_aux_source && !aux_source_flag) { + aux_source_flag = true; + goto again1; + } + } + + /* + * Sync with FrameGen frame counter moving so that we may disable + * repeat_en of DPRC(s) and fetchunit(s) correctly. + */ + if (need_wait4fgfcm || need_aux_wait4fgfcm) { + /* + * don't relinquish CPU until DPRC repeat_en is disabled and + * ExtDst shadow load is initiated to disable fetchunit(s). + */ + local_irq_save(flags); + preempt_disable(); + framegen_wait_for_frame_counter_moving(dcstate->use_pc ? + dpu_crtc->m_fg : + dpu_crtc->fg); + } + + for (i = 0; i < dpu_crtc->hw_plane_num; i++) { + struct dpu_fetchunit *fe; + struct dpu_hscaler *hs; + struct dpu_vscaler *vs; + bool aux_source_disable; + + old_dpstate = old_dcstate->dpu_plane_states[i]; + if (!old_dpstate) + continue; + + aux_source_disable = false; +again2: + source = aux_source_disable ? + old_dpstate->aux_source : old_dpstate->source; + use_prefetch = aux_source_disable ? + old_dpstate->use_aux_prefetch : + old_dpstate->use_prefetch; + fu = source_to_fu(res, source); + if (!fu) { + if (need_wait4fgfcm || need_aux_wait4fgfcm) { + local_irq_restore(flags); + preempt_enable(); + } + return; + } + + if (!fu->ops->is_enabled(fu)) { + fu->ops->set_stream_id(fu, DPU_PLANE_SRC_DISABLED); + if (fu->dprc && use_prefetch) + dprc_disable_repeat_en(fu->dprc); + } + + if (!fetchunit_is_fetchdecode(fu)) + continue; + + fe = fetchdecode_get_fetcheco(fu); + if (!fe->ops->is_enabled(fe)) + fe->ops->set_stream_id(fe, DPU_PLANE_SRC_DISABLED); + + hs = fetchdecode_get_hscaler(fu); + if (!hscaler_is_enabled(hs)) + hscaler_set_stream_id(hs, DPU_PLANE_SRC_DISABLED); + + vs = fetchdecode_get_vscaler(fu); + if (!vscaler_is_enabled(vs)) + vscaler_set_stream_id(vs, DPU_PLANE_SRC_DISABLED); + + if (old_dpstate->need_aux_source && !aux_source_disable) { + aux_source_disable = true; + goto again2; + } + } + + if (dcstate->use_pc) { + aux_dpu_crtc = dpu_crtc_get_aux_dpu_crtc(dpu_crtc); + + if (dpu_crtc->aux_is_master) { + m_content_shdld_done = &aux_dpu_crtc->content_shdld_done; + s_content_shdld_done = &dpu_crtc->content_shdld_done; + } else { + m_content_shdld_done = &dpu_crtc->content_shdld_done; + s_content_shdld_done = &aux_dpu_crtc->content_shdld_done; + } + } + + if (!need_modeset) { + enable_irq(dpu_crtc->content_shdld_irq); + if (dcstate->use_pc) + enable_irq(aux_dpu_crtc->content_shdld_irq); + + if (dcstate->use_pc) { + if (extdst_is_master(ed)) { + extdst_pixengcfg_sync_trigger(ed); + } else { + aux_ed = dpu_aux_ed_peek(ed); + extdst_pixengcfg_sync_trigger(aux_ed); + } + } else { + extdst_pixengcfg_sync_trigger(ed); + } + + if (need_wait4fgfcm || need_aux_wait4fgfcm) { + local_irq_restore(flags); + preempt_enable(); + } + + if (dcstate->use_pc) { + shdld_done = m_content_shdld_done; + ret = wait_for_completion_timeout(shdld_done, HZ); + if (ret == 0) + DRM_WARN("[CRTC:%d:%s] %s: wait for master content shdld done timeout\n", + crtc->base.id, crtc->name, __func__); + + shdld_done = s_content_shdld_done; + ret = wait_for_completion_timeout(shdld_done, HZ); + if (ret == 0) + DRM_WARN("[CRTC:%d:%s] %s: wait for slave content shdld done timeout\n", + crtc->base.id, crtc->name, __func__); + } else { + shdld_done = &dpu_crtc->content_shdld_done; + ret = wait_for_completion_timeout(shdld_done, HZ); + if (ret == 0) + DRM_WARN("[CRTC:%d:%s] %s: wait for content shdld done timeout\n", + crtc->base.id, crtc->name, __func__); + } + + disable_irq(dpu_crtc->content_shdld_irq); + if (dcstate->use_pc) + disable_irq(aux_dpu_crtc->content_shdld_irq); + + if (dcstate->use_pc) { + if (framegen_secondary_requests_to_read_empty_fifo(dpu_crtc->m_fg)) { + framegen_secondary_clear_channel_status(dpu_crtc->m_fg); + DRM_WARN("[CRTC:%d:%s] %s: master FrameGen requests to read empty FIFO\n", + crtc->base.id, crtc->name, __func__); + } + if (framegen_secondary_requests_to_read_empty_fifo(dpu_crtc->s_fg)) { + framegen_secondary_clear_channel_status(dpu_crtc->s_fg); + DRM_WARN("[CRTC:%d:%s] %s: slave FrameGen requests to read empty FIFO\n", + crtc->base.id, crtc->name, __func__); + } + } else { + if (framegen_secondary_requests_to_read_empty_fifo(dpu_crtc->fg)) { + framegen_secondary_clear_channel_status(dpu_crtc->fg); + DRM_WARN("[CRTC:%d:%s] %s: FrameGen requests to read empty FIFO\n", + crtc->base.id, crtc->name, __func__); + } + } + + dpu_crtc_queue_state_event(crtc); + } else if (!crtc->state->active) { + if (old_dcstate->use_pc) { + if (extdst_is_master(ed)) { + extdst_pixengcfg_sync_trigger(ed); + } else { + aux_ed = dpu_aux_ed_peek(ed); + extdst_pixengcfg_sync_trigger(aux_ed); + } + } else { + extdst_pixengcfg_sync_trigger(ed); + } + } + + if (!need_modeset && to_enable_dpu_crc(dcstate, old_dcstate)) + dpu_crtc_enable_crc_source(crtc, + dcstate->crc.source, &dcstate->crc.roi); +} + +static void dpu_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct dpu_crtc *aux_dpu_crtc = dpu_crtc_get_aux_dpu_crtc(dpu_crtc); + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc->state); + struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state); + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct dpu_plane *dplane = to_dpu_plane(crtc->primary); + struct dpu_plane_res *res = &dplane->grp->res; + struct dpu_constframe *pa_cf, *sa_cf; + struct dpu_disengcfg *dec; + struct dpu_extdst *ed, *plane_ed; + struct dpu_framegen *fg; + struct dpu_tcon *tcon; + struct dpu_store *st; + struct drm_encoder *encoder; + unsigned long encoder_type = DRM_MODE_ENCODER_NONE; + unsigned int stream_id; + int crtc_hdisplay = dcstate->use_pc ? + (mode->crtc_hdisplay >> 1) : mode->crtc_hdisplay; + extdst_src_sel_t ed_src; + bool cfg_aux_pipe = false; + + DRM_DEBUG_KMS("[CRTC:%d:%s] %s: mode->hdisplay: %d\n", + crtc->base.id, crtc->name, __func__, mode->hdisplay); + DRM_DEBUG_KMS("[CRTC:%d:%s] %s: mode->vdisplay: %d\n", + crtc->base.id, crtc->name, __func__, mode->vdisplay); + DRM_DEBUG_KMS("[CRTC:%d:%s] %s: mode->clock: %dKHz\n", + crtc->base.id, crtc->name, __func__, mode->clock); + DRM_DEBUG_KMS("[CRTC:%d:%s] %s: mode->vrefresh: %dHz\n", + crtc->base.id, crtc->name, __func__, + drm_mode_vrefresh(mode)); + if (dcstate->use_pc) + DRM_DEBUG_KMS("[CRTC:%d:%s] %s: use pixel combiner\n", + crtc->base.id, crtc->name, __func__); + + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + if (encoder->crtc == crtc) { + encoder_type = encoder->encoder_type; + break; + } + } + +again: + if (cfg_aux_pipe) { + pa_cf = dpu_crtc->aux_pa_cf; + sa_cf = dpu_crtc->aux_sa_cf; + dec = dpu_crtc->aux_dec; + ed = dpu_crtc->aux_ed; + fg = dpu_crtc->aux_fg; + tcon = dpu_crtc->aux_tcon; + st = aux_dpu_crtc->st; + stream_id = dpu_crtc->stream_id ^ 1; + } else { + pa_cf = dpu_crtc->pa_cf; + sa_cf = dpu_crtc->sa_cf; + dec = dpu_crtc->dec; + ed = dpu_crtc->ed; + fg = dpu_crtc->fg; + tcon = dpu_crtc->tcon; + st = dpu_crtc->st; + stream_id = dpu_crtc->stream_id; + } + + if (dcstate->use_pc) { + store_pixengcfg_syncmode_fixup(st, true); + framegen_syncmode_fixup(fg, + framegen_is_master(fg) ? false : true); + framegen_syncmode(fg, framegen_is_master(fg) ? + FGSYNCMODE__MASTER : FGSYNCMODE__SLAVE_ONCE); + } else { + store_pixengcfg_syncmode_fixup(st, false); + framegen_syncmode_fixup(fg, false); + framegen_syncmode(fg, FGSYNCMODE__OFF); + } + + framegen_cfg_videomode(fg, mode, dcstate->use_pc, encoder_type); + framegen_displaymode(fg, FGDM__SEC_ON_TOP); + + framegen_panic_displaymode(fg, FGDM__TEST); + + tcon_cfg_videomode(tcon, mode, dcstate->use_pc); + tcon_set_fmt(tcon, imx_crtc_state->bus_format); + tcon_configure_pc(tcon, stream_id, mode->crtc_hdisplay, + dcstate->use_pc ? PC_COMBINE : PC_BYPASS, 0); + + constframe_framedimensions(pa_cf, crtc_hdisplay, mode->crtc_vdisplay); + constframe_framedimensions(sa_cf, crtc_hdisplay, mode->crtc_vdisplay); + constframe_constantcolor(sa_cf, 0, 0, 0, 0); + + ed_src = stream_id ? ED_SRC_CONSTFRAME5 : ED_SRC_CONSTFRAME4; + extdst_pixengcfg_src_sel(ed, ed_src); + + plane_ed = res->ed[stream_id]; + ed_src = stream_id ? ED_SRC_CONSTFRAME1 : ED_SRC_CONSTFRAME0; + extdst_pixengcfg_src_sel(plane_ed, ed_src); + + if (dcstate->use_pc && !cfg_aux_pipe) { + cfg_aux_pipe = true; + goto again; + } +} + +static const struct drm_crtc_helper_funcs dpu_helper_funcs = { + .mode_set_nofb = dpu_crtc_mode_set_nofb, + .atomic_check = dpu_crtc_atomic_check, + .atomic_begin = dpu_crtc_atomic_begin, + .atomic_flush = dpu_crtc_atomic_flush, + .atomic_enable = dpu_crtc_atomic_enable, + .atomic_disable = dpu_crtc_atomic_disable, +}; + +static void dpu_crtc_put_resources(struct dpu_crtc *dpu_crtc) +{ + if (!IS_ERR_OR_NULL(dpu_crtc->pa_cf)) + dpu_cf_put(dpu_crtc->pa_cf); + if (!IS_ERR_OR_NULL(dpu_crtc->sa_cf)) + dpu_cf_put(dpu_crtc->sa_cf); + if (!IS_ERR_OR_NULL(dpu_crtc->dec)) + dpu_dec_put(dpu_crtc->dec); + if (!IS_ERR_OR_NULL(dpu_crtc->ed)) + dpu_ed_put(dpu_crtc->ed); + if (!IS_ERR_OR_NULL(dpu_crtc->fg)) + dpu_fg_put(dpu_crtc->fg); + if (!IS_ERR_OR_NULL(dpu_crtc->sig)) + dpu_sig_put(dpu_crtc->sig); + if (!IS_ERR_OR_NULL(dpu_crtc->tcon)) + dpu_tcon_put(dpu_crtc->tcon); +} + +static int dpu_crtc_get_resources(struct dpu_crtc *dpu_crtc) +{ + struct dpu_soc *dpu = dev_get_drvdata(dpu_crtc->dev->parent); + unsigned int stream_id = dpu_crtc->stream_id; + int ret; + + dpu_crtc->pa_cf = dpu_cf_get(dpu, stream_id + 4); + if (IS_ERR(dpu_crtc->pa_cf)) { + ret = PTR_ERR(dpu_crtc->pa_cf); + goto err_out; + } + dpu_crtc->aux_pa_cf = dpu_aux_cf_peek(dpu_crtc->pa_cf); + + dpu_crtc->sa_cf = dpu_cf_get(dpu, stream_id); + if (IS_ERR(dpu_crtc->sa_cf)) { + ret = PTR_ERR(dpu_crtc->sa_cf); + goto err_out; + } + dpu_crtc->aux_sa_cf = dpu_aux_cf_peek(dpu_crtc->sa_cf); + + dpu_crtc->dec = dpu_dec_get(dpu, stream_id); + if (IS_ERR(dpu_crtc->dec)) { + ret = PTR_ERR(dpu_crtc->dec); + goto err_out; + } + dpu_crtc->aux_dec = dpu_aux_dec_peek(dpu_crtc->dec); + + dpu_crtc->ed = dpu_ed_get(dpu, stream_id + 4); + if (IS_ERR(dpu_crtc->ed)) { + ret = PTR_ERR(dpu_crtc->ed); + goto err_out; + } + dpu_crtc->aux_ed = dpu_aux_ed_peek(dpu_crtc->ed); + + dpu_crtc->fg = dpu_fg_get(dpu, stream_id); + if (IS_ERR(dpu_crtc->fg)) { + ret = PTR_ERR(dpu_crtc->fg); + goto err_out; + } + dpu_crtc->aux_fg = dpu_aux_fg_peek(dpu_crtc->fg); + + dpu_crtc->sig = dpu_sig_get(dpu, stream_id); + if (IS_ERR(dpu_crtc->sig)) { + ret = PTR_ERR(dpu_crtc->sig); + goto err_out; + } + dpu_crtc->aux_sig = dpu_aux_sig_peek(dpu_crtc->sig); + + dpu_crtc->tcon = dpu_tcon_get(dpu, stream_id); + if (IS_ERR(dpu_crtc->tcon)) { + ret = PTR_ERR(dpu_crtc->tcon); + goto err_out; + } + dpu_crtc->aux_tcon = dpu_aux_tcon_peek(dpu_crtc->tcon); + + if (dpu_crtc->aux_is_master) { + dpu_crtc->m_pa_cf = dpu_crtc->aux_pa_cf; + dpu_crtc->m_sa_cf = dpu_crtc->aux_sa_cf; + dpu_crtc->m_dec = dpu_crtc->aux_dec; + dpu_crtc->m_ed = dpu_crtc->aux_ed; + dpu_crtc->m_fg = dpu_crtc->aux_fg; + dpu_crtc->m_tcon = dpu_crtc->aux_tcon; + + dpu_crtc->s_pa_cf = dpu_crtc->pa_cf; + dpu_crtc->s_sa_cf = dpu_crtc->sa_cf; + dpu_crtc->s_dec = dpu_crtc->dec; + dpu_crtc->s_ed = dpu_crtc->ed; + dpu_crtc->s_fg = dpu_crtc->fg; + dpu_crtc->s_tcon = dpu_crtc->tcon; + } else { + dpu_crtc->m_pa_cf = dpu_crtc->pa_cf; + dpu_crtc->m_sa_cf = dpu_crtc->sa_cf; + dpu_crtc->m_dec = dpu_crtc->dec; + dpu_crtc->m_ed = dpu_crtc->ed; + dpu_crtc->m_fg = dpu_crtc->fg; + dpu_crtc->m_tcon = dpu_crtc->tcon; + + dpu_crtc->s_pa_cf = dpu_crtc->aux_pa_cf; + dpu_crtc->s_sa_cf = dpu_crtc->aux_sa_cf; + dpu_crtc->s_dec = dpu_crtc->aux_dec; + dpu_crtc->s_ed = dpu_crtc->aux_ed; + dpu_crtc->s_fg = dpu_crtc->aux_fg; + dpu_crtc->s_tcon = dpu_crtc->aux_tcon; + } + + return 0; +err_out: + dpu_crtc_put_resources(dpu_crtc); + + return ret; +} + +static int dpu_crtc_init(struct dpu_crtc *dpu_crtc, + struct dpu_client_platformdata *pdata, struct drm_device *drm) +{ + struct dpu_soc *dpu = dev_get_drvdata(dpu_crtc->dev->parent); + struct device *dev = dpu_crtc->dev; + struct drm_crtc *crtc = &dpu_crtc->base; + struct dpu_plane_grp *plane_grp = pdata->plane_grp; + unsigned int stream_id = pdata->stream_id; + int i, ret; + + init_completion(&dpu_crtc->safety_shdld_done); + init_completion(&dpu_crtc->content_shdld_done); + init_completion(&dpu_crtc->dec_shdld_done); + init_completion(&dpu_crtc->crc_shdld_done); + init_completion(&dpu_crtc->aux_crc_done); + + dpu_crtc->stream_id = stream_id; + dpu_crtc->crtc_grp_id = pdata->di_grp_id; + dpu_crtc->hw_plane_num = plane_grp->hw_plane_num; + dpu_crtc->syncmode_min_prate = dpu_get_syncmode_min_prate(dpu); + dpu_crtc->singlemode_max_width = dpu_get_singlemode_max_width(dpu); + dpu_crtc->master_stream_id = dpu_get_master_stream_id(dpu); + dpu_crtc->aux_is_master = !(dpu_crtc->master_stream_id == stream_id); + dpu_crtc->st = pdata->st9; + + dpu_crtc->plane = devm_kcalloc(dev, dpu_crtc->hw_plane_num, + sizeof(*dpu_crtc->plane), GFP_KERNEL); + if (!dpu_crtc->plane) + return -ENOMEM; + + ret = dpu_crtc_get_resources(dpu_crtc); + if (ret) { + DRM_DEV_ERROR(dev, "getting resources failed with %d.\n", ret); + return ret; + } + + plane_grp->res.fg[stream_id] = dpu_crtc->fg; + dpu_crtc->plane[0] = dpu_plane_create(drm, 0, stream_id, plane_grp, + DRM_PLANE_TYPE_PRIMARY); + if (IS_ERR(dpu_crtc->plane[0])) { + ret = PTR_ERR(dpu_crtc->plane[0]); + DRM_DEV_ERROR(dev, + "initializing plane0 failed with %d.\n", ret); + goto err_put_resources; + } + + crtc->port = pdata->of_node; + drm_crtc_helper_add(crtc, &dpu_helper_funcs); + ret = drm_crtc_init_with_planes(drm, crtc, &dpu_crtc->plane[0]->base, NULL, + &dpu_crtc_funcs, NULL); + if (ret) { + DRM_DEV_ERROR(dev, "adding crtc failed with %d.\n", ret); + goto err_put_resources; + } + + for (i = 1; i < dpu_crtc->hw_plane_num; i++) { + dpu_crtc->plane[i] = dpu_plane_create(drm, + drm_crtc_mask(&dpu_crtc->base), + stream_id, plane_grp, + DRM_PLANE_TYPE_OVERLAY); + if (IS_ERR(dpu_crtc->plane[i])) { + ret = PTR_ERR(dpu_crtc->plane[i]); + DRM_DEV_ERROR(dev, + "initializing plane%d failed with %d.\n", + i, ret); + goto err_put_resources; + } + } + + dpu_crtc->vbl_irq = dpu_map_irq(dpu, stream_id ? + IRQ_DISENGCFG_FRAMECOMPLETE1 : + IRQ_DISENGCFG_FRAMECOMPLETE0); + irq_set_status_flags(dpu_crtc->vbl_irq, IRQ_DISABLE_UNLAZY); + ret = devm_request_irq(dev, dpu_crtc->vbl_irq, dpu_vbl_irq_handler, 0, + "imx_drm", dpu_crtc); + if (ret < 0) { + DRM_DEV_ERROR(dev, "vblank irq request failed with %d.\n", ret); + goto err_put_resources; + } + disable_irq(dpu_crtc->vbl_irq); + + dpu_crtc->safety_shdld_irq = dpu_map_irq(dpu, stream_id ? + IRQ_EXTDST5_SHDLOAD : IRQ_EXTDST4_SHDLOAD); + irq_set_status_flags(dpu_crtc->safety_shdld_irq, IRQ_DISABLE_UNLAZY); + ret = devm_request_irq(dev, dpu_crtc->safety_shdld_irq, + dpu_safety_shdld_irq_handler, 0, "imx_drm", + dpu_crtc); + if (ret < 0) { + DRM_DEV_ERROR(dev, + "safety shadow load irq request failed with %d.\n", + ret); + goto err_put_resources; + } + disable_irq(dpu_crtc->safety_shdld_irq); + + dpu_crtc->content_shdld_irq = dpu_map_irq(dpu, stream_id ? + IRQ_EXTDST1_SHDLOAD : IRQ_EXTDST0_SHDLOAD); + irq_set_status_flags(dpu_crtc->content_shdld_irq, IRQ_DISABLE_UNLAZY); + ret = devm_request_irq(dev, dpu_crtc->content_shdld_irq, + dpu_content_shdld_irq_handler, 0, "imx_drm", + dpu_crtc); + if (ret < 0) { + DRM_DEV_ERROR(dev, + "content shadow load irq request failed with %d.\n", + ret); + goto err_put_resources; + } + disable_irq(dpu_crtc->content_shdld_irq); + + dpu_crtc->dec_shdld_irq = dpu_map_irq(dpu, stream_id ? + IRQ_DISENGCFG_SHDLOAD1 : IRQ_DISENGCFG_SHDLOAD0); + irq_set_status_flags(dpu_crtc->dec_shdld_irq, IRQ_DISABLE_UNLAZY); + ret = devm_request_irq(dev, dpu_crtc->dec_shdld_irq, + dpu_dec_shdld_irq_handler, 0, "imx_drm", + dpu_crtc); + if (ret < 0) { + DRM_DEV_ERROR(dev, + "DEC shadow load irq request failed with %d.\n", + ret); + goto err_put_resources; + } + disable_irq(dpu_crtc->dec_shdld_irq); + + dpu_crtc->crc_valid_irq = dpu_map_irq(dpu, stream_id ? + IRQ_SIG1_VALID : IRQ_SIG0_VALID); + irq_set_status_flags(dpu_crtc->crc_valid_irq, IRQ_DISABLE_UNLAZY); + ret = devm_request_threaded_irq(dev, dpu_crtc->crc_valid_irq, NULL, + dpu_crc_valid_irq_threaded_handler, + IRQF_ONESHOT, "imx_drm", dpu_crtc); + if (ret < 0) { + dev_err(dev, + "CRC valid irq request failed with %d.\n", + ret); + goto err_put_resources; + } + disable_irq(dpu_crtc->crc_valid_irq); + + dpu_crtc->crc_shdld_irq = dpu_map_irq(dpu, stream_id ? + IRQ_SIG1_SHDLOAD : IRQ_SIG0_SHDLOAD); + irq_set_status_flags(dpu_crtc->crc_shdld_irq, IRQ_DISABLE_UNLAZY); + ret = devm_request_irq(dev, dpu_crtc->crc_shdld_irq, + dpu_crc_shdld_irq_handler, 0, "imx_drm", + dpu_crtc); + if (ret < 0) { + dev_err(dev, + "CRC shadow load irq request failed with %d.\n", + ret); + goto err_put_resources; + } + disable_irq(dpu_crtc->crc_shdld_irq); + + return 0; + +err_put_resources: + dpu_crtc_put_resources(dpu_crtc); + + return ret; +} + +static int dpu_crtc_bind(struct device *dev, struct device *master, void *data) +{ + struct dpu_client_platformdata *pdata = dev->platform_data; + struct drm_device *drm = data; + struct dpu_crtc *dpu_crtc = dev_get_drvdata(dev); + int ret; + + dpu_crtc->dev = dev; + + drm->mode_config.max_width = 5120; + drm->mode_config.max_height = 4096; + + ret = dpu_crtc_init(dpu_crtc, pdata, drm); + if (ret) + return ret; + + if (!drm->mode_config.funcs) + drm->mode_config.funcs = &dpu_drm_mode_config_funcs; + + return 0; +} + +static void dpu_crtc_unbind(struct device *dev, struct device *master, + void *data) +{ + struct dpu_crtc *dpu_crtc = dev_get_drvdata(dev); + + dpu_crtc_put_resources(dpu_crtc); +} + +static const struct component_ops dpu_crtc_ops = { + .bind = dpu_crtc_bind, + .unbind = dpu_crtc_unbind, +}; + +static int dpu_crtc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dpu_crtc *dpu_crtc; + + if (!dev->platform_data) + return -EINVAL; + + dpu_crtc = devm_kzalloc(dev, sizeof(*dpu_crtc), GFP_KERNEL); + if (!dpu_crtc) + return -ENOMEM; + + dev_set_drvdata(dev, dpu_crtc); + + return component_add(dev, &dpu_crtc_ops); +} + +static int dpu_crtc_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dpu_crtc_ops); + return 0; +} + +static struct platform_driver dpu_crtc_driver = { + .driver = { + .name = "imx-dpu-crtc", + }, + .probe = dpu_crtc_probe, + .remove = dpu_crtc_remove, +}; +module_platform_driver(dpu_crtc_driver); + +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_DESCRIPTION("i.MX DPU CRTC"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-dpu-crtc"); diff --git a/drivers/gpu/drm/imx/dpu/dpu-crtc.h b/drivers/gpu/drm/imx/dpu/dpu-crtc.h new file mode 100644 index 000000000000..423fe1339cf2 --- /dev/null +++ b/drivers/gpu/drm/imx/dpu/dpu-crtc.h @@ -0,0 +1,115 @@ +/* + * Copyright 2017-2020 NXP + * + * 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. + */ + +#ifndef _DPU_CRTC_H_ +#define _DPU_CRTC_H_ + +#include <drm/drm_vblank.h> +#include <video/dpu.h> +#include "dpu-plane.h" +#include "../imx-drm.h" + +struct dpu_crtc { + struct device *dev; + struct drm_crtc base; + struct imx_drm_crtc *imx_crtc; + struct dpu_constframe *pa_cf; + struct dpu_constframe *sa_cf; + struct dpu_disengcfg *dec; + struct dpu_extdst *ed; + struct dpu_framegen *fg; + struct dpu_signature *sig; + struct dpu_tcon *tcon; + struct dpu_store *st; + struct dpu_constframe *aux_pa_cf; + struct dpu_constframe *aux_sa_cf; + struct dpu_disengcfg *aux_dec; + struct dpu_extdst *aux_ed; + struct dpu_framegen *aux_fg; + struct dpu_signature *aux_sig; + struct dpu_tcon *aux_tcon; + /* master */ + struct dpu_constframe *m_pa_cf; + struct dpu_constframe *m_sa_cf; + struct dpu_disengcfg *m_dec; + struct dpu_extdst *m_ed; + struct dpu_framegen *m_fg; + struct dpu_tcon *m_tcon; + /* slave */ + struct dpu_constframe *s_pa_cf; + struct dpu_constframe *s_sa_cf; + struct dpu_disengcfg *s_dec; + struct dpu_extdst *s_ed; + struct dpu_framegen *s_fg; + struct dpu_tcon *s_tcon; + struct dpu_plane **plane; + unsigned int hw_plane_num; + unsigned int stream_id; + unsigned int crtc_grp_id; + unsigned int syncmode_min_prate; + unsigned int singlemode_max_width; + unsigned int master_stream_id; + int vbl_irq; + int safety_shdld_irq; + int content_shdld_irq; + int dec_shdld_irq; + int crc_valid_irq; + int crc_shdld_irq; + + bool aux_is_master; + bool use_dual_crc; + bool crc_is_enabled; + + struct completion safety_shdld_done; + struct completion content_shdld_done; + struct completion dec_shdld_done; + struct completion crc_shdld_done; + struct completion aux_crc_done; + + struct drm_pending_vblank_event *event; + + u32 crc_red; + u32 crc_green; + u32 crc_blue; + u32 dual_crc_flag; +}; + +struct dpu_crc { + enum dpu_crc_source source; + struct drm_rect roi; +}; + +struct dpu_crtc_state { + struct imx_crtc_state imx_crtc_state; + struct dpu_plane_state **dpu_plane_states; + struct dpu_crc crc; + bool use_pc; +}; + +static inline struct dpu_crtc_state *to_dpu_crtc_state(struct imx_crtc_state *s) +{ + return container_of(s, struct dpu_crtc_state, imx_crtc_state); +} + +static inline struct dpu_crtc *to_dpu_crtc(struct drm_crtc *crtc) +{ + return container_of(crtc, struct dpu_crtc, base); +} + +struct dpu_plane_state ** +crtc_state_get_dpu_plane_states(struct drm_crtc_state *state); + +struct dpu_crtc *dpu_crtc_get_aux_dpu_crtc(struct dpu_crtc *dpu_crtc); + +#endif diff --git a/drivers/gpu/drm/imx/dpu/dpu-kms.c b/drivers/gpu/drm/imx/dpu/dpu-kms.c new file mode 100644 index 000000000000..1007460fd973 --- /dev/null +++ b/drivers/gpu/drm/imx/dpu/dpu-kms.c @@ -0,0 +1,759 @@ +/* + * Copyright 2017-2020 NXP + * + * 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 <drm/drm_fourcc.h> +#include <drm/drm_vblank.h> +#include <drm/drm_print.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <linux/sort.h> +#include <video/dpu.h> +#include "dpu-crtc.h" +#include "dpu-plane.h" +#include "../imx-drm.h" + +static struct drm_plane_state ** +dpu_atomic_alloc_tmp_planes_per_crtc(struct drm_device *dev) +{ + int total_planes = dev->mode_config.num_total_plane; + struct drm_plane_state **states; + + states = kmalloc_array(total_planes, sizeof(*states), GFP_KERNEL); + if (!states) + return ERR_PTR(-ENOMEM); + + return states; +} + +static int zpos_cmp(const void *a, const void *b) +{ + const struct drm_plane_state *sa = *(struct drm_plane_state **)a; + const struct drm_plane_state *sb = *(struct drm_plane_state **)b; + + return sa->normalized_zpos - sb->normalized_zpos; +} + +static int dpu_atomic_sort_planes_per_crtc(struct drm_crtc_state *crtc_state, + struct drm_plane_state **states) +{ + struct drm_atomic_state *state = crtc_state->state; + struct drm_device *dev = state->dev; + struct drm_plane *plane; + int n = 0; + + drm_for_each_plane_mask(plane, dev, crtc_state->plane_mask) { + struct drm_plane_state *plane_state = + drm_atomic_get_plane_state(state, plane); + if (IS_ERR(plane_state)) + return PTR_ERR(plane_state); + states[n++] = plane_state; + } + + sort(states, n, sizeof(*states), zpos_cmp, NULL); + + return n; +} + +static void +dpu_atomic_compute_plane_lrx_per_crtc(struct drm_crtc_state *crtc_state, + struct drm_plane_state **states, int n) +{ + struct dpu_plane_state *dpstate; + struct drm_plane_state *plane_state; + int i; + int half_hdisplay = crtc_state->adjusted_mode.hdisplay >> 1; + bool lo, ro, bo; + + /* compute left/right_crtc_x if pixel combiner is needed */ + for (i = 0; i < n; i++) { + plane_state = states[i]; + dpstate = to_dpu_plane_state(plane_state); + + lo = dpstate->left_src_w && !dpstate->right_src_w; + ro = !dpstate->left_src_w && dpstate->right_src_w; + bo = dpstate->left_src_w && dpstate->right_src_w; + + if (lo || bo) { + dpstate->left_crtc_x = plane_state->crtc_x; + dpstate->right_crtc_x = 0; + } else if (ro) { + dpstate->left_crtc_x = 0; + dpstate->right_crtc_x = + plane_state->crtc_x - half_hdisplay; + } + } +} + +static void +dpu_atomic_set_top_plane_per_crtc(struct drm_plane_state **states, int n, + bool use_pc) +{ + struct dpu_plane_state *dpstate; + bool found_l_top = false, found_r_top = false; + int i; + + for (i = n - 1; i >= 0; i--) { + dpstate = to_dpu_plane_state(states[i]); + if (use_pc) { + if (dpstate->left_src_w && !found_l_top) { + dpstate->is_left_top = true; + found_l_top = true; + } else { + dpstate->is_left_top = false; + } + + if (dpstate->right_src_w && !found_r_top) { + dpstate->is_right_top = true; + found_r_top = true; + } else { + dpstate->is_right_top = false; + } + } else { + dpstate->is_top = (i == (n - 1)) ? true : false; + } + } +} + +static inline bool dpu_atomic_source_pos_is_available(int pos, u32 src_a_mask) +{ + return !!((1 << pos) & src_a_mask); +} + +static int dpu_atomic_get_source_pos(lb_sec_sel_t *current_source, + u32 src_a_mask) +{ + int pos = -1; + + if (*current_source != LB_SEC_SEL__DISABLE) { + for (pos = 0; pos < ARRAY_SIZE(sources); pos++) + if (*current_source == sources[pos] && + dpu_atomic_source_pos_is_available(pos, src_a_mask)) + break; + + if (pos == ARRAY_SIZE(sources)) + *current_source = LB_SEC_SEL__DISABLE; + } + + /* current_source not found, or already used */ + if (pos == -1 || pos == ARRAY_SIZE(sources)) + pos = ffs(src_a_mask) - 1; + + return pos; +} + +static int +dpu_atomic_assign_plane_source_per_crtc(struct drm_plane_state **states, + int n, bool use_pc) +{ + struct dpu_plane_state *dpstate; + struct dpu_plane *dplane; + struct dpu_plane_grp *grp; + struct drm_framebuffer *fb; + struct dpu_fetchunit *fu; + struct dpu_fetchunit *fe; + struct dpu_hscaler *hs; + struct dpu_vscaler *vs; + lb_prim_sel_t stage; + lb_sec_sel_t current_source; + dpu_block_id_t blend; + unsigned int sid, src_sid; + unsigned int num_planes; + int bit; + int i, j, k = 0, m; + int total_asrc_num; + int s0_layer_cnt = 0, s1_layer_cnt = 0; + int s0_n = 0, s1_n = 0; + u32 src_a_mask, cap_mask, fe_mask, hs_mask, vs_mask; + bool need_fetcheco, need_hscaler, need_vscaler; + bool fmt_is_yuv; + bool alloc_aux_source; + + if (use_pc) { + for (i = 0; i < n; i++) { + dpstate = to_dpu_plane_state(states[i]); + + if (dpstate->left_src_w) + s0_n++; + + if (dpstate->right_src_w) + s1_n++; + } + } else { + s0_n = n; + s1_n = n; + } + + /* for active planes only */ + for (i = 0; i < n; i++) { + dpstate = to_dpu_plane_state(states[i]); + dplane = to_dpu_plane(states[i]->plane); + fb = states[i]->fb; + num_planes = fb->format->num_planes; + fmt_is_yuv = drm_format_is_yuv(fb->format->format); + grp = dplane->grp; + alloc_aux_source = false; + + if (use_pc) + sid = dpstate->left_src_w ? 0 : 1; + else + sid = dplane->stream_id; + +again: + if (alloc_aux_source) + sid ^= 1; + + need_fetcheco = (num_planes > 1); + need_hscaler = (states[i]->src_w >> 16 != states[i]->crtc_w); + need_vscaler = (states[i]->src_h >> 16 != states[i]->crtc_h); + + total_asrc_num = 0; + src_a_mask = grp->src_a_mask; + fe_mask = 0; + hs_mask = 0; + vs_mask = 0; + + for_each_set_bit(bit, (unsigned long *)&src_a_mask, 32) + total_asrc_num++; + + current_source = alloc_aux_source ? + dpstate->aux_source : dpstate->source; + + /* assign source */ + mutex_lock(&grp->mutex); + for (j = 0; j < total_asrc_num; j++) { + k = dpu_atomic_get_source_pos(¤t_source, src_a_mask); + if (k < 0) + return -EINVAL; + + fu = source_to_fu(&grp->res, sources[k]); + if (!fu) + return -EINVAL; + + /* avoid on-the-fly/hot migration */ + src_sid = fu->ops->get_stream_id(fu); + if (src_sid && src_sid != BIT(sid)) + goto next; + + if (fetchunit_is_fetchdecode(fu)) { + cap_mask = fetchdecode_get_vproc_mask(fu); + + if (need_fetcheco) { + fe = fetchdecode_get_fetcheco(fu); + + /* avoid on-the-fly/hot migration */ + src_sid = fu->ops->get_stream_id(fe); + if (src_sid && src_sid != BIT(sid)) + goto next; + + /* fetch unit has the fetcheco cap? */ + if (!dpu_vproc_has_fetcheco_cap(cap_mask)) + goto next; + + fe_mask = + dpu_vproc_get_fetcheco_cap(cap_mask); + + /* fetcheco available? */ + if (grp->src_use_vproc_mask & fe_mask) + goto next; + } + + if (need_hscaler) { + hs = fetchdecode_get_hscaler(fu); + + /* avoid on-the-fly/hot migration */ + src_sid = hscaler_get_stream_id(hs); + if (src_sid && src_sid != BIT(sid)) + goto next; + + /* fetch unit has the hscale cap */ + if (!dpu_vproc_has_hscale_cap(cap_mask)) + goto next; + + hs_mask = + dpu_vproc_get_hscale_cap(cap_mask); + + /* hscaler available? */ + if (grp->src_use_vproc_mask & hs_mask) + goto next; + } + + if (need_vscaler) { + vs = fetchdecode_get_vscaler(fu); + + /* avoid on-the-fly/hot migration */ + src_sid = vscaler_get_stream_id(vs); + if (src_sid && src_sid != BIT(sid)) + goto next; + + /* fetch unit has the vscale cap? */ + if (!dpu_vproc_has_vscale_cap(cap_mask)) + goto next; + + vs_mask = + dpu_vproc_get_vscale_cap(cap_mask); + + /* vscaler available? */ + if (grp->src_use_vproc_mask & vs_mask) + goto next; + } + } else { + if (fmt_is_yuv || need_fetcheco || + need_hscaler || need_vscaler) + goto next; + } + + grp->src_a_mask &= ~BIT(k); + grp->src_use_vproc_mask |= fe_mask | hs_mask | vs_mask; + break; +next: + src_a_mask &= ~BIT(k); + fe_mask = 0; + hs_mask = 0; + vs_mask = 0; + } + mutex_unlock(&grp->mutex); + + if (j == total_asrc_num) + return -EINVAL; + + if (alloc_aux_source) + dpstate->aux_source = sources[k]; + else + dpstate->source = sources[k]; + + /* assign stage and blend */ + if (sid) { + m = grp->hw_plane_num - (s1_n - s1_layer_cnt); + stage = s1_layer_cnt ? stages[m - 1] : cf_stages[sid]; + blend = blends[m]; + + s1_layer_cnt++; + } else { + stage = s0_layer_cnt ? + stages[s0_layer_cnt - 1] : cf_stages[sid]; + blend = blends[s0_layer_cnt]; + + s0_layer_cnt++; + } + + if (alloc_aux_source) { + dpstate->aux_stage = stage; + dpstate->aux_blend = blend; + } else { + dpstate->stage = stage; + dpstate->blend = blend; + } + + if (dpstate->need_aux_source && !alloc_aux_source) { + alloc_aux_source = true; + goto again; + } + } + + return 0; +} + +static void +dpu_atomic_mark_pipe_states_prone_to_put_per_crtc(struct drm_crtc *crtc, + u32 crtc_mask, + struct drm_atomic_state *state, + bool *puts) +{ + struct drm_plane *plane; + struct drm_plane_state *plane_state; + bool found_pstate = false; + int i; + + if ((crtc_mask & drm_crtc_mask(crtc)) == 0) { + for_each_new_plane_in_state(state, plane, plane_state, i) { + if (plane->possible_crtcs & drm_crtc_mask(crtc)) { + found_pstate = true; + break; + } + } + + if (!found_pstate) + puts[drm_crtc_index(crtc)] = true; + } +} + +static void +dpu_atomic_put_plane_state(struct drm_atomic_state *state, + struct drm_plane *plane) +{ + int index = drm_plane_index(plane); + + plane->funcs->atomic_destroy_state(plane, state->planes[index].state); + state->planes[index].ptr = NULL; + state->planes[index].state = NULL; + + drm_modeset_unlock(&plane->mutex); +} + +static void +dpu_atomic_put_crtc_state(struct drm_atomic_state *state, + struct drm_crtc *crtc) +{ + int index = drm_crtc_index(crtc); + + crtc->funcs->atomic_destroy_state(crtc, state->crtcs[index].state); + state->crtcs[index].ptr = NULL; + state->crtcs[index].state = NULL; + + drm_modeset_unlock(&crtc->mutex); +} + +static void +dpu_atomic_put_possible_states_per_crtc(struct drm_crtc_state *crtc_state) +{ + struct drm_atomic_state *state = crtc_state->state; + struct drm_crtc *crtc = crtc_state->crtc; + struct drm_crtc_state *old_crtc_state = crtc->state; + struct drm_plane *plane; + struct drm_plane_state *plane_state; + struct dpu_plane *dplane = to_dpu_plane(crtc->primary); + struct dpu_plane_state **old_dpstates; + struct dpu_plane_state *old_dpstate, *new_dpstate; + u32 active_mask = 0; + int i; + + old_dpstates = crtc_state_get_dpu_plane_states(old_crtc_state); + if (WARN_ON(!old_dpstates)) + return; + + for (i = 0; i < dplane->grp->hw_plane_num; i++) { + old_dpstate = old_dpstates[i]; + if (!old_dpstate) + continue; + + active_mask |= BIT(i); + + drm_atomic_crtc_state_for_each_plane(plane, crtc_state) { + if (drm_plane_index(plane) != + drm_plane_index(old_dpstate->base.plane)) + continue; + + plane_state = + drm_atomic_get_existing_plane_state(state, + plane); + if (WARN_ON(!plane_state)) + return; + + new_dpstate = to_dpu_plane_state(plane_state); + + active_mask &= ~BIT(i); + + /* + * Should be enough to check the below real HW plane + * resources only. + * Things like vproc resources should be fine. + */ + if (old_dpstate->stage != new_dpstate->stage || + old_dpstate->source != new_dpstate->source || + old_dpstate->blend != new_dpstate->blend || + old_dpstate->aux_stage != new_dpstate->aux_stage || + old_dpstate->aux_source != new_dpstate->aux_source || + old_dpstate->aux_blend != new_dpstate->aux_blend) + return; + } + } + + /* pure software check */ + if (WARN_ON(active_mask)) + return; + + drm_atomic_crtc_state_for_each_plane(plane, crtc_state) + dpu_atomic_put_plane_state(state, plane); + + dpu_atomic_put_crtc_state(state, crtc); +} + +static int dpu_drm_atomic_check(struct drm_device *dev, + struct drm_atomic_state *state) +{ + struct drm_crtc *crtc; + struct drm_crtc_state *crtc_state; + struct drm_plane *plane; + struct dpu_plane *dpu_plane; + struct drm_plane_state *plane_state; + struct dpu_plane_state *dpstate; + struct drm_framebuffer *fb; + struct dpu_plane_grp *grp[MAX_DPU_PLANE_GRP]; + int ret, i, grp_id; + int active_plane[MAX_DPU_PLANE_GRP]; + int active_plane_fetcheco[MAX_DPU_PLANE_GRP]; + int active_plane_hscale[MAX_DPU_PLANE_GRP]; + int active_plane_vscale[MAX_DPU_PLANE_GRP]; + int half_hdisplay = 0; + bool pipe_states_prone_to_put[MAX_CRTC]; + bool use_pc[MAX_DPU_PLANE_GRP]; + u32 crtc_mask_in_state = 0; + + ret = drm_atomic_helper_check_modeset(dev, state); + if (ret) { + DRM_DEBUG_KMS("%s: failed to check modeset\n", __func__); + return ret; + } + + for (i = 0; i < MAX_CRTC; i++) + pipe_states_prone_to_put[i] = false; + + for (i = 0; i < MAX_DPU_PLANE_GRP; i++) { + active_plane[i] = 0; + active_plane_fetcheco[i] = 0; + active_plane_hscale[i] = 0; + active_plane_vscale[i] = 0; + use_pc[i] = false; + grp[i] = NULL; + } + + for_each_new_crtc_in_state(state, crtc, crtc_state, i) + crtc_mask_in_state |= drm_crtc_mask(crtc); + + drm_for_each_crtc(crtc, dev) { + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct imx_crtc_state *imx_crtc_state; + struct dpu_crtc_state *dcstate; + bool need_left, need_right, need_aux_source, use_pc_per_crtc; + + use_pc_per_crtc = false; + + dpu_atomic_mark_pipe_states_prone_to_put_per_crtc(crtc, + crtc_mask_in_state, state, + pipe_states_prone_to_put); + + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + imx_crtc_state = to_imx_crtc_state(crtc_state); + dcstate = to_dpu_crtc_state(imx_crtc_state); + + if (crtc_state->enable) { + if (use_pc[dpu_crtc->crtc_grp_id]) { + DRM_DEBUG_KMS("other crtc needs pixel combiner\n"); + return -EINVAL; + } + + if (crtc_state->adjusted_mode.clock > + dpu_crtc->syncmode_min_prate || + crtc_state->adjusted_mode.hdisplay > + dpu_crtc->singlemode_max_width) + use_pc_per_crtc = true; + } + + if (use_pc_per_crtc) { + use_pc[dpu_crtc->crtc_grp_id] = true; + half_hdisplay = crtc_state->adjusted_mode.hdisplay >> 1; + } + + dcstate->use_pc = use_pc_per_crtc; + + drm_for_each_plane_mask(plane, dev, crtc_state->plane_mask) { + plane_state = drm_atomic_get_plane_state(state, plane); + if (IS_ERR(plane_state)) { + DRM_DEBUG_KMS("failed to get plane state\n"); + return PTR_ERR(plane_state); + } + + dpstate = to_dpu_plane_state(plane_state); + fb = plane_state->fb; + dpu_plane = to_dpu_plane(plane); + grp_id = dpu_plane->grp->id; + active_plane[grp_id]++; + + need_left = false; + need_right = false; + need_aux_source = false; + + if (use_pc_per_crtc) { + if (plane_state->crtc_x < half_hdisplay) + need_left = true; + + if ((plane_state->crtc_w + + plane_state->crtc_x) > half_hdisplay) + need_right = true; + + if (need_left && need_right) { + need_aux_source = true; + active_plane[grp_id]++; + } + } + + if (need_left && need_right) { + dpstate->left_crtc_w = half_hdisplay; + dpstate->left_crtc_w -= plane_state->crtc_x; + + dpstate->left_src_w = dpstate->left_crtc_w; + } else if (need_left) { + dpstate->left_crtc_w = plane_state->crtc_w; + dpstate->left_src_w = plane_state->src_w >> 16; + } else { + dpstate->left_crtc_w = 0; + dpstate->left_src_w = 0; + } + + if (need_right && need_left) { + dpstate->right_crtc_w = plane_state->crtc_x + + plane_state->crtc_w; + dpstate->right_crtc_w -= half_hdisplay; + + dpstate->right_src_w = dpstate->right_crtc_w; + } else if (need_right) { + dpstate->right_crtc_w = plane_state->crtc_w; + dpstate->right_src_w = plane_state->src_w >> 16; + } else { + dpstate->right_crtc_w = 0; + dpstate->right_src_w = 0; + } + + if (fb->format->num_planes > 1) { + active_plane_fetcheco[grp_id]++; + if (need_aux_source) + active_plane_fetcheco[grp_id]++; + } + + if (plane_state->src_w >> 16 != plane_state->crtc_w) { + if (use_pc_per_crtc) + return -EINVAL; + + active_plane_hscale[grp_id]++; + } + + if (plane_state->src_h >> 16 != plane_state->crtc_h) { + if (use_pc_per_crtc) + return -EINVAL; + + active_plane_vscale[grp_id]++; + } + + if (grp[grp_id] == NULL) + grp[grp_id] = dpu_plane->grp; + + dpstate->need_aux_source = need_aux_source; + } + } + + /* enough resources? */ + for (i = 0; i < MAX_DPU_PLANE_GRP; i++) { + if (!grp[i]) + continue; + + if (active_plane[i] > grp[i]->hw_plane_num) { + DRM_DEBUG_KMS("no enough fetch units\n"); + return -EINVAL; + } + + if (active_plane_fetcheco[i] > grp[i]->hw_plane_fetcheco_num) { + DRM_DEBUG_KMS("no enough FetchEcos\n"); + return -EINVAL; + } + + if (active_plane_hscale[i] > grp[i]->hw_plane_hscaler_num) { + DRM_DEBUG_KMS("no enough Hscalers\n"); + return -EINVAL; + } + + if (active_plane_vscale[i] > grp[i]->hw_plane_vscaler_num) { + DRM_DEBUG_KMS("no enough Vscalers\n"); + return -EINVAL; + } + } + + /* initialize resource mask */ + for (i = 0; i < MAX_DPU_PLANE_GRP; i++) { + if (!grp[i]) + continue; + + mutex_lock(&grp[i]->mutex); + grp[i]->src_a_mask = grp[i]->src_mask; + grp[i]->src_use_vproc_mask = 0; + mutex_unlock(&grp[i]->mutex); + } + + ret = drm_atomic_normalize_zpos(dev, state); + if (ret) + return ret; + + for_each_new_crtc_in_state(state, crtc, crtc_state, i) { + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct drm_plane_state **states; + int n; + + states = dpu_atomic_alloc_tmp_planes_per_crtc(dev); + if (IS_ERR(states)) { + DRM_DEBUG_KMS( + "[CRTC:%d:%s] cannot alloc plane state ptrs\n", + crtc->base.id, crtc->name); + return PTR_ERR(states); + } + + n = dpu_atomic_sort_planes_per_crtc(crtc_state, states); + if (n < 0) { + DRM_DEBUG_KMS("[CRTC:%d:%s] failed to sort planes\n", + crtc->base.id, crtc->name); + kfree(states); + return n; + } + + /* no active planes? */ + if (n == 0) { + kfree(states); + continue; + } + + if (use_pc[dpu_crtc->crtc_grp_id]) + dpu_atomic_compute_plane_lrx_per_crtc(crtc_state, + states, n); + + dpu_atomic_set_top_plane_per_crtc(states, n, + use_pc[dpu_crtc->crtc_grp_id]); + + ret = dpu_atomic_assign_plane_source_per_crtc(states, n, + use_pc[dpu_crtc->crtc_grp_id]); + if (ret) { + DRM_DEBUG_KMS("[CRTC:%d:%s] cannot assign plane rscs\n", + crtc->base.id, crtc->name); + kfree(states); + return ret; + } + + kfree(states); + } + + drm_for_each_crtc(crtc, dev) { + if (pipe_states_prone_to_put[drm_crtc_index(crtc)]) { + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (WARN_ON(IS_ERR(crtc_state))) + return PTR_ERR(crtc_state); + + dpu_atomic_put_possible_states_per_crtc(crtc_state); + } + } + + ret = drm_atomic_helper_check_planes(dev, state); + if (ret) { + DRM_DEBUG_KMS("%s: failed to check planes\n", __func__); + return ret; + } + + return ret; +} + +const struct drm_mode_config_funcs dpu_drm_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = dpu_drm_atomic_check, + .atomic_commit = drm_atomic_helper_commit, +}; diff --git a/drivers/gpu/drm/imx/dpu/dpu-kms.h b/drivers/gpu/drm/imx/dpu/dpu-kms.h new file mode 100644 index 000000000000..73723e500239 --- /dev/null +++ b/drivers/gpu/drm/imx/dpu/dpu-kms.h @@ -0,0 +1,20 @@ +/* + * Copyright 2017-2019 NXP + * + * 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. + */ + +#ifndef _DPU_KMS_H_ +#define _DPU_KMS_H_ + +extern const struct drm_mode_config_funcs dpu_drm_mode_config_funcs; + +#endif diff --git a/drivers/gpu/drm/imx/dpu/dpu-plane.c b/drivers/gpu/drm/imx/dpu/dpu-plane.c new file mode 100644 index 000000000000..d0db1edb43c6 --- /dev/null +++ b/drivers/gpu/drm/imx/dpu/dpu-plane.c @@ -0,0 +1,1024 @@ +/* + * Copyright 2017-2019,2022 NXP + * + * 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 <drm/drm_vblank.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_blend.h> +#include <drm/drm_color_mgmt.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> +#include <video/dpu.h> +#include <video/imx8-prefetch.h> +#include "dpu-plane.h" +#include "../imx-drm.h" + +#define FRAC_16_16(mult, div) (((mult) << 16) / (div)) + +static const uint32_t dpu_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_RGB565, + + DRM_FORMAT_YUYV, + DRM_FORMAT_UYVY, + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, +}; + +static const uint64_t dpu_format_modifiers[] = { + DRM_FORMAT_MOD_VIVANTE_TILED, + DRM_FORMAT_MOD_VIVANTE_SUPER_TILED, + DRM_FORMAT_MOD_AMPHION_TILED, + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID, +}; + +static unsigned int dpu_plane_get_default_zpos(enum drm_plane_type type) +{ + if (type == DRM_PLANE_TYPE_PRIMARY) + return 0; + else if (type == DRM_PLANE_TYPE_OVERLAY) + return 1; + + return 0; +} + +static void dpu_plane_destroy(struct drm_plane *plane) +{ + struct dpu_plane *dpu_plane = to_dpu_plane(plane); + + drm_plane_cleanup(plane); + kfree(dpu_plane); +} + +static void dpu_plane_reset(struct drm_plane *plane) +{ + struct dpu_plane_state *state; + + if (plane->state) { + __drm_atomic_helper_plane_destroy_state(plane->state); + kfree(to_dpu_plane_state(plane->state)); + plane->state = NULL; + } + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return; + + __drm_atomic_helper_plane_reset(plane, &state->base); + + plane->state->zpos = dpu_plane_get_default_zpos(plane->type); + plane->state->color_encoding = DRM_COLOR_YCBCR_BT601; + plane->state->color_range = DRM_COLOR_YCBCR_FULL_RANGE; +} + +static struct drm_plane_state * +dpu_drm_atomic_plane_duplicate_state(struct drm_plane *plane) +{ + struct dpu_plane_state *state, *copy; + + if (WARN_ON(!plane->state)) + return NULL; + + copy = kmalloc(sizeof(*state), GFP_KERNEL); + if (!copy) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, ©->base); + state = to_dpu_plane_state(plane->state); + copy->stage = state->stage; + copy->source = state->source; + copy->blend = state->blend; + copy->aux_stage = state->aux_stage; + copy->aux_source = state->aux_source; + copy->aux_blend = state->aux_blend; + copy->is_top = state->is_top; + copy->use_prefetch = state->use_prefetch; + copy->use_aux_prefetch = state->use_aux_prefetch; + copy->need_aux_source = state->need_aux_source; + copy->left_src_w = state->left_src_w; + copy->left_crtc_w = state->left_crtc_w; + copy->left_crtc_x = state->left_crtc_x; + copy->right_src_w = state->right_src_w; + copy->right_crtc_w = state->right_crtc_w; + copy->right_crtc_x = state->right_crtc_x; + copy->is_left_top = state->is_left_top; + copy->is_right_top = state->is_right_top; + + return ©->base; +} + +static bool dpu_drm_plane_format_mod_supported(struct drm_plane *plane, + uint32_t format, + uint64_t modifier) +{ + if (WARN_ON(modifier == DRM_FORMAT_MOD_INVALID)) + return false; + + switch (format) { + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + return modifier == DRM_FORMAT_MOD_LINEAR; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_RGB565: + return modifier == DRM_FORMAT_MOD_LINEAR || + modifier == DRM_FORMAT_MOD_VIVANTE_TILED || + modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED; + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + return modifier == DRM_FORMAT_MOD_LINEAR || + modifier == DRM_FORMAT_MOD_AMPHION_TILED; + default: + return false; + } +} + +static void dpu_drm_atomic_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + __drm_atomic_helper_plane_destroy_state(state); + kfree(to_dpu_plane_state(state)); +} + +static const struct drm_plane_funcs dpu_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = dpu_plane_destroy, + .reset = dpu_plane_reset, + .atomic_duplicate_state = dpu_drm_atomic_plane_duplicate_state, + .atomic_destroy_state = dpu_drm_atomic_plane_destroy_state, + .format_mod_supported = dpu_drm_plane_format_mod_supported, +}; + +static inline dma_addr_t +drm_plane_state_to_baseaddr(struct drm_plane_state *state, bool aux_source) +{ + struct drm_framebuffer *fb = state->fb; + struct drm_gem_cma_object *cma_obj; + struct dpu_plane_state *dpstate = to_dpu_plane_state(state); + unsigned int x = (state->src.x1 >> 16) + + (aux_source ? dpstate->left_src_w : 0); + unsigned int y = state->src.y1 >> 16; + + cma_obj = drm_fb_cma_get_gem_obj(fb, 0); + BUG_ON(!cma_obj); + + if (fb->modifier) + return cma_obj->paddr + fb->offsets[0]; + + if (fb->flags & DRM_MODE_FB_INTERLACED) + y /= 2; + + return cma_obj->paddr + fb->offsets[0] + fb->pitches[0] * y + + fb->format->cpp[0] * x; +} + +static inline dma_addr_t +drm_plane_state_to_uvbaseaddr(struct drm_plane_state *state, bool aux_source) +{ + struct drm_framebuffer *fb = state->fb; + struct drm_gem_cma_object *cma_obj; + struct dpu_plane_state *dpstate = to_dpu_plane_state(state); + int x = (state->src.x1 >> 16) + (aux_source ? dpstate->left_src_w : 0); + int y = state->src.y1 >> 16; + + cma_obj = drm_fb_cma_get_gem_obj(fb, 1); + BUG_ON(!cma_obj); + + if (fb->modifier) + return cma_obj->paddr + fb->offsets[1]; + + x /= fb->format->hsub; + y /= fb->format->vsub; + + if (fb->flags & DRM_MODE_FB_INTERLACED) + y /= 2; + + return cma_obj->paddr + fb->offsets[1] + fb->pitches[1] * y + + fb->format->cpp[1] * x; +} + +static inline bool dpu_plane_fb_format_is_yuv(u32 fmt) +{ + return fmt == DRM_FORMAT_YUYV || fmt == DRM_FORMAT_UYVY || + fmt == DRM_FORMAT_NV12 || fmt == DRM_FORMAT_NV21; +} + +static int dpu_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct dpu_plane *dplane = to_dpu_plane(plane); + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct dpu_plane_state *dpstate = to_dpu_plane_state(new_plane_state); + struct dpu_plane_res *res = &dplane->grp->res; + struct drm_crtc_state *crtc_state; + struct drm_framebuffer *fb = new_plane_state->fb; + struct dpu_fetchunit *fu; + struct dprc *dprc; + dma_addr_t baseaddr, uv_baseaddr = 0; + u32 src_w, src_h, src_x, src_y; + unsigned int frame_width; + int min_scale, bpp, ret; + bool fb_is_interlaced; + bool check_aux_source = false; + + /* ok to disable */ + if (!fb) { + dpstate->stage = LB_PRIM_SEL__DISABLE; + dpstate->source = LB_SEC_SEL__DISABLE; + dpstate->blend = ID_NONE; + dpstate->aux_stage = LB_PRIM_SEL__DISABLE; + dpstate->aux_source = LB_SEC_SEL__DISABLE; + dpstate->aux_blend = ID_NONE; + dpstate->is_top = false; + dpstate->use_prefetch = false; + dpstate->use_aux_prefetch = false; + dpstate->need_aux_source = false; + dpstate->left_src_w = 0; + dpstate->left_crtc_w = 0; + dpstate->left_crtc_x = 0; + dpstate->right_src_w = 0; + dpstate->right_crtc_w = 0; + dpstate->right_crtc_x = 0; + dpstate->is_left_top = false; + dpstate->is_right_top = false; + return 0; + } + + if (!new_plane_state->crtc) { + DRM_DEBUG_KMS("[PLANE:%d:%s] has no CRTC in plane state\n", + plane->base.id, plane->name); + return -EINVAL; + } + + src_w = new_plane_state->src_w >> 16; + src_h = new_plane_state->src_h >> 16; + src_x = new_plane_state->src_x >> 16; + src_y = new_plane_state->src_y >> 16; + + fb_is_interlaced = !!(fb->flags & DRM_MODE_FB_INTERLACED); + + if (fb->modifier && + fb->modifier != DRM_FORMAT_MOD_AMPHION_TILED && + fb->modifier != DRM_FORMAT_MOD_VIVANTE_TILED && + fb->modifier != DRM_FORMAT_MOD_VIVANTE_SUPER_TILED) { + DRM_DEBUG_KMS("[PLANE:%d:%s] unsupported fb modifier\n", + plane->base.id, plane->name); + return -EINVAL; + } + + crtc_state = drm_atomic_get_existing_crtc_state(state, + new_plane_state->crtc); + if (WARN_ON(!crtc_state)) + return -EINVAL; + + min_scale = dplane->grp->has_vproc ? + FRAC_16_16(min(src_w, src_h), 8192) : + DRM_PLANE_HELPER_NO_SCALING; + ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state, + min_scale, + DRM_PLANE_HELPER_NO_SCALING, + true, false); + if (ret) { + DRM_DEBUG_KMS("[PLANE:%d:%s] failed to check plane state\n", + plane->base.id, plane->name); + return ret; + } + + /* no off screen */ + if (new_plane_state->dst.x1 < 0 || new_plane_state->dst.y1 < 0 || + (new_plane_state->dst.x2 > crtc_state->adjusted_mode.hdisplay) || + (new_plane_state->dst.y2 > crtc_state->adjusted_mode.vdisplay)) { + DRM_DEBUG_KMS("[PLANE:%d:%s] no off screen\n", + plane->base.id, plane->name); + return -EINVAL; + } + + /* pixel/line count and position parameters check */ + if (fb->format->hsub == 2) { + if (dpstate->left_src_w || dpstate->right_src_w) { + if ((dpstate->left_src_w % 2) || + (dpstate->right_src_w % 2) || (src_x % 2)) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad left/right uv width or xoffset\n", + plane->base.id, plane->name); + return -EINVAL; + } + } else { + if ((src_w % 2) || (src_x % 2)) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv width or xoffset\n", + plane->base.id, plane->name); + return -EINVAL; + } + } + } + if (fb->format->vsub == 2) { + if (src_h % (fb_is_interlaced ? 4 : 2)) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv height\n", + plane->base.id, plane->name); + return -EINVAL; + } + if (src_y % (fb_is_interlaced ? 4 : 2)) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv yoffset\n", + plane->base.id, plane->name); + return -EINVAL; + } + } + + /* for tile formats, framebuffer has to be tile aligned */ + switch (fb->modifier) { + case DRM_FORMAT_MOD_AMPHION_TILED: + if (fb->width % 8) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad fb width for AMPHION tile\n", + plane->base.id, plane->name); + return -EINVAL; + } + if (fb->height % 256) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad fb height for AMPHION tile\n", + plane->base.id, plane->name); + return -EINVAL; + } + break; + case DRM_FORMAT_MOD_VIVANTE_TILED: + if (fb->width % 4) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad fb width for VIVANTE tile\n", + plane->base.id, plane->name); + return -EINVAL; + } + if (fb->height % 4) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad fb height for VIVANTE tile\n", + plane->base.id, plane->name); + return -EINVAL; + } + break; + case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED: + if (fb->width % 64) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad fb width for VIVANTE super tile\n", + plane->base.id, plane->name); + return -EINVAL; + } + if (fb->height % 64) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad fb height for VIVANTE super tile\n", + plane->base.id, plane->name); + return -EINVAL; + } + break; + default: + break; + } + + /* do not support BT709 full range */ + if (dpu_plane_fb_format_is_yuv(fb->format->format) && + new_plane_state->color_encoding == DRM_COLOR_YCBCR_BT709 && + new_plane_state->color_range == DRM_COLOR_YCBCR_FULL_RANGE) + return -EINVAL; + +again: + fu = source_to_fu(res, + check_aux_source ? dpstate->aux_source : dpstate->source); + if (!fu) { + DRM_DEBUG_KMS("[PLANE:%d:%s] cannot get fetch unit\n", + plane->base.id, plane->name); + return -EINVAL; + } + + dprc = fu->dprc; + + if (dpstate->need_aux_source) + frame_width = check_aux_source ? + dpstate->right_src_w : dpstate->left_src_w; + else + frame_width = src_w; + + if (dprc && + dprc_format_supported(dprc, fb->format->format, fb->modifier) && + dprc_stride_supported(dprc, fb->pitches[0], fb->pitches[1], + frame_width, fb->format->format)) { + if (check_aux_source) + dpstate->use_aux_prefetch = true; + else + dpstate->use_prefetch = true; + } else { + if (check_aux_source) + dpstate->use_aux_prefetch = false; + else + dpstate->use_prefetch = false; + } + + if (fb->modifier) { + if (check_aux_source && !dpstate->use_aux_prefetch) { + DRM_DEBUG_KMS("[PLANE:%d:%s] cannot do tile resolving wo prefetch\n", + plane->base.id, plane->name); + return -EINVAL; + } else if (!check_aux_source && !dpstate->use_prefetch) { + DRM_DEBUG_KMS("[PLANE:%d:%s] cannot do tile resolving wo prefetch\n", + plane->base.id, plane->name); + return -EINVAL; + } + } + + /* base address alignment check */ + baseaddr = drm_plane_state_to_baseaddr(new_plane_state, + check_aux_source); + switch (fb->format->format) { + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + bpp = 16; + break; + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + bpp = 8; + break; + default: + bpp = fb->format->cpp[0] * 8; + break; + } + switch (bpp) { + case 32: + if (baseaddr & 0x3) { + DRM_DEBUG_KMS("[PLANE:%d:%s] 32bpp fb bad baddr alignment\n", + plane->base.id, plane->name); + return -EINVAL; + } + break; + case 16: + if (fb->modifier) { + if (baseaddr & 0x1) { + DRM_DEBUG_KMS("[PLANE:%d:%s] 16bpp tile fb bad baddr alignment\n", + plane->base.id, plane->name); + return -EINVAL; + } + } else { + if (check_aux_source) { + if (baseaddr & + (dpstate->use_aux_prefetch ? 0x7 : 0x1)) { + DRM_DEBUG_KMS("[PLANE:%d:%s] 16bpp fb bad baddr alignment\n", + plane->base.id, plane->name); + return -EINVAL; + } + } else { + if (baseaddr & + (dpstate->use_prefetch ? 0x7 : 0x1)) { + DRM_DEBUG_KMS("[PLANE:%d:%s] 16bpp fb bad baddr alignment\n", + plane->base.id, plane->name); + return -EINVAL; + } + } + } + break; + } + + if (fb->pitches[0] > 0x10000) { + DRM_DEBUG_KMS("[PLANE:%d:%s] fb pitch[0] is too big\n", + plane->base.id, plane->name); + return -EINVAL; + } + + /* UV base address alignment check, assuming 16bpp */ + if (fb->format->num_planes > 1) { + uv_baseaddr = drm_plane_state_to_uvbaseaddr(new_plane_state, + check_aux_source); + if (fb->modifier) { + if (uv_baseaddr & 0x1) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv baddr alignment for tile fb\n", + plane->base.id, plane->name); + return -EINVAL; + } + } else { + if (check_aux_source) { + if (uv_baseaddr & + (dpstate->use_aux_prefetch ? 0x7 : 0x1)) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv baddr alignment\n", + plane->base.id, plane->name); + return -EINVAL; + } + } else { + if (uv_baseaddr & + (dpstate->use_prefetch ? 0x7 : 0x1)) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv baddr alignment\n", + plane->base.id, plane->name); + return -EINVAL; + } + } + } + + if (fb->pitches[1] > 0x10000) { + DRM_DEBUG_KMS("[PLANE:%d:%s] fb pitch[1] is too big\n", + plane->base.id, plane->name); + return -EINVAL; + } + } + + if (!check_aux_source && dpstate->use_prefetch && + !dprc_stride_double_check(dprc, frame_width, src_x, + fb->format->format, + fb->modifier, + baseaddr, uv_baseaddr)) { + if (fb->modifier) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad pitch\n", + plane->base.id, plane->name); + return -EINVAL; + } + + if (bpp == 16 && (baseaddr & 0x1)) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad baddr alignment\n", + plane->base.id, plane->name); + return -EINVAL; + } + + if (uv_baseaddr & 0x1) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv baddr alignment\n", + plane->base.id, plane->name); + return -EINVAL; + } + + dpstate->use_prefetch = false; + } else if (check_aux_source && dpstate->use_aux_prefetch && + !dprc_stride_double_check(dprc, frame_width, src_x, + fb->format->format, + fb->modifier, + baseaddr, uv_baseaddr)) { + if (fb->modifier) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad pitch\n", + plane->base.id, plane->name); + return -EINVAL; + } + + if (bpp == 16 && (baseaddr & 0x1)) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad baddr alignment\n", + plane->base.id, plane->name); + return -EINVAL; + } + + if (uv_baseaddr & 0x1) { + DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv baddr alignment\n", + plane->base.id, plane->name); + return -EINVAL; + } + + dpstate->use_aux_prefetch = false; + } + + if (dpstate->need_aux_source && !check_aux_source) { + check_aux_source = true; + goto again; + } + + return 0; +} + +static void dpu_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct dpu_plane *dplane = to_dpu_plane(plane); + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct dpu_plane_state *dpstate = to_dpu_plane_state(new_plane_state); + struct drm_framebuffer *fb = new_plane_state->fb; + struct dpu_plane_res *res = &dplane->grp->res; + struct dpu_fetchunit *fu; + struct dpu_fetchunit *fe = NULL; + struct dprc *dprc; + struct dpu_hscaler *hs = NULL; + struct dpu_vscaler *vs = NULL; + struct dpu_layerblend *lb; + struct dpu_extdst *ed; + struct dpu_framegen *fg; + dma_addr_t baseaddr, uv_baseaddr = 0; + dpu_block_id_t blend, fe_id, vs_id = ID_NONE, hs_id; + lb_sec_sel_t source; + lb_prim_sel_t stage; + unsigned int stream_id; + unsigned int src_w, src_h, src_x, src_y, dst_w, dst_h; + unsigned int crtc_x; + unsigned int mt_w = 0, mt_h = 0; /* w/h in a micro-tile */ + int bpp, lb_id; + bool need_fetcheco, need_hscaler = false, need_vscaler = false; + bool prefetch_start, uv_prefetch_start; + bool crtc_use_pc = dpstate->left_src_w || dpstate->right_src_w; + bool update_aux_source = false; + bool use_prefetch; + bool need_modeset; + bool fb_is_interlaced; + + /* + * Do nothing since the plane is disabled by + * crtc_func->atomic_begin/flush. + */ + if (!fb) + return; + + need_modeset = + drm_atomic_crtc_needs_modeset(new_plane_state->crtc->state); + fb_is_interlaced = !!(fb->flags & DRM_MODE_FB_INTERLACED); + +again: + need_fetcheco = false; + prefetch_start = false; + uv_prefetch_start = false; + + source = update_aux_source ? dpstate->aux_source : dpstate->source; + blend = update_aux_source ? dpstate->aux_blend : dpstate->blend; + stage = update_aux_source ? dpstate->aux_stage : dpstate->stage; + use_prefetch = update_aux_source ? + dpstate->use_aux_prefetch : dpstate->use_prefetch; + + if (crtc_use_pc) { + if (update_aux_source) { + stream_id = 1; + crtc_x = dpstate->right_crtc_x; + } else { + stream_id = dpstate->left_src_w ? 0 : 1; + crtc_x = dpstate->left_src_w ? + dpstate->left_crtc_x : dpstate->right_crtc_x; + } + } else { + stream_id = dplane->stream_id; + crtc_x = new_plane_state->crtc_x; + } + + fg = res->fg[stream_id]; + + fu = source_to_fu(res, source); + if (!fu) + return; + + dprc = fu->dprc; + + lb_id = blend_to_id(blend); + if (lb_id < 0) + return; + + lb = res->lb[lb_id]; + + if (crtc_use_pc) { + if (update_aux_source || !dpstate->left_src_w) + src_w = dpstate->right_src_w; + else + src_w = dpstate->left_src_w; + } else { + src_w = drm_rect_width(&new_plane_state->src) >> 16; + } + src_h = drm_rect_height(&new_plane_state->src) >> 16; + if (crtc_use_pc && update_aux_source) { + if (fb->modifier) + src_x = (new_plane_state->src_x >> 16) + + dpstate->left_src_w; + else + src_x = 0; + } else { + src_x = fb->modifier ? (new_plane_state->src_x >> 16) : 0; + } + src_y = fb->modifier ? (new_plane_state->src_y >> 16) : 0; + dst_w = drm_rect_width(&new_plane_state->dst); + dst_h = drm_rect_height(&new_plane_state->dst); + + if (fetchunit_is_fetchdecode(fu)) { + if (fetchdecode_need_fetcheco(fu, fb->format->format)) { + need_fetcheco = true; + fe = fetchdecode_get_fetcheco(fu); + if (IS_ERR(fe)) + return; + } + + /* assume pixel combiner is unused */ + if ((src_w != dst_w) && !crtc_use_pc) { + need_hscaler = true; + hs = fetchdecode_get_hscaler(fu); + if (IS_ERR(hs)) + return; + } + + if ((src_h != dst_h) || fb_is_interlaced) { + need_vscaler = true; + vs = fetchdecode_get_vscaler(fu); + if (IS_ERR(vs)) + return; + } + } + + switch (fb->format->format) { + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + bpp = 16; + break; + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + bpp = 8; + break; + default: + bpp = fb->format->cpp[0] * 8; + break; + } + + switch (fb->modifier) { + case DRM_FORMAT_MOD_AMPHION_TILED: + mt_w = 8; + mt_h = 8; + break; + case DRM_FORMAT_MOD_VIVANTE_TILED: + case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED: + mt_w = (bpp == 16) ? 8 : 4; + mt_h = 4; + break; + default: + break; + } + + baseaddr = drm_plane_state_to_baseaddr(new_plane_state, + update_aux_source); + if (need_fetcheco) + uv_baseaddr = drm_plane_state_to_uvbaseaddr(new_plane_state, + update_aux_source); + + if (use_prefetch && + (fu->ops->get_stream_id(fu) == DPU_PLANE_SRC_DISABLED || + need_modeset)) + prefetch_start = true; + + fu->ops->set_burstlength(fu, src_x, mt_w, bpp, baseaddr, use_prefetch); + fu->ops->set_src_bpp(fu, bpp); + fu->ops->set_src_stride(fu, src_w, src_w, mt_w, bpp, fb->pitches[0], + baseaddr, use_prefetch); + fu->ops->set_src_buf_dimensions(fu, src_w, src_h, 0, fb_is_interlaced); + fu->ops->set_pixel_blend_mode(fu, new_plane_state->pixel_blend_mode, + new_plane_state->alpha, + fb->format->format); + fu->ops->set_fmt(fu, fb->format->format, + new_plane_state->color_encoding, + new_plane_state->color_range, fb_is_interlaced); + fu->ops->enable_src_buf(fu); + fu->ops->set_framedimensions(fu, src_w, src_h, fb_is_interlaced); + fu->ops->set_baseaddress(fu, src_w, src_x, src_y, mt_w, mt_h, bpp, + baseaddr); + fu->ops->set_stream_id(fu, stream_id ? + DPU_PLANE_SRC_TO_DISP_STREAM1 : + DPU_PLANE_SRC_TO_DISP_STREAM0); + + DRM_DEBUG_KMS("[PLANE:%d:%s] %s-0x%02x\n", + plane->base.id, plane->name, fu->name, fu->id); + + if (need_fetcheco) { + fe_id = fetcheco_get_block_id(fe); + if (fe_id == ID_NONE) + return; + + if (use_prefetch && + (fe->ops->get_stream_id(fe) == DPU_PLANE_SRC_DISABLED || + need_modeset)) + uv_prefetch_start = true; + + fetchdecode_pixengcfg_dynamic_src_sel(fu, + (fd_dynamic_src_sel_t)fe_id); + fe->ops->set_burstlength(fe, src_w, mt_w, bpp, uv_baseaddr, + use_prefetch); + fe->ops->set_src_bpp(fe, 16); + fe->ops->set_src_stride(fe, src_w, src_x, mt_w, bpp, + fb->pitches[1], + uv_baseaddr, use_prefetch); + fe->ops->set_fmt(fe, fb->format->format, + new_plane_state->color_encoding, + new_plane_state->color_range, + fb_is_interlaced); + fe->ops->set_src_buf_dimensions(fe, src_w, src_h, + fb->format->format, + fb_is_interlaced); + fe->ops->set_framedimensions(fe, src_w, src_h, + fb_is_interlaced); + fe->ops->set_baseaddress(fe, src_w, src_x, src_y / 2, + mt_w, mt_h, bpp, uv_baseaddr); + fe->ops->enable_src_buf(fe); + fe->ops->set_stream_id(fe, stream_id ? + DPU_PLANE_SRC_TO_DISP_STREAM1 : + DPU_PLANE_SRC_TO_DISP_STREAM0); + + DRM_DEBUG_KMS("[PLANE:%d:%s] %s-0x%02x\n", + plane->base.id, plane->name, fe->name, fe_id); + } else { + if (fetchunit_is_fetchdecode(fu)) + fetchdecode_pixengcfg_dynamic_src_sel(fu, + FD_SRC_DISABLE); + } + + /* vscaler comes first */ + if (need_vscaler) { + vs_id = vscaler_get_block_id(vs); + if (vs_id == ID_NONE) + return; + + vscaler_pixengcfg_dynamic_src_sel(vs, (vs_src_sel_t)source); + vscaler_pixengcfg_clken(vs, CLKEN__AUTOMATIC); + vscaler_setup1(vs, src_h, new_plane_state->crtc_h, + fb_is_interlaced); + vscaler_setup2(vs, fb_is_interlaced); + vscaler_setup3(vs, fb_is_interlaced); + vscaler_output_size(vs, dst_h); + vscaler_field_mode(vs, fb_is_interlaced ? + SCALER_ALWAYS0 : SCALER_INPUT); + vscaler_filter_mode(vs, SCALER_LINEAR); + vscaler_scale_mode(vs, SCALER_UPSCALE); + vscaler_mode(vs, SCALER_ACTIVE); + vscaler_set_stream_id(vs, dplane->stream_id ? + DPU_PLANE_SRC_TO_DISP_STREAM1 : + DPU_PLANE_SRC_TO_DISP_STREAM0); + + source = (lb_sec_sel_t)vs_id; + + DRM_DEBUG_KMS("[PLANE:%d:%s] vscaler-0x%02x\n", + plane->base.id, plane->name, vs_id); + } + + /* and then, hscaler */ + if (need_hscaler) { + hs_id = hscaler_get_block_id(hs); + if (hs_id == ID_NONE) + return; + + hscaler_pixengcfg_dynamic_src_sel(hs, need_vscaler ? + (hs_src_sel_t)vs_id : + (hs_src_sel_t)source); + hscaler_pixengcfg_clken(hs, CLKEN__AUTOMATIC); + hscaler_setup1(hs, src_w, dst_w); + hscaler_output_size(hs, dst_w); + hscaler_filter_mode(hs, SCALER_LINEAR); + hscaler_scale_mode(hs, SCALER_UPSCALE); + hscaler_mode(hs, SCALER_ACTIVE); + hscaler_set_stream_id(hs, dplane->stream_id ? + DPU_PLANE_SRC_TO_DISP_STREAM1 : + DPU_PLANE_SRC_TO_DISP_STREAM0); + + source = (lb_sec_sel_t)hs_id; + + DRM_DEBUG_KMS("[PLANE:%d:%s] hscaler-0x%02x\n", + plane->base.id, plane->name, hs_id); + } + + if (use_prefetch) { + dprc_configure(dprc, stream_id, + src_w, src_h, src_x, src_y, + fb->pitches[0], fb->format->format, + fb->modifier, baseaddr, uv_baseaddr, + prefetch_start, uv_prefetch_start, + fb_is_interlaced); + + dprc_enable(dprc); + + dprc_reg_update(dprc); + + if (prefetch_start || uv_prefetch_start) { + dprc_first_frame_handle(dprc); + + if (!need_modeset && + new_plane_state->normalized_zpos != 0) + framegen_wait_for_frame_counter_moving(fg); + } + + if (update_aux_source) + DRM_DEBUG_KMS("[PLANE:%d:%s] use aux prefetch\n", + plane->base.id, plane->name); + else + DRM_DEBUG_KMS("[PLANE:%d:%s] use prefetch\n", + plane->base.id, plane->name); + } else if (dprc) { + dprc_disable(dprc); + + if (update_aux_source) + DRM_DEBUG_KMS("[PLANE:%d:%s] bypass aux prefetch\n", + plane->base.id, plane->name); + else + DRM_DEBUG_KMS("[PLANE:%d:%s] bypass prefetch\n", + plane->base.id, plane->name); + } + + layerblend_pixengcfg_dynamic_prim_sel(lb, stage); + layerblend_pixengcfg_dynamic_sec_sel(lb, source); + layerblend_control(lb, LB_BLEND); + layerblend_blendcontrol(lb, new_plane_state->normalized_zpos, + new_plane_state->pixel_blend_mode, + new_plane_state->alpha); + layerblend_pixengcfg_clken(lb, CLKEN__AUTOMATIC); + layerblend_position(lb, crtc_x, new_plane_state->crtc_y); + + if (crtc_use_pc) { + if ((!stream_id && dpstate->is_left_top) || + (stream_id && dpstate->is_right_top)) { + ed = res->ed[stream_id]; + extdst_pixengcfg_src_sel(ed, (extdst_src_sel_t)blend); + } + } else { + if (dpstate->is_top) { + ed = res->ed[stream_id]; + extdst_pixengcfg_src_sel(ed, (extdst_src_sel_t)blend); + } + } + + if (update_aux_source) + DRM_DEBUG_KMS("[PLANE:%d:%s] *aux* source-0x%02x stage-0x%02x blend-0x%02x\n", + plane->base.id, plane->name, + source, dpstate->stage, dpstate->blend); + else + DRM_DEBUG_KMS("[PLANE:%d:%s] source-0x%02x stage-0x%02x blend-0x%02x\n", + plane->base.id, plane->name, + source, dpstate->stage, dpstate->blend); + + if (dpstate->need_aux_source && !update_aux_source) { + update_aux_source = true; + goto again; + } +} + +static const struct drm_plane_helper_funcs dpu_plane_helper_funcs = { + .prepare_fb = drm_gem_plane_helper_prepare_fb, + .atomic_check = dpu_plane_atomic_check, + .atomic_update = dpu_plane_atomic_update, +}; + +struct dpu_plane *dpu_plane_create(struct drm_device *drm, + unsigned int possible_crtcs, + unsigned int stream_id, + struct dpu_plane_grp *grp, + enum drm_plane_type type) +{ + struct dpu_plane *dpu_plane; + struct drm_plane *plane; + unsigned int zpos = dpu_plane_get_default_zpos(type); + int ret; + + dpu_plane = kzalloc(sizeof(*dpu_plane), GFP_KERNEL); + if (!dpu_plane) + return ERR_PTR(-ENOMEM); + + dpu_plane->stream_id = stream_id; + dpu_plane->grp = grp; + + plane = &dpu_plane->base; + + ret = drm_universal_plane_init(drm, plane, possible_crtcs, + &dpu_plane_funcs, + dpu_formats, ARRAY_SIZE(dpu_formats), + dpu_format_modifiers, type, NULL); + if (ret) + goto err; + + drm_plane_helper_add(plane, &dpu_plane_helper_funcs); + + ret = drm_plane_create_zpos_property(plane, + zpos, 0, grp->hw_plane_num - 1); + if (ret) + goto err; + + ret = drm_plane_create_alpha_property(plane); + if (ret) + goto err; + + ret = drm_plane_create_blend_mode_property(plane, + BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI) | + BIT(DRM_MODE_BLEND_COVERAGE)); + if (ret) + goto err; + + ret = drm_plane_create_color_properties(plane, + BIT(DRM_COLOR_YCBCR_BT601) | + BIT(DRM_COLOR_YCBCR_BT709), + BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) | + BIT(DRM_COLOR_YCBCR_FULL_RANGE), + DRM_COLOR_YCBCR_BT601, + DRM_COLOR_YCBCR_FULL_RANGE); + if (ret) + goto err; + + return dpu_plane; + +err: + kfree(dpu_plane); + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/imx/dpu/dpu-plane.h b/drivers/gpu/drm/imx/dpu/dpu-plane.h new file mode 100644 index 000000000000..8d42ad0c9ff4 --- /dev/null +++ b/drivers/gpu/drm/imx/dpu/dpu-plane.h @@ -0,0 +1,210 @@ +/* + * Copyright 2017-2019,2022 NXP + * + * 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. + */ + +#ifndef __DPU_PLANE_H__ +#define __DPU_PLANE_H__ + +#include <video/dpu.h> +#include "../imx-drm.h" + +#define MAX_DPU_PLANE_GRP (MAX_CRTC / 2) + +enum dpu_plane_src_type { + DPU_PLANE_SRC_FL, + DPU_PLANE_SRC_FW, + DPU_PLANE_SRC_FD, +}; + +struct dpu_plane { + struct drm_plane base; + struct dpu_plane_grp *grp; + struct list_head head; + unsigned int stream_id; +}; + +struct dpu_plane_state { + struct drm_plane_state base; + lb_prim_sel_t stage; + lb_sec_sel_t source; + dpu_block_id_t blend; + lb_prim_sel_t aux_stage; + lb_sec_sel_t aux_source; + dpu_block_id_t aux_blend; + + bool is_top; + bool use_prefetch; + bool use_aux_prefetch; + bool need_aux_source; + + /* used when pixel combiner is needed */ + unsigned int left_src_w; + unsigned int left_crtc_w; + unsigned int left_crtc_x; + unsigned int right_src_w; + unsigned int right_crtc_w; + unsigned int right_crtc_x; + + bool is_left_top; + bool is_right_top; +}; + +static const lb_prim_sel_t cf_stages[] = {LB_PRIM_SEL__CONSTFRAME0, + LB_PRIM_SEL__CONSTFRAME1}; +static const lb_prim_sel_t stages[] = {LB_PRIM_SEL__LAYERBLEND0, + LB_PRIM_SEL__LAYERBLEND1, + LB_PRIM_SEL__LAYERBLEND2, + LB_PRIM_SEL__LAYERBLEND3}; +/* TODO: Add source entries for subsidiary layers. */ +static const lb_sec_sel_t sources[] = {LB_SEC_SEL__FETCHLAYER0, + LB_SEC_SEL__FETCHWARP2, + LB_SEC_SEL__FETCHDECODE0, + LB_SEC_SEL__FETCHDECODE1}; +static const dpu_block_id_t blends[] = {ID_LAYERBLEND0, ID_LAYERBLEND1, + ID_LAYERBLEND2, ID_LAYERBLEND3}; + +static inline struct dpu_plane *to_dpu_plane(struct drm_plane *plane) +{ + return container_of(plane, struct dpu_plane, base); +} + +static inline struct dpu_plane_state * +to_dpu_plane_state(struct drm_plane_state *plane_state) +{ + return container_of(plane_state, struct dpu_plane_state, base); +} + +static inline int source_to_type(lb_sec_sel_t source) +{ + switch (source) { + case LB_SEC_SEL__FETCHLAYER0: + return DPU_PLANE_SRC_FL; + case LB_SEC_SEL__FETCHWARP2: + return DPU_PLANE_SRC_FW; + case LB_SEC_SEL__FETCHDECODE0: + case LB_SEC_SEL__FETCHDECODE1: + return DPU_PLANE_SRC_FD; + default: + break; + } + + WARN_ON(1); + return -EINVAL; +} + +static inline int source_to_id(lb_sec_sel_t source) +{ + int i, offset = 0; + int type = source_to_type(source); + + for (i = 0; i < ARRAY_SIZE(sources); i++) { + if (source != sources[i]) + continue; + + /* FetchLayer */ + if (type == DPU_PLANE_SRC_FL) + return i; + + /* FetchWarp or FetchDecode */ + while (offset < ARRAY_SIZE(sources)) { + if (source_to_type(sources[offset]) == type) + break; + offset++; + } + return i - offset; + } + + WARN_ON(1); + return -EINVAL; +} + +static inline struct dpu_fetchunit * +source_to_fu(struct dpu_plane_res *res, lb_sec_sel_t source) +{ + int fu_type = source_to_type(source); + int fu_id = source_to_id(source); + + if (fu_type < 0 || fu_id < 0) + return NULL; + + switch (fu_type) { + case DPU_PLANE_SRC_FD: + if (fu_id >= ARRAY_SIZE(res->fd)) { + WARN_ON(1); + return NULL; + } + + return res->fd[fu_id]; + case DPU_PLANE_SRC_FL: + if (fu_id >= ARRAY_SIZE(res->fl)) { + WARN_ON(1); + return NULL; + } + + return res->fl[fu_id]; + case DPU_PLANE_SRC_FW: + if (fu_id >= ARRAY_SIZE(res->fw)) { + WARN_ON(1); + return NULL; + } + + return res->fw[fu_id]; + } + + return NULL; +} + +static inline struct dpu_fetchunit * +dpstate_to_fu(struct dpu_plane_state *dpstate) +{ + struct drm_plane *plane = dpstate->base.plane; + struct dpu_plane *dplane = to_dpu_plane(plane); + struct dpu_plane_res *res = &dplane->grp->res; + + return source_to_fu(res, dpstate->source); +} + +static inline int blend_to_id(dpu_block_id_t blend) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(blends); i++) { + if (blend == blends[i]) + return i; + } + + WARN_ON(1); + return -EINVAL; +} + +static inline bool drm_format_is_yuv(uint32_t format) +{ + switch (format) { + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + return true; + default: + break; + } + + return false; +} + +struct dpu_plane *dpu_plane_create(struct drm_device *drm, + unsigned int possible_crtcs, + unsigned int stream_id, + struct dpu_plane_grp *grp, + enum drm_plane_type type); +#endif diff --git a/drivers/gpu/drm/imx/dw_hdmi-imx.c b/drivers/gpu/drm/imx/dw_hdmi-imx.c index a2277a0d6d06..b08eae5278d0 100644 --- a/drivers/gpu/drm/imx/dw_hdmi-imx.c +++ b/drivers/gpu/drm/imx/dw_hdmi-imx.c @@ -9,7 +9,9 @@ #include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/phy/phy.h> #include <linux/regmap.h> +#include <linux/reset.h> #include <video/imx-ipu-v3.h> @@ -22,6 +24,7 @@ #include <drm/drm_of.h> #include <drm/drm_simple_kms_helper.h> +#include "imx8mp-hdmi-pavi.h" #include "imx-drm.h" struct imx_hdmi; @@ -31,11 +34,20 @@ struct imx_hdmi_encoder { struct imx_hdmi *hdmi; }; +/* GPR reg */ +struct imx_hdmi_chip_data { + int reg_offset; + u32 mask_bits; + u32 shift_bit; +}; + struct imx_hdmi { struct device *dev; struct drm_bridge *bridge; struct dw_hdmi *hdmi; struct regmap *regmap; + const struct imx_hdmi_chip_data *chip_data; + struct phy *phy; }; static inline struct imx_hdmi *enc_to_imx_hdmi(struct drm_encoder *e) @@ -43,6 +55,19 @@ static inline struct imx_hdmi *enc_to_imx_hdmi(struct drm_encoder *e) return container_of(e, struct imx_hdmi_encoder, encoder)->hdmi; } +struct clk_bulk_data imx8mp_clocks[] = { + { .id = "pix_clk" }, + { .id = "phy_int" }, + { .id = "prep_clk" }, + { .id = "skp_clk" }, + { .id = "sfr_clk" }, + { .id = "cec_clk" }, + { .id = "apb_clk" }, + { .id = "hpi_clk" }, + { .id = "fdcc_ref" }, + { .id = "pipe_clk" }, +}; + static const struct dw_hdmi_mpll_config imx_mpll_cfg[] = { { 45250000, { @@ -101,7 +126,7 @@ static const struct dw_hdmi_curr_ctrl imx_cur_ctr[] = { * PREEMP config 0.00 * TX/CK level 10 */ -static const struct dw_hdmi_phy_config imx_phy_config[] = { +static const struct dw_hdmi_phy_config imx6_phy_config[] = { /*pixelclk symbol term vlev */ { 216000000, 0x800d, 0x0005, 0x01ad}, { ~0UL, 0x0000, 0x0000, 0x0000} @@ -112,9 +137,11 @@ static void dw_hdmi_imx_encoder_enable(struct drm_encoder *encoder) struct imx_hdmi *hdmi = enc_to_imx_hdmi(encoder); int mux = drm_of_encoder_active_port_id(hdmi->dev->of_node, encoder); - regmap_update_bits(hdmi->regmap, IOMUXC_GPR3, - IMX6Q_GPR3_HDMI_MUX_CTL_MASK, - mux << IMX6Q_GPR3_HDMI_MUX_CTL_SHIFT); + if (hdmi->chip_data->reg_offset < 0) + return; + + regmap_update_bits(hdmi->regmap, hdmi->chip_data->reg_offset, + hdmi->chip_data->mask_bits, mux << hdmi->chip_data->shift_bit); } static int dw_hdmi_imx_atomic_check(struct drm_encoder *encoder, @@ -124,6 +151,7 @@ static int dw_hdmi_imx_atomic_check(struct drm_encoder *encoder, struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24; + imx_crtc_state->bus_flags = DRM_BUS_FLAG_DE_HIGH; imx_crtc_state->di_hsync_pin = 2; imx_crtc_state->di_vsync_pin = 3; @@ -163,18 +191,169 @@ imx6dl_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data, return MODE_OK; } +static bool imx8mp_hdmi_check_clk_rate(int rate_khz) +{ + int rate = rate_khz * 1000; + + /* Check hdmi phy pixel clock support rate */ + if (rate != clk_round_rate(imx8mp_clocks[0].clk, rate)) + return false; + return true; +} + +static enum drm_mode_status +imx8mp_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->clock < 13500) + return MODE_CLOCK_LOW; + if (mode->clock > 297000) + return MODE_CLOCK_HIGH; + + if (!imx8mp_hdmi_check_clk_rate(mode->clock)) + return MODE_CLOCK_RANGE; + + /* We don't support double-clocked and Interlaced modes */ + if (mode->flags & DRM_MODE_FLAG_DBLCLK || + mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_BAD; + + return MODE_OK; +} + +struct imx_hdmi_chip_data imx6_chip_data = { + .reg_offset = IOMUXC_GPR3, + .mask_bits = IMX6Q_GPR3_HDMI_MUX_CTL_MASK, + .shift_bit = IMX6Q_GPR3_HDMI_MUX_CTL_SHIFT, +}; + static struct dw_hdmi_plat_data imx6q_hdmi_drv_data = { .mpll_cfg = imx_mpll_cfg, .cur_ctr = imx_cur_ctr, - .phy_config = imx_phy_config, + .phy_config = imx6_phy_config, .mode_valid = imx6q_hdmi_mode_valid, + .phy_data = &imx6_chip_data, }; static struct dw_hdmi_plat_data imx6dl_hdmi_drv_data = { .mpll_cfg = imx_mpll_cfg, .cur_ctr = imx_cur_ctr, - .phy_config = imx_phy_config, + .phy_config = imx6_phy_config, .mode_valid = imx6dl_hdmi_mode_valid, + .phy_data = &imx6_chip_data, +}; + +static int imx8mp_hdmi_phy_init(struct dw_hdmi *dw_hdmi, void *data, + const struct drm_display_info *display, + const struct drm_display_mode *mode) +{ + struct imx_hdmi *hdmi = (struct imx_hdmi *)data; + int val; + + dev_dbg(hdmi->dev, "%s\n", __func__); + + dw_hdmi_phy_reset(dw_hdmi); + + /* enable PVI */ + imx8mp_hdmi_pavi_powerup(); + imx8mp_hdmi_pvi_enable(mode); + + regmap_read(hdmi->regmap, 0x200, &val); + /* HDMI PHY power off */ + val |= 0x8; + regmap_write(hdmi->regmap, 0x200, val); + /* HDMI PHY power on */ + val &= ~0x8; + /* Enable CEC */ + val |= 0x2; + regmap_write(hdmi->regmap, 0x200, val); + + if (!hdmi->phy) + return 0; + + phy_power_on(hdmi->phy); + + return 0; +} + +static void imx8mp_hdmi_phy_disable(struct dw_hdmi *dw_hdmi, void *data) +{ + struct imx_hdmi *hdmi = (struct imx_hdmi *)data; + int val; + + dev_dbg(hdmi->dev, "%s\n", __func__); + if (!hdmi->phy) + return; + + /* disable PVI */ + imx8mp_hdmi_pvi_disable(); + imx8mp_hdmi_pavi_powerdown(); + + /* TODO */ + regmap_read(hdmi->regmap, 0x200, &val); + /* Disable CEC */ + val &= ~0x2; + /* Power down HDMI PHY + * TODO move PHY power off to hdmi phy driver + * val |= 0x8; + * regmap_write(hdmi->regmap, 0x200, val); + */ +} + +static int imx8mp_hdmimix_setup(struct imx_hdmi *hdmi) +{ + int ret; + + if (NULL == imx8mp_hdmi_pavi_init()) { + dev_err(hdmi->dev, "No pavi info found\n"); + return -EPROBE_DEFER; + } + + ret = device_reset(hdmi->dev); + if (ret == -EPROBE_DEFER) + return ret; + + ret = devm_clk_bulk_get(hdmi->dev, ARRAY_SIZE(imx8mp_clocks), imx8mp_clocks); + if (ret < 0) { + dev_err(hdmi->dev, "No hdmimix bulk clk got\n"); + return -EPROBE_DEFER; + } + + return clk_bulk_prepare_enable(ARRAY_SIZE(imx8mp_clocks), imx8mp_clocks); +} + +void imx8mp_hdmi_enable_audio(struct dw_hdmi *dw_hdmi, void *data, int channel, + int width, int rate, int non_pcm) +{ + imx8mp_hdmi_pai_enable(channel, width, rate, non_pcm); +} + +void imx8mp_hdmi_disable_audio(struct dw_hdmi *dw_hdmi, void *data) +{ + imx8mp_hdmi_pai_disable(); +} + +static const struct dw_hdmi_phy_ops imx8mp_hdmi_phy_ops = { + .init = imx8mp_hdmi_phy_init, + .disable = imx8mp_hdmi_phy_disable, + .read_hpd = dw_hdmi_phy_read_hpd, + .update_hpd = dw_hdmi_phy_update_hpd, + .setup_hpd = dw_hdmi_phy_setup_hpd, + .enable_audio = imx8mp_hdmi_enable_audio, + .disable_audio = imx8mp_hdmi_disable_audio, +}; + +struct imx_hdmi_chip_data imx8mp_chip_data = { + .reg_offset = -1, +}; + +static const struct dw_hdmi_plat_data imx8mp_hdmi_drv_data = { + .mode_valid = imx8mp_hdmi_mode_valid, + .phy_data = &imx8mp_chip_data, + .phy_ops = &imx8mp_hdmi_phy_ops, + .phy_name = "samsung_dw_hdmi_phy2", + .phy_force_vendor = true, }; static const struct of_device_id dw_hdmi_imx_dt_ids[] = { @@ -183,6 +362,9 @@ static const struct of_device_id dw_hdmi_imx_dt_ids[] = { }, { .compatible = "fsl,imx6dl-hdmi", .data = &imx6dl_hdmi_drv_data + }, { + .compatible = "fsl,imx8mp-hdmi", + .data = &imx8mp_hdmi_drv_data }, {}, }; @@ -221,6 +403,7 @@ static int dw_hdmi_imx_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; const struct of_device_id *match = of_match_node(dw_hdmi_imx_dt_ids, np); + struct dw_hdmi_plat_data *plat_data; struct imx_hdmi *hdmi; int ret; @@ -237,7 +420,29 @@ static int dw_hdmi_imx_probe(struct platform_device *pdev) return PTR_ERR(hdmi->regmap); } - hdmi->hdmi = dw_hdmi_probe(pdev, match->data); + hdmi->phy = devm_phy_optional_get(hdmi->dev, "hdmi"); + if (IS_ERR(hdmi->phy)) { + ret = PTR_ERR(hdmi->phy); + if (ret != -EPROBE_DEFER) + dev_err(hdmi->dev, "failed to get phy\n"); + return ret; + } + + plat_data = devm_kmemdup(&pdev->dev, match->data, + sizeof(*plat_data), GFP_KERNEL); + if (!plat_data) + return -ENOMEM; + + hdmi->chip_data = plat_data->phy_data; + plat_data->phy_data = hdmi; + + if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx8mp-hdmi")) { + ret = imx8mp_hdmimix_setup(hdmi); + if (ret < 0) + return ret; + } + + hdmi->hdmi = dw_hdmi_probe(pdev, plat_data); if (IS_ERR(hdmi->hdmi)) return PTR_ERR(hdmi->hdmi); @@ -265,11 +470,25 @@ static int dw_hdmi_imx_remove(struct platform_device *pdev) return 0; } +static int __maybe_unused dw_hdmi_imx_resume(struct device *dev) +{ + struct imx_hdmi *hdmi = dev_get_drvdata(dev); + + dw_hdmi_resume(hdmi->hdmi); + + return 0; +} + +static const struct dev_pm_ops dw_hdmi_imx_pm = { + SET_SYSTEM_SLEEP_PM_OPS(NULL, dw_hdmi_imx_resume) +}; + static struct platform_driver dw_hdmi_imx_platform_driver = { .probe = dw_hdmi_imx_probe, .remove = dw_hdmi_imx_remove, .driver = { .name = "dwhdmi-imx", + .pm = &dw_hdmi_imx_pm, .of_match_table = dw_hdmi_imx_dt_ids, }, }; diff --git a/drivers/gpu/drm/imx/dw_mipi_dsi-imx.c b/drivers/gpu/drm/imx/dw_mipi_dsi-imx.c new file mode 100644 index 000000000000..40de8b031d92 --- /dev/null +++ b/drivers/gpu/drm/imx/dw_mipi_dsi-imx.c @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 NXP + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/phy/phy-mipi-dphy.h> +#include <linux/pm_runtime.h> + +#include <drm/bridge/dw_mipi_dsi.h> +#include <drm/drm_bridge.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> +#include <drm/drm_simple_kms_helper.h> + +#include "imx-drm.h" + +#define enc_to_dsi(enc) container_of(enc, struct dw_mipi_dsi_imx, encoder) + +struct dw_mipi_dsi_imx { + struct device *dev; + struct drm_encoder encoder; + void __iomem *base; + + struct clk *byte_clk; + + struct phy *phy; + union phy_configure_opts phy_cfg; + + u32 lanes; + u32 format; + + struct dw_mipi_dsi *dmd; + struct dw_mipi_dsi_plat_data pdata; +}; + +static enum drm_mode_status +__dw_mipi_dsi_imx_mode_valid(void *priv_data, + const struct drm_display_mode *mode, + union phy_configure_opts *phy_cfg) +{ + struct dw_mipi_dsi_imx *dsi = priv_data; + struct device *dev = dsi->dev; + int bpp; + int ret; + + bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + if (bpp < 0) { + DRM_DEV_DEBUG(dev, "failed to get bpp for pixel format %d\n", + dsi->format); + return MODE_ERROR; + } + + ret = phy_mipi_dphy_get_default_config(mode->clock * MSEC_PER_SEC, + bpp, dsi->lanes, + &phy_cfg->mipi_dphy); + if (ret < 0) { + DRM_DEV_DEBUG(dev, "failed to get default phy cfg %d\n", ret); + return MODE_ERROR; + } + + ret = phy_validate(dsi->phy, PHY_MODE_MIPI_DPHY, 0, phy_cfg); + if (ret < 0) { + DRM_DEV_DEBUG(dev, "failed to validate phy cfg %d\n", ret); + return MODE_ERROR; + } + + return MODE_OK; +} + +static enum drm_mode_status +dw_mipi_dsi_imx_mode_valid(void *priv_data, + const struct drm_display_mode *mode) +{ + union phy_configure_opts phy_cfg; + + return __dw_mipi_dsi_imx_mode_valid(priv_data, mode, &phy_cfg); +} + +static int dw_mipi_dsi_imx_phy_init(void *priv_data) +{ + struct dw_mipi_dsi_imx *dsi = priv_data; + int ret; + + ret = phy_set_mode(dsi->phy, PHY_MODE_MIPI_DPHY); + if (ret) { + DRM_DEV_ERROR(dsi->dev, "failed to set phy mode: %d\n", ret); + return ret; + } + + ret = phy_init(dsi->phy); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "failed to init phy: %d\n", ret); + return ret; + } + + ret = phy_configure(dsi->phy, &dsi->phy_cfg); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "failed to configure phy: %d\n", ret); + goto uninit_phy; + } + + ret = phy_power_on(dsi->phy); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "failed to power on phy: %d\n", ret); + goto uninit_phy; + } + + return ret; + +uninit_phy: + phy_exit(dsi->phy); + return ret; +} + +static void dw_mipi_dsi_imx_phy_power_off(void *priv_data) +{ + struct dw_mipi_dsi_imx *dsi = priv_data; + int ret; + + ret = phy_power_off(dsi->phy); + if (ret < 0) + DRM_DEV_ERROR(dsi->dev, "failed to power off phy: %d\n", ret); + + ret = phy_exit(dsi->phy); + if (ret < 0) + DRM_DEV_ERROR(dsi->dev, "failed to exit phy: %d\n", ret); +} + +static int +dw_mipi_dsi_get_lane_mbps(void *priv_data, const struct drm_display_mode *mode, + unsigned long mode_flags, u32 lanes, u32 format, + unsigned int *lane_mbps) +{ + struct dw_mipi_dsi_imx *dsi = priv_data; + struct device *dev = dsi->dev; + unsigned long mpclk = DIV_ROUND_UP(mode->clock, MSEC_PER_SEC); + int bpp; + int ret; + + bpp = mipi_dsi_pixel_format_to_bpp(format); + if (bpp < 0) { + DRM_DEV_ERROR(dev, "failed to get bpp for pixel format %d\n", + format); + return bpp; + } + + *lane_mbps = mpclk * (bpp / lanes); + + ret = phy_mipi_dphy_get_default_config(mode->clock * MSEC_PER_SEC, + bpp, lanes, + &dsi->phy_cfg.mipi_dphy); + if (ret < 0) { + DRM_DEV_ERROR(dev, "failed to get default phy cfg %d\n", ret); + return ret; + } + + return 0; +} + +struct hstt { + unsigned int maxfreq; + struct dw_mipi_dsi_dphy_timing timing; +}; + +#define HSTT(_maxfreq, _c_lp2hs, _c_hs2lp, _d_lp2hs, _d_hs2lp) \ +{ \ + .maxfreq = (_maxfreq), \ + .timing = { \ + .clk_lp2hs = (_c_lp2hs), \ + .clk_hs2lp = (_c_hs2lp), \ + .data_lp2hs = (_d_lp2hs), \ + .data_hs2lp = (_d_hs2lp), \ + } \ +} + +/* Table A-4 High-Speed Transition Times */ +struct hstt hstt_table[] = { + HSTT(80, 21, 17, 15, 10), + HSTT(90, 23, 17, 16, 10), + HSTT(100, 22, 17, 16, 10), + HSTT(110, 25, 18, 17, 11), + HSTT(120, 26, 20, 18, 11), + HSTT(130, 27, 19, 19, 11), + HSTT(140, 27, 19, 19, 11), + HSTT(150, 28, 20, 20, 12), + HSTT(160, 30, 21, 22, 13), + HSTT(170, 30, 21, 23, 13), + HSTT(180, 31, 21, 23, 13), + HSTT(190, 32, 22, 24, 13), + HSTT(205, 35, 22, 25, 13), + HSTT(220, 37, 26, 27, 15), + HSTT(235, 38, 28, 27, 16), + HSTT(250, 41, 29, 30, 17), + HSTT(275, 43, 29, 32, 18), + HSTT(300, 45, 32, 35, 19), + HSTT(325, 48, 33, 36, 18), + HSTT(350, 51, 35, 40, 20), + HSTT(400, 59, 37, 44, 21), + HSTT(450, 65, 40, 49, 23), + HSTT(500, 71, 41, 54, 24), + HSTT(550, 77, 44, 57, 26), + HSTT(600, 82, 46, 64, 27), + HSTT(650, 87, 48, 67, 28), + HSTT(700, 94, 52, 71, 29), + HSTT(750, 99, 52, 75, 31), + HSTT(800, 105, 55, 82, 32), + HSTT(850, 110, 58, 85, 32), + HSTT(900, 115, 58, 88, 35), + HSTT(950, 120, 62, 93, 36), + HSTT(1000, 128, 63, 99, 38), + HSTT(1050, 132, 65, 102, 38), + HSTT(1100, 138, 67, 106, 39), + HSTT(1150, 146, 69, 112, 42), + HSTT(1200, 151, 71, 117, 43), + HSTT(1250, 153, 74, 120, 45), + HSTT(1300, 160, 73, 124, 46), + HSTT(1350, 165, 76, 130, 47), + HSTT(1400, 172, 78, 134, 49), + HSTT(1450, 177, 80, 138, 49), + HSTT(1500, 183, 81, 143, 52), + HSTT(1550, 191, 84, 147, 52), + HSTT(1600, 194, 85, 152, 52), + HSTT(1650, 201, 86, 155, 53), + HSTT(1700, 208, 88, 161, 53), + HSTT(1750, 212, 89, 165, 53), + HSTT(1800, 220, 90, 171, 54), + HSTT(1850, 223, 92, 175, 54), + HSTT(1900, 231, 91, 180, 55), + HSTT(1950, 236, 95, 185, 56), + HSTT(2000, 243, 97, 190, 56), + HSTT(2050, 248, 99, 194, 58), + HSTT(2100, 252, 100, 199, 59), + HSTT(2150, 259, 102, 204, 61), + HSTT(2200, 266, 105, 210, 62), + HSTT(2250, 269, 109, 213, 63), + HSTT(2300, 272, 109, 217, 65), + HSTT(2350, 281, 112, 225, 66), + HSTT(2400, 283, 115, 226, 66), + HSTT(2450, 282, 115, 226, 67), + HSTT(2500, 281, 118, 227, 67), +}; + +static int +dw_mipi_dsi_phy_get_timing(void *priv_data, unsigned int lane_mbps, + struct dw_mipi_dsi_dphy_timing *timing) +{ + struct dw_mipi_dsi_imx *dsi = priv_data; + int i; + + for (i = 0; i < ARRAY_SIZE(hstt_table); i++) + if (lane_mbps <= hstt_table[i].maxfreq) + break; + + if (i == ARRAY_SIZE(hstt_table)) + i--; + + *timing = hstt_table[i].timing; + + DRM_DEV_DEBUG(dsi->dev, "get phy timing for %u <= %u (lane_mbps)\n", + lane_mbps, hstt_table[i].maxfreq); + + return 0; +} + +static const struct dw_mipi_dsi_phy_ops dw_mipi_dsi_imx_phy_ops = { + .init = dw_mipi_dsi_imx_phy_init, + .power_off = dw_mipi_dsi_imx_phy_power_off, + .get_lane_mbps = dw_mipi_dsi_get_lane_mbps, + .get_timing = dw_mipi_dsi_phy_get_timing, +}; + +static int dw_mipi_dsi_imx_host_attach(void *priv_data, + struct mipi_dsi_device *device) +{ + struct dw_mipi_dsi_imx *dsi = priv_data; + + dsi->lanes = device->lanes; + dsi->format = device->format; + + return 0; +} + +static const struct dw_mipi_dsi_host_ops dw_mipi_dsi_imx_host_ops = { + .attach = dw_mipi_dsi_imx_host_attach, +}; + +static bool +dw_mipi_dsi_imx_hcomponents_need_fixup(struct dw_mipi_dsi_imx *dsi, + int bpp, + const struct drm_display_mode *mode) +{ + int htotal = mode->htotal; + int hsa = mode->hsync_end - mode->hsync_start; + int hbp = mode->htotal - mode->hsync_end; + int divisor = dsi->lanes * 8; + + /* + * It appears that (hcomponent * bpp) / (8 * lanes) + * should be no remainder. + */ + return !!((htotal * bpp) % divisor) || + !!((hsa * bpp) % divisor) || + !!((hbp * bpp) % divisor); +} + +static int dw_mipi_dsi_imx_fixup_hcomponent(struct dw_mipi_dsi_imx *dsi, + int bpp, int component) +{ + int divisor, i; + + divisor = dsi->lanes * 8; + + for (i = 0; i < divisor; i++) { + if ((bpp * (component + i)) % divisor == 0) { + component += i; + break; + } + } + + return component; +} + +static void +dw_mipi_dsi_imx_fixup_hcomponents(struct dw_mipi_dsi_imx *dsi, + int bpp, + const struct drm_display_mode *mode, + struct drm_display_mode *adj) +{ + struct device *dev = dsi->dev; + int hfp = mode->hsync_start - mode->hdisplay; + int hsa = mode->hsync_end - mode->hsync_start; + int hbp = mode->htotal - mode->hsync_end; + + hfp = dw_mipi_dsi_imx_fixup_hcomponent(dsi, bpp, hfp); + adj->hsync_start = adj->hdisplay + hfp; + + hsa = dw_mipi_dsi_imx_fixup_hcomponent(dsi, bpp, hsa); + adj->hsync_end = adj->hsync_start + hsa; + + hbp = dw_mipi_dsi_imx_fixup_hcomponent(dsi, bpp, hbp); + adj->htotal = adj->hsync_end + hbp; + + DRM_DEV_DEBUG(dev, "fixing up mode:\n"); + DRM_DEV_DEBUG(dev, "\tModeline " DRM_MODE_FMT "\n", DRM_MODE_ARG(mode)); + DRM_DEV_DEBUG(dev, "adjusted mode:\n"); + DRM_DEV_DEBUG(dev, "\tModeline " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj)); +} + +static int +dw_mipi_dsi_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct imx_crtc_state *s = to_imx_crtc_state(crtc_state); + struct dw_mipi_dsi_imx *dsi = enc_to_dsi(encoder); + struct device *dev = dsi->dev; + + switch (dsi->format) { + case MIPI_DSI_FMT_RGB888: + s->bus_format = MEDIA_BUS_FMT_RGB888_1X24; + break; + case MIPI_DSI_FMT_RGB666: + s->bus_format = MEDIA_BUS_FMT_RGB666_1X18; + break; + case MIPI_DSI_FMT_RGB565: + s->bus_format = MEDIA_BUS_FMT_RGB565_1X16; + break; + default: + DRM_DEV_ERROR(dev, "unsupported DSI format: 0x%x\n", + dsi->format); + return -EINVAL; + } + + s->bus_flags = DRM_BUS_FLAG_DE_HIGH; + + /* Force mode_changed to true, when CRTC state is changed to active. */ + if (crtc_state->active_changed && crtc_state->active) + crtc_state->mode_changed = true; + + if (crtc_state->active) { + const struct drm_display_mode *mode = &crtc_state->mode; + struct drm_display_mode *adj = &crtc_state->adjusted_mode; + union phy_configure_opts phy_cfg; + struct phy_configure_opts_mipi_dphy *cfg = &phy_cfg.mipi_dphy; + enum drm_mode_status mode_status; + int bpp; + + mode_status = __dw_mipi_dsi_imx_mode_valid(dsi, mode, &phy_cfg); + if (mode_status != MODE_OK) + return -EINVAL; + + bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + if (bpp < 0) + return -EINVAL; + + adj->clock = cfg->hs_clk_rate * dsi->lanes / bpp / MSEC_PER_SEC; + if (adj->clock != mode->clock) + DRM_DEV_DEBUG(dev, "adjust mode clock %dKHz->%dKHz\n", + mode->clock, adj->clock); + + if (dw_mipi_dsi_imx_hcomponents_need_fixup(dsi, bpp, mode)) + dw_mipi_dsi_imx_fixup_hcomponents(dsi, bpp, mode, adj); + } + + return 0; +} + +static void +dw_mipi_dsi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct dw_mipi_dsi_imx *dsi = enc_to_dsi(encoder); + + pm_runtime_get_sync(dsi->dev); +} + +static void dw_mipi_dsi_encoder_disable(struct drm_encoder *encoder) +{ + struct dw_mipi_dsi_imx *dsi = enc_to_dsi(encoder); + + pm_runtime_put(dsi->dev); +} + +static const struct drm_encoder_helper_funcs +dw_mipi_dsi_encoder_helper_funcs = { + .atomic_check = dw_mipi_dsi_encoder_atomic_check, + .mode_set = dw_mipi_dsi_encoder_mode_set, + .disable = dw_mipi_dsi_encoder_disable, +}; + +static int imx_dsi_drm_create_encoder(struct dw_mipi_dsi_imx *dsi, + struct drm_device *drm_dev) +{ + struct device *dev = dsi->dev; + struct drm_encoder *encoder = &dsi->encoder; + int ret; + + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, + dev->of_node); + + ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_DSI); + if (ret) { + DRM_DEV_ERROR(dev, + "failed to initialize encoder with drm: %d\n", + ret); + return ret; + } + + drm_encoder_helper_add(encoder, &dw_mipi_dsi_encoder_helper_funcs); + + return 0; +} + +static int +dw_mipi_dsi_imx_bind(struct device *dev, struct device *master, void *data) +{ + struct dw_mipi_dsi_imx *dsi = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + struct drm_bridge *bridge; + int ret; + + ret = imx_dsi_drm_create_encoder(dsi, drm_dev); + if (ret) { + DRM_DEV_ERROR(dev, "failed to create drm encoder\n"); + return ret; + } + + bridge = of_drm_find_bridge(dev->of_node); + if (!bridge) { + DRM_DEV_ERROR(dev, "failed to find drm bridge\n"); + return -ENODEV; + } + + ret = drm_bridge_attach(&dsi->encoder, bridge, NULL, 0); + if (ret) { + DRM_DEV_ERROR(dev, "failed to attach bridge: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct component_ops dw_mipi_dsi_imx_ops = { + .bind = dw_mipi_dsi_imx_bind, +}; + +static int dw_mipi_dsi_imx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dw_mipi_dsi_imx *dsi; + struct resource *res; + int ret; + + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dsi->base = devm_ioremap_resource(dev, res); + if (IS_ERR(dsi->base)) { + DRM_DEV_ERROR(dev, "failed to get dsi registers\n"); + return PTR_ERR(dsi->base); + } + + dsi->byte_clk = devm_clk_get(dev, "byte"); + if (IS_ERR(dsi->byte_clk)) { + ret = PTR_ERR(dsi->byte_clk); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "failed to get byte clk: %d\n", ret); + return ret; + } + + dsi->phy = devm_phy_get(dev, "dphy"); + if (IS_ERR(dsi->phy)) { + ret = PTR_ERR(dsi->phy); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "failed to get phy: %d\n", ret); + return ret; + } + + dsi->dev = dev; + dsi->pdata.base = dsi->base; + dsi->pdata.max_data_lanes = 4; + dsi->pdata.mode_valid = dw_mipi_dsi_imx_mode_valid; + dsi->pdata.phy_ops = &dw_mipi_dsi_imx_phy_ops; + dsi->pdata.host_ops = &dw_mipi_dsi_imx_host_ops; + dsi->pdata.priv_data = dsi; + platform_set_drvdata(pdev, dsi); + + dsi->dmd = dw_mipi_dsi_probe(pdev, &dsi->pdata); + if (IS_ERR(dsi->dmd)) { + ret = PTR_ERR(dsi->dmd); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, + "failed to probe dw_mipi_dsi: %d\n", ret); + return ret; + } + + ret = component_add(dev, &dw_mipi_dsi_imx_ops); + if (ret) { + DRM_DEV_ERROR(dev, "failed to register component: %d\n", ret); + dw_mipi_dsi_remove(dsi->dmd); + } + + return ret; +} + +static int dw_mipi_dsi_imx_remove(struct platform_device *pdev) +{ + struct dw_mipi_dsi_imx *dsi = platform_get_drvdata(pdev); + + component_del(dsi->dev, &dw_mipi_dsi_imx_ops); + + dw_mipi_dsi_remove(dsi->dmd); + + clk_disable_unprepare(dsi->byte_clk); + + return 0; +} + +#ifdef CONFIG_PM +static int dw_mipi_dsi_imx_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dw_mipi_dsi_imx *dsi = platform_get_drvdata(pdev); + + clk_disable_unprepare(dsi->byte_clk); + + return 0; +} + +static int dw_mipi_dsi_imx_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dw_mipi_dsi_imx *dsi = platform_get_drvdata(pdev); + int ret; + + ret = clk_prepare_enable(dsi->byte_clk); + if (ret) + DRM_DEV_ERROR(dev, "failed to enable byte_clk: %d\n", ret); + + return ret; +} + +static const struct dev_pm_ops dw_mipi_dsi_imx_pm_ops = { + SET_RUNTIME_PM_OPS(dw_mipi_dsi_imx_runtime_suspend, + dw_mipi_dsi_imx_runtime_resume, NULL) +}; +#endif + +static const struct of_device_id dw_mipi_dsi_imx_dt_ids[] = { + { .compatible = "fsl,imx93-mipi-dsi", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dw_mipi_dsi_imx_dt_ids); + +struct platform_driver dw_mipi_dsi_imx_driver = { + .probe = dw_mipi_dsi_imx_probe, + .remove = dw_mipi_dsi_imx_remove, + .driver = { + .of_match_table = dw_mipi_dsi_imx_dt_ids, + .name = "dw-mipi-dsi-imx", + .pm = &dw_mipi_dsi_imx_pm_ops, + }, +}; + +module_platform_driver(dw_mipi_dsi_imx_driver); + +MODULE_DESCRIPTION("Freescale i.MX93 Synopsys DesignWare MIPI DSI Host Controller driver"); +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:dw-mipi-dsi-imx"); diff --git a/drivers/gpu/drm/imx/imx-drm-core.c b/drivers/gpu/drm/imx/imx-drm-core.c index cb685fe2039b..25c9ab12f59c 100644 --- a/drivers/gpu/drm/imx/imx-drm-core.c +++ b/drivers/gpu/drm/imx/imx-drm-core.c @@ -12,6 +12,8 @@ #include <linux/platform_device.h> #include <video/imx-ipu-v3.h> +#include <video/imx-lcdif.h> +#include <video/imx-lcdifv3.h> #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> @@ -25,11 +27,10 @@ #include <drm/drm_plane_helper.h> #include <drm/drm_probe_helper.h> #include <drm/drm_vblank.h> +#include <video/dpu.h> +#include "dpu/dpu-blit.h" #include "imx-drm.h" -#include "ipuv3-plane.h" - -#define MAX_CRTC 4 static int legacyfb_depth = 16; module_param(legacyfb_depth, int, 0444); @@ -43,81 +44,6 @@ void imx_drm_connector_destroy(struct drm_connector *connector) } EXPORT_SYMBOL_GPL(imx_drm_connector_destroy); -static int imx_drm_atomic_check(struct drm_device *dev, - struct drm_atomic_state *state) -{ - int ret; - - ret = drm_atomic_helper_check(dev, state); - if (ret) - return ret; - - /* - * Check modeset again in case crtc_state->mode_changed is - * updated in plane's ->atomic_check callback. - */ - ret = drm_atomic_helper_check_modeset(dev, state); - if (ret) - return ret; - - /* Assign PRG/PRE channels and check if all constrains are satisfied. */ - ret = ipu_planes_assign_pre(dev, state); - if (ret) - return ret; - - return ret; -} - -static const struct drm_mode_config_funcs imx_drm_mode_config_funcs = { - .fb_create = drm_gem_fb_create, - .atomic_check = imx_drm_atomic_check, - .atomic_commit = drm_atomic_helper_commit, -}; - -static void imx_drm_atomic_commit_tail(struct drm_atomic_state *state) -{ - struct drm_device *dev = state->dev; - struct drm_plane *plane; - struct drm_plane_state *old_plane_state, *new_plane_state; - bool plane_disabling = false; - int i; - - drm_atomic_helper_commit_modeset_disables(dev, state); - - drm_atomic_helper_commit_planes(dev, state, - DRM_PLANE_COMMIT_ACTIVE_ONLY | - DRM_PLANE_COMMIT_NO_DISABLE_AFTER_MODESET); - - drm_atomic_helper_commit_modeset_enables(dev, state); - - for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) { - if (drm_atomic_plane_disabling(old_plane_state, new_plane_state)) - plane_disabling = true; - } - - /* - * The flip done wait is only strictly required by imx-drm if a deferred - * plane disable is in-flight. As the core requires blocking commits - * to wait for the flip it is done here unconditionally. This keeps the - * workitem around a bit longer than required for the majority of - * non-blocking commits, but we accept that for the sake of simplicity. - */ - drm_atomic_helper_wait_for_flip_done(dev, state); - - if (plane_disabling) { - for_each_old_plane_in_state(state, plane, old_plane_state, i) - ipu_plane_disable_deferred(plane); - - } - - drm_atomic_helper_commit_hw_done(state); -} - -static const struct drm_mode_config_helper_funcs imx_drm_mode_config_helpers = { - .atomic_commit_tail = imx_drm_atomic_commit_tail, -}; - - int imx_drm_encoder_parse_of(struct drm_device *drm, struct drm_encoder *encoder, struct device_node *np) { @@ -145,9 +71,9 @@ static const struct drm_ioctl_desc imx_drm_ioctls[] = { /* none so far */ }; -static int imx_drm_dumb_create(struct drm_file *file_priv, - struct drm_device *drm, - struct drm_mode_create_dumb *args) +static int imx_drm_ipu_dumb_create(struct drm_file *file_priv, + struct drm_device *drm, + struct drm_mode_create_dumb *args) { u32 width = args->width; int ret; @@ -162,9 +88,9 @@ static int imx_drm_dumb_create(struct drm_file *file_priv, return ret; } -static const struct drm_driver imx_drm_driver = { +static struct drm_driver imx_drm_driver = { .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, - DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(imx_drm_dumb_create), + DRM_GEM_CMA_DRIVER_OPS, .ioctls = imx_drm_ioctls, .num_ioctls = ARRAY_SIZE(imx_drm_ioctls), .fops = &imx_drm_driver_fops, @@ -176,6 +102,35 @@ static const struct drm_driver imx_drm_driver = { .patchlevel = 0, }; +static const struct drm_driver imx_drm_ipu_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(imx_drm_ipu_dumb_create), + .ioctls = imx_drm_ioctls, + .num_ioctls = ARRAY_SIZE(imx_drm_ioctls), + .fops = &imx_drm_driver_fops, + .name = "imx-drm", + .desc = "i.MX DRM graphics", + .date = "20120507", + .major = 1, + .minor = 0, + .patchlevel = 0, +}; + +static const struct drm_driver imx_drm_dpu_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC | + DRIVER_RENDER, + DRM_GEM_CMA_DRIVER_OPS, + .ioctls = imx_drm_dpu_ioctls, + .num_ioctls = ARRAY_SIZE(imx_drm_dpu_ioctls), + .fops = &imx_drm_driver_fops, + .name = "imx-drm", + .desc = "i.MX DRM graphics", + .date = "20120507", + .major = 1, + .minor = 0, + .patchlevel = 0, +}; + static int compare_of(struct device *dev, void *data) { struct device_node *np = data; @@ -185,6 +140,30 @@ static int compare_of(struct device *dev, void *data) struct ipu_client_platformdata *pdata = dev->platform_data; return pdata->of_node == np; + } else if (strcmp(dev->driver->name, "imx-dpu-crtc") == 0) { + struct dpu_client_platformdata *pdata = dev->platform_data; + + return pdata->of_node == np; + } else if (strcmp(dev->driver->name, "imx-lcdif-crtc") == 0 || + strcmp(dev->driver->name, "imx-lcdifv3-crtc") == 0) { + struct lcdif_client_platformdata *pdata = dev->platform_data; +#if IS_ENABLED(CONFIG_DRM_FBDEV_EMULATION) + /* set legacyfb_depth to be 32 for lcdif, since + * default format of the connectors attached to + * lcdif is usually RGB888 + */ + if (pdata->of_node == np) + legacyfb_depth = 32; +#endif + + return pdata->of_node == np; + } + + /* This is a special case for dpu bliteng. */ + if (strcmp(dev->driver->name, "imx-drm-dpu-bliteng") == 0) { + struct dpu_client_platformdata *pdata = dev->platform_data; + + return pdata->of_node == np; } /* Special case for LDB, one device for two channels */ @@ -196,12 +175,122 @@ static int compare_of(struct device *dev, void *data) return dev->of_node == np; } +static const char *const imx_drm_ipu_comp_parents[] = { + "fsl,imx51-ipu", + "fsl,imx53-ipu", + "fsl,imx6q-ipu", + "fsl,imx6qp-ipu", +}; + +static const char *const imx_drm_dpu_comp_parents[] = { + "fsl,imx8qm-dpu", + "fsl,imx8qxp-dpu", +}; + +static bool imx_drm_parent_is_compatible(struct device *dev, + const char *const comp_parents[], + int comp_parents_size) +{ + struct device_node *port, *parent; + bool ret = false; + int i; + + port = of_parse_phandle(dev->of_node, "ports", 0); + if (!port) + return ret; + + parent = of_get_parent(port); + + for (i = 0; i < comp_parents_size; i++) { + if (of_device_is_compatible(parent, comp_parents[i])) { + ret = true; + break; + } + } + + of_node_put(parent); + + of_node_put(port); + + return ret; +} + +static inline bool has_ipu(struct device *dev) +{ + return imx_drm_parent_is_compatible(dev, imx_drm_ipu_comp_parents, + ARRAY_SIZE(imx_drm_ipu_comp_parents)); +} + +static inline bool has_dpu(struct device *dev) +{ + return imx_drm_parent_is_compatible(dev, imx_drm_dpu_comp_parents, + ARRAY_SIZE(imx_drm_dpu_comp_parents)); +} + +static void add_dpu_bliteng_components(struct device *dev, + struct component_match **matchptr) +{ + /* + * As there may be two dpu bliteng device, + * so need add something in compare data to distinguish. + * Use its parent dpu's of_node as the data here. + */ + struct device_node *port, *parent; + /* assume max dpu number is 8 */ + struct device_node *dpu[8]; + int num_dpu = 0; + int i, j; + bool found = false; + + for (i = 0; ; i++) { + port = of_parse_phandle(dev->of_node, "ports", i); + if (!port) + break; + + parent = of_get_parent(port); + + for (j = 0; j < num_dpu; j++) { + if (dpu[j] == parent) { + found = true; + break; + } + } + + if (found) { + found = false; + } else { + if (num_dpu >= ARRAY_SIZE(dpu)) { + dev_err(dev, "The number of found dpu is greater than max [%ld].\n", + ARRAY_SIZE(dpu)); + of_node_put(parent); + of_node_put(port); + break; + } + + dpu[num_dpu] = parent; + num_dpu++; + + component_match_add(dev, matchptr, compare_of, parent); + } + + of_node_put(parent); + of_node_put(port); + } +} + static int imx_drm_bind(struct device *dev) { struct drm_device *drm; int ret; - drm = drm_dev_alloc(&imx_drm_driver, dev); + if (has_ipu(dev)) { + drm = drm_dev_alloc(&imx_drm_ipu_driver, dev); + } else if (has_dpu(dev)) { + drm = drm_dev_alloc(&imx_drm_dpu_driver, dev); + } else { + drm = drm_dev_alloc(&imx_drm_driver, dev); + } + if (IS_ERR(drm)) return PTR_ERR(drm); @@ -214,8 +303,6 @@ static int imx_drm_bind(struct device *dev) drm->mode_config.min_height = 1; drm->mode_config.max_width = 4096; drm->mode_config.max_height = 4096; - drm->mode_config.funcs = &imx_drm_mode_config_funcs; - drm->mode_config.helper_private = &imx_drm_mode_config_helpers; drm->mode_config.normalize_zpos = true; ret = drmm_mode_config_init(drm); @@ -226,8 +313,6 @@ static int imx_drm_bind(struct device *dev) if (ret) goto err_kms; - dev_set_drvdata(dev, drm); - /* Now try and bind all our sub-components */ ret = component_bind_all(dev, drm); if (ret) @@ -253,6 +338,8 @@ static int imx_drm_bind(struct device *dev) drm_fbdev_generic_setup(drm, legacyfb_depth); + dev_set_drvdata(dev, drm); + return 0; err_poll_fini: @@ -286,7 +373,14 @@ static const struct component_master_ops imx_drm_ops = { static int imx_drm_platform_probe(struct platform_device *pdev) { - int ret = drm_of_component_probe(&pdev->dev, compare_of, &imx_drm_ops); + struct component_match *match = NULL; + int ret; + + if (has_dpu(&pdev->dev)) + add_dpu_bliteng_components(&pdev->dev, &match); + + ret = drm_of_component_probe_with_match(&pdev->dev, match, compare_of, + &imx_drm_ops); if (!ret) ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); @@ -333,23 +427,7 @@ static struct platform_driver imx_drm_pdrv = { .of_match_table = imx_drm_dt_ids, }, }; - -static struct platform_driver * const drivers[] = { - &imx_drm_pdrv, - &ipu_drm_driver, -}; - -static int __init imx_drm_init(void) -{ - return platform_register_drivers(drivers, ARRAY_SIZE(drivers)); -} -module_init(imx_drm_init); - -static void __exit imx_drm_exit(void) -{ - platform_unregister_drivers(drivers, ARRAY_SIZE(drivers)); -} -module_exit(imx_drm_exit); +module_platform_driver(imx_drm_pdrv); MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); MODULE_DESCRIPTION("i.MX drm driver core"); diff --git a/drivers/gpu/drm/imx/imx-drm.h b/drivers/gpu/drm/imx/imx-drm.h index c3e1a3f14d30..2ab936f6faa0 100644 --- a/drivers/gpu/drm/imx/imx-drm.h +++ b/drivers/gpu/drm/imx/imx-drm.h @@ -2,6 +2,8 @@ #ifndef _IMX_DRM_H_ #define _IMX_DRM_H_ +#define MAX_CRTC 4 + struct device_node; struct drm_crtc; struct drm_connector; @@ -28,8 +30,6 @@ int imx_drm_init_drm(struct platform_device *pdev, int preferred_bpp); int imx_drm_exit_drm(void); -extern struct platform_driver ipu_drm_driver; - void imx_drm_mode_config_init(struct drm_device *drm); struct drm_gem_cma_object *imx_drm_fb_get_obj(struct drm_framebuffer *fb); @@ -39,7 +39,4 @@ int imx_drm_encoder_parse_of(struct drm_device *drm, void imx_drm_connector_destroy(struct drm_connector *connector); -int ipu_planes_assign_pre(struct drm_device *dev, - struct drm_atomic_state *state); - #endif /* _IMX_DRM_H_ */ diff --git a/drivers/gpu/drm/imx/imx8mp-hdmi-pavi.c b/drivers/gpu/drm/imx/imx8mp-hdmi-pavi.c new file mode 100644 index 000000000000..d93694d80334 --- /dev/null +++ b/drivers/gpu/drm/imx/imx8mp-hdmi-pavi.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright 2020 NXP + * + * Programe Video/Audio Interface between LCDIF and HDMI Ctrl in HDMIMIX + * + */ + +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <drm/drm_fourcc.h> + +#include "imx8mp-hdmi-pavi.h" + +#define DRIVER_NAME "imx-hdmi-pavi" + +#define HTX_PVI_CTRL 0x0 +#define HTX_PVI_IRQ_MASK 0x04 +#define HTX_TMG_GEN_DISP_LRC 0x10 +#define HTX_TMG_GEN_DE_ULC 0x14 +#define HTX_TMG_GEN_DE_LRC 0x18 +#define HTX_TMG_GEN_HSYNC 0x1c +#define HTX_TMG_GEN_VSYNC 0x20 +#define HTX_TMG_GEN_IRQ0 0x24 +#define HTX_TMG_GEN_IRQ1 0x28 +#define HTX_TMG_GEN_IRQ2 0x2c +#define HTX_TMG_GEN_IRQ3 0x30 +#define HTX_TMG_GEN_CFG 0x40 + +#define HTX_PAI_CTRL 0x800 +#define HTX_PAI_CTRL_EXT 0x804 +#define HTX_PAI_FIELD_CTRL 0x808 + +#define HTX_PAI_CTRL_ENABLE 1 + + +static struct imx8mp_hdmi_pavi *gpavi; + +/* PAI APIs */ +void imx8mp_hdmi_pai_enable(int channel, int width, int rate, int non_pcm) +{ + /* PAI set */ + writel((0x3030000 | ((channel-1) << 8)), + gpavi->base + HTX_PAI_CTRL_EXT); + + /* hbr */ + if (non_pcm && width == 32 && channel == 8 && rate == 192000) + writel(0x004e77df, gpavi->base + HTX_PAI_FIELD_CTRL); + else if (width == 32) + writel(0x1c8c675b, gpavi->base + HTX_PAI_FIELD_CTRL); + else + writel(0x1c0c675b, gpavi->base + HTX_PAI_FIELD_CTRL); + + /* PAI start running */ + writel(HTX_PAI_CTRL_ENABLE, gpavi->base + HTX_PAI_CTRL); +} +EXPORT_SYMBOL(imx8mp_hdmi_pai_enable); + +void imx8mp_hdmi_pai_disable(void) +{ + /* stop PAI */ + writel(0, gpavi->base + HTX_PAI_CTRL); +} +EXPORT_SYMBOL(imx8mp_hdmi_pai_disable); + +/* PVI APIs */ +void imx8mp_hdmi_pvi_enable(const struct drm_display_mode *mode) +{ + writel(0x00000003, gpavi->base + HTX_PVI_IRQ_MASK); + writel(0x08970464, gpavi->base + HTX_TMG_GEN_DISP_LRC); + writel(0x00bf0029, gpavi->base + HTX_TMG_GEN_DE_ULC); + writel(0x083f0460, gpavi->base + HTX_TMG_GEN_DE_LRC); + writel(0x0897002b, gpavi->base + HTX_TMG_GEN_HSYNC); + writel(0x04640004, gpavi->base + HTX_TMG_GEN_VSYNC); + writel(0x000100ff, gpavi->base + HTX_TMG_GEN_IRQ0); + writel(0x000100f0, gpavi->base + HTX_TMG_GEN_IRQ1); + writel(0x00010315, gpavi->base + HTX_TMG_GEN_IRQ2); + writel(0x00010207, gpavi->base + HTX_TMG_GEN_IRQ3); + writel(0x84640000, gpavi->base + HTX_TMG_GEN_CFG); + + /* DE/VSYN/HSYNC pol */ + if ((mode->flags & DRM_MODE_FLAG_PVSYNC) && + (mode->flags & DRM_MODE_FLAG_PHSYNC)) { + writel(0x00377004, gpavi->base + HTX_PVI_CTRL); + writel(0x00377005, gpavi->base + HTX_PVI_CTRL); + } else { + writel(0x00311004, gpavi->base + HTX_PVI_CTRL); + writel(0x00311005, gpavi->base + HTX_PVI_CTRL); + } +} +EXPORT_SYMBOL(imx8mp_hdmi_pvi_enable); + +void imx8mp_hdmi_pvi_disable(void) +{ + /* Stop PVI */ + writel(0x0, gpavi->base + HTX_PVI_CTRL); +} +EXPORT_SYMBOL(imx8mp_hdmi_pvi_disable); + +void imx8mp_hdmi_pavi_powerup(void) +{ + clk_prepare_enable(gpavi->clk_pvi); + clk_prepare_enable(gpavi->clk_pai); + + /* deassert pai reset */ + if (!gpavi->reset_pai) + reset_control_deassert(gpavi->reset_pai); + + /* deassert pvi reset */ + if (!gpavi->reset_pvi) + reset_control_deassert(gpavi->reset_pvi); +} +EXPORT_SYMBOL(imx8mp_hdmi_pavi_powerup); + +void imx8mp_hdmi_pavi_powerdown(void) +{ + /* set pvi reset */ + if (!gpavi->reset_pvi) + reset_control_assert(gpavi->reset_pvi); + + /* set pai reset */ + if (!gpavi->reset_pai) + reset_control_assert(gpavi->reset_pai); + + clk_disable_unprepare(gpavi->clk_pai); + clk_disable_unprepare(gpavi->clk_pvi); +} +EXPORT_SYMBOL(imx8mp_hdmi_pavi_powerdown); + +struct imx8mp_hdmi_pavi *imx8mp_hdmi_pavi_init(void) +{ + return gpavi; +} +EXPORT_SYMBOL(imx8mp_hdmi_pavi_init); + +static int imx8mp_hdmi_pavi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx8mp_hdmi_pavi *pavi; + struct resource *res; + + dev_dbg(dev, "%s: probe begin\n", __func__); + + pavi = devm_kzalloc(dev, sizeof(*pavi), GFP_KERNEL); + if (!pavi) { + dev_err(dev, "Can't allocate 'imx8mp pavi' structure\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + pavi->base = devm_ioremap_resource(dev, res); + if (IS_ERR(pavi->base)) + return PTR_ERR(pavi->base); + + pavi->clk_pvi = devm_clk_get(dev, "pvi_clk"); + if (IS_ERR(pavi->clk_pvi)) { + dev_err(dev, "No pvi clock get\n"); + return -EPROBE_DEFER; + } + + pavi->clk_pai = devm_clk_get(dev, "pai_clk"); + if (IS_ERR(pavi->clk_pai)) { + dev_err(dev, "No pai clock get\n"); + return -EPROBE_DEFER; + } + + pavi->reset_pai = devm_reset_control_get(dev, "pai_rst"); + if (IS_ERR(pavi->reset_pai)) { + dev_err(pavi->dev, "No PAI reset\n"); + return -EPROBE_DEFER; + } + + pavi->reset_pvi = devm_reset_control_get(dev, "pvi_rst"); + if (IS_ERR(pavi->reset_pvi)) { + dev_err(pavi->dev, "No PVI reset\n"); + return -EPROBE_DEFER; + } + + platform_set_drvdata(pdev, pavi); + + gpavi = pavi; + + dev_dbg(dev, "%s: probe success\n", __func__); + return 0; +} + +static int imx8mp_hdmi_pavi_remove(struct platform_device *pdev) +{ + gpavi = NULL; + return 0; +} + +static const struct of_device_id imx8mp_hdmi_pavi_dt_ids[] = { + { .compatible = "fsl,imx8mp-hdmi-pavi", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx8mp_hdmi_pavi_dt_ids); + +struct platform_driver imx8mp_hdmi_pavi_driver = { + .probe = imx8mp_hdmi_pavi_probe, + .remove = imx8mp_hdmi_pavi_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = imx8mp_hdmi_pavi_dt_ids, + }, +}; + +module_platform_driver(imx8mp_hdmi_pavi_driver); + +MODULE_DESCRIPTION("NXP i.MX8MP HDMI PAI/PVI Mix driver"); +MODULE_AUTHOR("Sandor Yu <Sandor.yu@nxp.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/imx/imx8mp-hdmi-pavi.h b/drivers/gpu/drm/imx/imx8mp-hdmi-pavi.h new file mode 100644 index 000000000000..ef90bf46cb97 --- /dev/null +++ b/drivers/gpu/drm/imx/imx8mp-hdmi-pavi.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright 2020 NXP + * + * PAI/PVI Head file + * + */ +#ifndef _IMX8MP_HDMI_AV_CTL_H_ +#define _IMX8MP_HDMI_AV_CTL_H_ + +#include <linux/clk.h> +#include <linux/reset.h> +#include <drm/drm_modes.h> + +struct imx8mp_hdmi_pavi { + struct device *dev; + + void __iomem *base; + atomic_t rpm_suspended; + + struct clk *clk_pai; + struct clk *clk_pvi; + struct reset_control *reset_pai; + struct reset_control *reset_pvi; +}; + +void imx8mp_hdmi_pai_enable(int channel, int width, int rate, int non_pcm); +void imx8mp_hdmi_pai_disable(void); + +void imx8mp_hdmi_pvi_enable(const struct drm_display_mode *mode); +void imx8mp_hdmi_pvi_disable(void); + +void imx8mp_hdmi_pavi_powerup(void); +void imx8mp_hdmi_pavi_powerdown(void); + +struct imx8mp_hdmi_pavi *imx8mp_hdmi_pavi_init(void); + +#endif /* _IMX8MP_HDMI_PAVI_H_ */ diff --git a/drivers/gpu/drm/imx/imx8mp-ldb.c b/drivers/gpu/drm/imx/imx8mp-ldb.c new file mode 100644 index 000000000000..be969f453587 --- /dev/null +++ b/drivers/gpu/drm/imx/imx8mp-ldb.c @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2020,2022 NXP + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> + +#include <drm/bridge/fsl_imx_ldb.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> + +#include "imx-drm.h" + +#define DRIVER_NAME "imx8mp-ldb" + +#define LDB_CH0_MODE_EN_TO_DI0 (1 << 0) +#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0) +#define LDB_CH0_MODE_EN_MASK (3 << 0) +#define LDB_CH1_MODE_EN_TO_DI0 (1 << 2) +#define LDB_CH1_MODE_EN_TO_DI1 (3 << 2) +#define LDB_CH1_MODE_EN_MASK (3 << 2) +#define LDB_REG_CH0_FIFO_RESET (1 << 11) +#define LDB_REG_CH1_FIFO_RESET (1 << 12) +#define LDB_REG_ASYNC_FIFO_EN (1 << 24) +#define LDB_FIFO_THRESHOLD (4 << 25) + +struct imx8mp_ldb; + +struct imx8mp_ldb_channel { + struct ldb_channel base; + struct imx8mp_ldb *imx8mp_ldb; + + struct drm_connector connector; + struct drm_encoder encoder; + + struct phy *phy; + bool phy_is_on; + + u32 bus_flags; +}; + +static inline struct imx8mp_ldb_channel * +con_to_imx8mp_ldb_ch(struct drm_connector *c) +{ + return container_of(c, struct imx8mp_ldb_channel, connector); +} + +static inline struct imx8mp_ldb_channel * +enc_to_imx8mp_ldb_ch(struct drm_encoder *e) +{ + return container_of(e, struct imx8mp_ldb_channel, encoder); +} + +struct imx8mp_ldb { + struct ldb base; + struct imx8mp_ldb_channel channel[LDB_CH_NUM]; + struct clk *clk_root; +}; + +static struct drm_encoder *imx8mp_ldb_connector_best_encoder( + struct drm_connector *connector) +{ + struct imx8mp_ldb_channel *imx8mp_ldb_ch = + con_to_imx8mp_ldb_ch(connector); + + return &imx8mp_ldb_ch->encoder; +} + +static void imx8mp_ldb_encoder_enable(struct drm_encoder *encoder) +{ + struct imx8mp_ldb_channel *imx8mp_ldb_ch = + enc_to_imx8mp_ldb_ch(encoder); + struct imx8mp_ldb *imx8mp_ldb = imx8mp_ldb_ch->imx8mp_ldb; + struct ldb *ldb = &imx8mp_ldb->base; + + clk_prepare_enable(imx8mp_ldb->clk_root); + + if (imx8mp_ldb_ch == &imx8mp_ldb->channel[0] || ldb->dual) { + ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; + ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0; + } + if (imx8mp_ldb_ch == &imx8mp_ldb->channel[1] || ldb->dual) { + ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; + ldb->ldb_ctrl |= ldb->dual ? + LDB_CH1_MODE_EN_TO_DI0 : LDB_CH1_MODE_EN_TO_DI1; + } + + if (ldb->dual) { + phy_power_on(imx8mp_ldb->channel[0].phy); + phy_power_on(imx8mp_ldb->channel[1].phy); + + imx8mp_ldb->channel[0].phy_is_on = true; + imx8mp_ldb->channel[1].phy_is_on = true; + } else { + phy_power_on(imx8mp_ldb_ch->phy); + + imx8mp_ldb_ch->phy_is_on = true; + } +} + +static void +imx8mp_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *connector_state) +{ + struct imx8mp_ldb_channel *imx8mp_ldb_ch = + enc_to_imx8mp_ldb_ch(encoder); + struct imx8mp_ldb *imx8mp_ldb = imx8mp_ldb_ch->imx8mp_ldb; + struct ldb_channel *ldb_ch = &imx8mp_ldb_ch->base; + struct ldb *ldb = &imx8mp_ldb->base; + struct drm_display_mode *mode = &crtc_state->adjusted_mode; + unsigned long serial_clk; + + if (mode->clock > 160000) { + dev_warn(ldb->dev, + "%s: mode exceeds 160 MHz pixel clock\n", __func__); + } + if (mode->clock > 80000 && !ldb->dual) { + dev_warn(ldb->dev, + "%s: mode exceeds 80 MHz pixel clock\n", __func__); + } + + serial_clk = mode->clock * (ldb->dual ? 3500UL : 7000UL); + clk_set_rate(imx8mp_ldb->clk_root, serial_clk); + + if (!ldb_ch->bus_format) { + struct drm_connector *connector = connector_state->connector; + struct drm_display_info *di = &connector->display_info; + + if (di->num_bus_formats) + ldb_ch->bus_format = di->bus_formats[0]; + } +} + +static void imx8mp_ldb_encoder_disable(struct drm_encoder *encoder) +{ + struct imx8mp_ldb_channel *imx8mp_ldb_ch = + enc_to_imx8mp_ldb_ch(encoder); + struct imx8mp_ldb *imx8mp_ldb = imx8mp_ldb_ch->imx8mp_ldb; + struct ldb *ldb = &imx8mp_ldb->base; + + if (ldb->dual) { + phy_power_off(imx8mp_ldb->channel[0].phy); + phy_power_off(imx8mp_ldb->channel[1].phy); + + imx8mp_ldb->channel[0].phy_is_on = false; + imx8mp_ldb->channel[1].phy_is_on = false; + } else { + phy_power_off(imx8mp_ldb_ch->phy); + + imx8mp_ldb_ch->phy_is_on = false; + } + + clk_disable_unprepare(imx8mp_ldb->clk_root); +} + +static int +imx8mp_ldb_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); + struct imx8mp_ldb_channel *imx8mp_ldb_ch = + enc_to_imx8mp_ldb_ch(encoder); + struct ldb_channel *ldb_ch = &imx8mp_ldb_ch->base; + struct imx8mp_ldb *imx8mp_ldb = imx8mp_ldb_ch->imx8mp_ldb; + struct ldb *ldb = &imx8mp_ldb->base; + struct drm_display_info *di = &conn_state->connector->display_info; + struct drm_display_mode *mode = &crtc_state->adjusted_mode; + u32 bus_format = ldb_ch->bus_format; + + /* Bus format description in DT overrides connector display info. */ + if (!bus_format && di->num_bus_formats) { + bus_format = di->bus_formats[0]; + imx_crtc_state->bus_flags = di->bus_flags; + } else { + bus_format = ldb_ch->bus_format; + imx_crtc_state->bus_flags = imx8mp_ldb_ch->bus_flags; + } + switch (bus_format) { + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB666_1X18; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24; + break; + default: + return -EINVAL; + } + + /* + * Due to limited video PLL frequency points on i.MX8mp, + * we do mode fixup here in case any mode is unsupported. + */ + if (ldb->dual) + mode->clock = mode->clock > 100000 ? 148500 : 74250; + else + mode->clock = 74250; + + return 0; +} + +static enum drm_mode_status +imx8mp_ldb_encoder_mode_valid(struct drm_encoder *encoder, + const struct drm_display_mode *mode) +{ + struct imx8mp_ldb_channel *imx8mp_ldb_ch = + enc_to_imx8mp_ldb_ch(encoder); + struct ldb_channel *ldb_ch = &imx8mp_ldb_ch->base; + struct imx8mp_ldb *imx8mp_ldb = imx8mp_ldb_ch->imx8mp_ldb; + struct ldb *ldb = &imx8mp_ldb->base; + + /* it should be okay with a panel */ + if (ldb_ch->panel) + return MODE_OK; + + /* + * Due to limited video PLL frequency points on i.MX8mp, + * we do mode valid check here. + */ + if (ldb->dual && mode->clock != 74250 && mode->clock != 148500) + return MODE_NOCLOCK; + + if (!ldb->dual && mode->clock != 74250) + return MODE_NOCLOCK; + + return MODE_OK; +} + +static const struct drm_connector_funcs imx8mp_ldb_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = imx_drm_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_connector_helper_funcs +imx8mp_ldb_connector_helper_funcs = { + .best_encoder = imx8mp_ldb_connector_best_encoder, +}; + +static const struct drm_encoder_helper_funcs imx8mp_ldb_encoder_helper_funcs = { + .atomic_mode_set = imx8mp_ldb_encoder_atomic_mode_set, + .enable = imx8mp_ldb_encoder_enable, + .disable = imx8mp_ldb_encoder_disable, + .atomic_check = imx8mp_ldb_encoder_atomic_check, + .mode_valid = imx8mp_ldb_encoder_mode_valid, +}; + +static const struct of_device_id imx8mp_ldb_dt_ids[] = { + { .compatible = "fsl,imx8mp-ldb", }, + { } +}; +MODULE_DEVICE_TABLE(of, imx8mp_ldb_dt_ids); + +static int +imx8mp_ldb_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm = data; + struct device_node *np = dev->of_node; + struct device_node *child; + struct imx8mp_ldb *imx8mp_ldb = dev_get_drvdata(dev); + struct ldb *ldb; + struct ldb_channel *ldb_ch; + struct drm_encoder *encoder[LDB_CH_NUM]; + int ret; + int i; + + ldb = &imx8mp_ldb->base; + ldb->dev = dev; + ldb->ctrl_reg = 0x5c, + ldb->output_port = 1; + + for (i = 0; i < LDB_CH_NUM; i++) { + imx8mp_ldb->channel[i].imx8mp_ldb = imx8mp_ldb; + ldb->channel[i] = &imx8mp_ldb->channel[i].base; + } + + imx8mp_ldb->clk_root = devm_clk_get(dev, "ldb"); + if (IS_ERR(imx8mp_ldb->clk_root)) + return PTR_ERR(imx8mp_ldb->clk_root); + + for_each_child_of_node(np, child) { + ret = of_property_read_u32(child, "reg", &i); + if (ret || i < 0 || i > 1) + return -EINVAL; + + if (!of_device_is_available(child)) + continue; + + encoder[i] = &imx8mp_ldb->channel[i].encoder; + + drm_encoder_helper_add(encoder[i], + &imx8mp_ldb_encoder_helper_funcs); + drm_simple_encoder_init(drm, encoder[i], DRM_MODE_ENCODER_LVDS); + } + + pm_runtime_enable(dev); + + ret = ldb_bind(ldb, encoder); + if (ret) + goto disable_pm_runtime; + + for_each_child_of_node(np, child) { + struct imx8mp_ldb_channel *imx8mp_ldb_ch; + bool auxiliary_ch = false; + + ret = of_property_read_u32(child, "reg", &i); + if (ret || i < 0 || i > 1) { + ret = -EINVAL; + goto free_child; + } + + if (ldb->dual && i > 0) { + auxiliary_ch = true; + imx8mp_ldb_ch = &imx8mp_ldb->channel[i]; + goto get_phy; + } + + if (!of_device_is_available(child)) + continue; + + imx8mp_ldb_ch = &imx8mp_ldb->channel[i]; +get_phy: + imx8mp_ldb_ch->phy = devm_of_phy_get(dev, child, "ldb_phy"); + if (IS_ERR(imx8mp_ldb_ch->phy)) { + ret = PTR_ERR(imx8mp_ldb_ch->phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "can't get channel%d phy: %d\n", + i, ret); + goto free_child; + } + + ret = phy_init(imx8mp_ldb_ch->phy); + if (ret < 0) { + dev_err(dev, "failed to initialize channel%d phy: %d\n", + i, ret); + goto free_child; + } + + if (auxiliary_ch) + continue; + } + + for (i = 0; i < LDB_CH_NUM; i++) { + ldb_ch = &imx8mp_ldb->channel[i].base; + + if (!ldb_ch->is_valid) + continue; + + ret = imx_drm_encoder_parse_of(drm, encoder[i], ldb_ch->child); + if (ret) + goto disable_pm_runtime; + } + + return 0; + +free_child: + of_node_put(child); +disable_pm_runtime: + pm_runtime_disable(dev); + + return ret; +} + +static void imx8mp_ldb_unbind(struct device *dev, struct device *master, + void *data) +{ + struct imx8mp_ldb *imx8mp_ldb = dev_get_drvdata(dev); + int i; + + for (i = 0; i < LDB_CH_NUM; i++) { + struct imx8mp_ldb_channel *imx8mp_ldb_ch = + &imx8mp_ldb->channel[i]; + + if (imx8mp_ldb_ch->phy_is_on) + phy_power_off(imx8mp_ldb_ch->phy); + + phy_exit(imx8mp_ldb_ch->phy); + } + + pm_runtime_disable(dev); +} + +static const struct component_ops imx8mp_ldb_ops = { + .bind = imx8mp_ldb_bind, + .unbind = imx8mp_ldb_unbind, +}; + +static int imx8mp_ldb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx8mp_ldb *imx8mp_ldb; + + imx8mp_ldb = devm_kzalloc(dev, sizeof(*imx8mp_ldb), GFP_KERNEL); + if (!imx8mp_ldb) + return -ENOMEM; + + dev_set_drvdata(dev, imx8mp_ldb); + + return component_add(dev, &imx8mp_ldb_ops); +} + +static int imx8mp_ldb_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &imx8mp_ldb_ops); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int imx8mp_ldb_suspend(struct device *dev) +{ + struct imx8mp_ldb *imx8mp_ldb = dev_get_drvdata(dev); + int i; + + if (imx8mp_ldb == NULL) + return 0; + + for (i = 0; i < LDB_CH_NUM; i++) + phy_exit(imx8mp_ldb->channel[i].phy); + + return 0; +} + +static int imx8mp_ldb_resume(struct device *dev) +{ + struct imx8mp_ldb *imx8mp_ldb = dev_get_drvdata(dev); + int i; + + if (imx8mp_ldb == NULL) + return 0; + + for (i = 0; i < LDB_CH_NUM; i++) + phy_init(imx8mp_ldb->channel[i].phy); + + return 0; +} +#endif + +static const struct dev_pm_ops imx8mp_ldb_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(imx8mp_ldb_suspend, imx8mp_ldb_resume) +}; + +static struct platform_driver imx8mp_ldb_driver = { + .probe = imx8mp_ldb_probe, + .remove = imx8mp_ldb_remove, + .driver = { + .of_match_table = imx8mp_ldb_dt_ids, + .name = DRIVER_NAME, + .pm = &imx8mp_ldb_pm_ops, + }, +}; + +module_platform_driver(imx8mp_ldb_driver); + +MODULE_DESCRIPTION("i.MX8MP LVDS driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/imx/imx8qm-ldb.c b/drivers/gpu/drm/imx/imx8qm-ldb.c new file mode 100644 index 000000000000..383c80014fdf --- /dev/null +++ b/drivers/gpu/drm/imx/imx8qm-ldb.c @@ -0,0 +1,571 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2020,2022 NXP + */ + +#include <dt-bindings/firmware/imx/rsrc.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/firmware/imx/sci.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/phy/phy-mixel-lvds.h> + +#include <drm/bridge/fsl_imx_ldb.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> + +#include "imx-drm.h" + +#define DRIVER_NAME "imx8qm-ldb" + +#define LDB_CH0_MODE_EN_TO_DI0 (1 << 0) +#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0) +#define LDB_CH0_MODE_EN_MASK (3 << 0) +#define LDB_CH1_MODE_EN_TO_DI0 (1 << 2) +#define LDB_CH1_MODE_EN_TO_DI1 (3 << 2) +#define LDB_CH1_MODE_EN_MASK (3 << 2) +#define LDB_BIT_MAP_CH0_JEIDA (1 << 6) +#define LDB_BIT_MAP_CH1_JEIDA (1 << 8) +#define LDB_CH0_10BIT_EN (1 << 22) +#define LDB_CH1_10BIT_EN (1 << 23) +#define LDB_CH0_DATA_WIDTH_24BIT (1 << 24) +#define LDB_CH1_DATA_WIDTH_24BIT (1 << 26) +#define LDB_CH0_DATA_WIDTH_30BIT (2 << 24) +#define LDB_CH1_DATA_WIDTH_30BIT (2 << 26) + +struct imx8qm_ldb; + +struct imx8qm_ldb_channel { + struct ldb_channel base; + struct imx8qm_ldb *imx8qm_ldb; + + struct drm_connector connector; + struct drm_encoder encoder; + + struct phy *phy; + bool phy_is_on; + + u32 bus_flags; +}; + +static inline struct imx8qm_ldb_channel * +con_to_imx8qm_ldb_ch(struct drm_connector *c) +{ + return container_of(c, struct imx8qm_ldb_channel, connector); +} + +static inline struct imx8qm_ldb_channel * +enc_to_imx8qm_ldb_ch(struct drm_encoder *e) +{ + return container_of(e, struct imx8qm_ldb_channel, encoder); +} + +struct imx8qm_ldb { + struct ldb base; + struct imx8qm_ldb_channel channel[LDB_CH_NUM]; + struct clk *clk_pixel; + struct clk *clk_bypass; + struct imx_sc_ipc *handle; + + int id; +}; + +static void +imx8qm_ldb_ch_set_bus_format(struct imx8qm_ldb_channel *imx8qm_ldb_ch, + u32 bus_format) +{ + struct imx8qm_ldb *imx8qm_ldb = imx8qm_ldb_ch->imx8qm_ldb; + struct ldb *ldb = &imx8qm_ldb->base; + struct ldb_channel *ldb_ch = &imx8qm_ldb_ch->base; + + switch (bus_format) { + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + if (ldb_ch->chno == 0 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH0_DATA_WIDTH_24BIT; + if (ldb_ch->chno == 1 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH1_DATA_WIDTH_24BIT; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + if (ldb_ch->chno == 0 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH0_DATA_WIDTH_24BIT; + if (ldb_ch->chno == 1 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH1_DATA_WIDTH_24BIT; + break; + case MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG: + if (ldb_ch->chno == 0 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH0_10BIT_EN | + LDB_CH0_DATA_WIDTH_30BIT; + if (ldb_ch->chno == 1 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH1_10BIT_EN | + LDB_CH1_DATA_WIDTH_30BIT; + break; + case MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA: + if (ldb_ch->chno == 0 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH0_10BIT_EN | + LDB_CH0_DATA_WIDTH_30BIT | + LDB_BIT_MAP_CH0_JEIDA; + if (ldb_ch->chno == 1 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH1_10BIT_EN | + LDB_CH1_DATA_WIDTH_30BIT | + LDB_BIT_MAP_CH1_JEIDA; + break; + } +} + +static struct drm_encoder *imx8qm_ldb_connector_best_encoder( + struct drm_connector *connector) +{ + struct imx8qm_ldb_channel *imx8qm_ldb_ch = + con_to_imx8qm_ldb_ch(connector); + + return &imx8qm_ldb_ch->encoder; +} + +static void imx8qm_ldb_pxlink_set_mst_valid(struct imx8qm_ldb *imx8qm_ldb, + int dc_id, bool enable) +{ + u32 rsc = dc_id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0; + + imx_sc_misc_set_control(imx8qm_ldb->handle, + rsc, IMX_SC_C_PXL_LINK_MST2_VLD, enable); +} + +static void imx8qm_ldb_pxlink_set_sync_ctrl(struct imx8qm_ldb *imx8qm_ldb, + int dc_id, bool enable) +{ + u32 rsc = dc_id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0; + + imx_sc_misc_set_control(imx8qm_ldb->handle, + rsc, IMX_SC_C_SYNC_CTRL1, enable); +} + +static void imx8qm_ldb_encoder_enable(struct drm_encoder *encoder) +{ + struct imx8qm_ldb_channel *imx8qm_ldb_ch = + enc_to_imx8qm_ldb_ch(encoder); + struct imx8qm_ldb *imx8qm_ldb = imx8qm_ldb_ch->imx8qm_ldb; + struct ldb *ldb = &imx8qm_ldb->base; + + clk_prepare_enable(imx8qm_ldb->clk_pixel); + clk_prepare_enable(imx8qm_ldb->clk_bypass); + + if (imx8qm_ldb_ch == &imx8qm_ldb->channel[0] || ldb->dual) { + ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; + ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0; + } + if (imx8qm_ldb_ch == &imx8qm_ldb->channel[1] || ldb->dual) { + ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; + ldb->ldb_ctrl |= ldb->dual ? + LDB_CH1_MODE_EN_TO_DI0 : LDB_CH1_MODE_EN_TO_DI1; + } + + if (ldb->dual) { + phy_power_on(imx8qm_ldb->channel[0].phy); + phy_power_on(imx8qm_ldb->channel[1].phy); + + imx8qm_ldb->channel[0].phy_is_on = true; + imx8qm_ldb->channel[1].phy_is_on = true; + } else { + phy_power_on(imx8qm_ldb_ch->phy); + + imx8qm_ldb_ch->phy_is_on = true; + } + + imx8qm_ldb_pxlink_set_mst_valid(imx8qm_ldb, imx8qm_ldb->id, true); + imx8qm_ldb_pxlink_set_sync_ctrl(imx8qm_ldb, imx8qm_ldb->id, true); +} + +static void +imx8qm_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *connector_state) +{ + struct imx8qm_ldb_channel *imx8qm_ldb_ch = + enc_to_imx8qm_ldb_ch(encoder); + struct imx8qm_ldb *imx8qm_ldb = imx8qm_ldb_ch->imx8qm_ldb; + struct ldb_channel *ldb_ch = &imx8qm_ldb_ch->base; + struct ldb *ldb = &imx8qm_ldb->base; + struct drm_display_mode *mode = &crtc_state->adjusted_mode; + unsigned long di_clk = mode->clock * 1000; + + if (mode->clock > 300000) { + dev_warn(ldb->dev, + "%s: mode exceeds 300 MHz pixel clock\n", __func__); + } + if (mode->clock > 150000 && !ldb->dual) { + dev_warn(ldb->dev, + "%s: mode exceeds 150 MHz pixel clock\n", __func__); + } + + clk_set_rate(imx8qm_ldb->clk_bypass, di_clk); + clk_set_rate(imx8qm_ldb->clk_pixel, di_clk); + + if (ldb->dual) { + mixel_phy_lvds_set_phy_speed(imx8qm_ldb->channel[0].phy, + di_clk / 2); + mixel_phy_lvds_set_phy_speed(imx8qm_ldb->channel[1].phy, + di_clk / 2); + } else { + mixel_phy_lvds_set_phy_speed(imx8qm_ldb_ch->phy, di_clk); + } + + if (ldb->dual) { + /* VSYNC */ + if (mode->flags & DRM_MODE_FLAG_NVSYNC) { + mixel_phy_lvds_set_vsync_pol(imx8qm_ldb->channel[0].phy, + false); + mixel_phy_lvds_set_vsync_pol(imx8qm_ldb->channel[1].phy, + false); + } else if (mode->flags & DRM_MODE_FLAG_PVSYNC) { + mixel_phy_lvds_set_vsync_pol(imx8qm_ldb->channel[0].phy, + true); + mixel_phy_lvds_set_vsync_pol(imx8qm_ldb->channel[1].phy, + true); + } + /* HSYNC */ + if (mode->flags & DRM_MODE_FLAG_NHSYNC) { + mixel_phy_lvds_set_hsync_pol(imx8qm_ldb->channel[0].phy, + false); + mixel_phy_lvds_set_hsync_pol(imx8qm_ldb->channel[1].phy, + false); + } else if (mode->flags & DRM_MODE_FLAG_PHSYNC) { + mixel_phy_lvds_set_hsync_pol(imx8qm_ldb->channel[0].phy, + true); + mixel_phy_lvds_set_hsync_pol(imx8qm_ldb->channel[1].phy, + true); + } + } else { + /* VSYNC */ + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + mixel_phy_lvds_set_vsync_pol(imx8qm_ldb_ch->phy, false); + else if (mode->flags & DRM_MODE_FLAG_PVSYNC) + mixel_phy_lvds_set_vsync_pol(imx8qm_ldb_ch->phy, true); + /* HSYNC */ + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + mixel_phy_lvds_set_hsync_pol(imx8qm_ldb_ch->phy, false); + else if (mode->flags & DRM_MODE_FLAG_PHSYNC) + mixel_phy_lvds_set_hsync_pol(imx8qm_ldb_ch->phy, true); + } + + if (!ldb_ch->bus_format) { + struct drm_connector *connector = connector_state->connector; + struct drm_display_info *di = &connector->display_info; + + if (di->num_bus_formats) + ldb_ch->bus_format = di->bus_formats[0]; + } + imx8qm_ldb_ch_set_bus_format(imx8qm_ldb_ch, ldb_ch->bus_format); +} + +static void imx8qm_ldb_encoder_disable(struct drm_encoder *encoder) +{ + struct imx8qm_ldb_channel *imx8qm_ldb_ch = + enc_to_imx8qm_ldb_ch(encoder); + struct imx8qm_ldb *imx8qm_ldb = imx8qm_ldb_ch->imx8qm_ldb; + struct ldb *ldb = &imx8qm_ldb->base; + + imx8qm_ldb_pxlink_set_mst_valid(imx8qm_ldb, imx8qm_ldb->id, false); + imx8qm_ldb_pxlink_set_sync_ctrl(imx8qm_ldb, imx8qm_ldb->id, false); + + if (ldb->dual) { + phy_power_off(imx8qm_ldb->channel[0].phy); + phy_power_off(imx8qm_ldb->channel[1].phy); + + imx8qm_ldb->channel[0].phy_is_on = false; + imx8qm_ldb->channel[1].phy_is_on = false; + } else { + phy_power_off(imx8qm_ldb_ch->phy); + + imx8qm_ldb_ch->phy_is_on = false; + } + + clk_disable_unprepare(imx8qm_ldb->clk_bypass); + clk_disable_unprepare(imx8qm_ldb->clk_pixel); +} + +static int +imx8qm_ldb_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); + struct imx8qm_ldb_channel *imx8qm_ldb_ch = + enc_to_imx8qm_ldb_ch(encoder); + struct ldb_channel *ldb_ch = &imx8qm_ldb_ch->base; + struct drm_display_info *di = &conn_state->connector->display_info; + u32 bus_format = ldb_ch->bus_format; + + /* Bus format description in DT overrides connector display info. */ + if (!bus_format && di->num_bus_formats) { + bus_format = di->bus_formats[0]; + imx_crtc_state->bus_flags = di->bus_flags; + } else { + bus_format = ldb_ch->bus_format; + imx_crtc_state->bus_flags = imx8qm_ldb_ch->bus_flags; + } + switch (bus_format) { + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB666_1X30_PADLO; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X30_PADLO; + break; + case MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG: + case MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA: + imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB101010_1X30; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct drm_connector_funcs imx8qm_ldb_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = imx_drm_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_connector_helper_funcs +imx8qm_ldb_connector_helper_funcs = { + .best_encoder = imx8qm_ldb_connector_best_encoder, +}; + +static const struct drm_encoder_helper_funcs imx8qm_ldb_encoder_helper_funcs = { + .atomic_mode_set = imx8qm_ldb_encoder_atomic_mode_set, + .enable = imx8qm_ldb_encoder_enable, + .disable = imx8qm_ldb_encoder_disable, + .atomic_check = imx8qm_ldb_encoder_atomic_check, +}; + +static const struct of_device_id imx8qm_ldb_dt_ids[] = { + { .compatible = "fsl,imx8qm-ldb", }, + { } +}; +MODULE_DEVICE_TABLE(of, imx8qm_ldb_dt_ids); + +static int +imx8qm_ldb_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm = data; + struct device_node *np = dev->of_node; + struct device_node *child; + struct imx8qm_ldb *imx8qm_ldb = dev_get_drvdata(dev); + struct ldb *ldb; + struct ldb_channel *ldb_ch; + struct drm_encoder *encoder[LDB_CH_NUM]; + int ret; + int i; + + ldb = &imx8qm_ldb->base; + ldb->dev = dev; + ldb->ctrl_reg = 0xe0; + ldb->output_port = 1; + + for (i = 0; i < LDB_CH_NUM; i++) { + imx8qm_ldb->channel[i].imx8qm_ldb = imx8qm_ldb; + ldb->channel[i] = &imx8qm_ldb->channel[i].base; + } + + ret = imx_scu_get_handle(&imx8qm_ldb->handle); + if (ret) { + dev_err(dev, "failed to get scu ipc handle %d\n", ret); + return ret; + } + + imx8qm_ldb->id = of_alias_get_id(np, "ldb"); + + imx8qm_ldb->clk_pixel = devm_clk_get(dev, "pixel"); + if (IS_ERR(imx8qm_ldb->clk_pixel)) + return PTR_ERR(imx8qm_ldb->clk_pixel); + + imx8qm_ldb->clk_bypass = devm_clk_get(dev, "bypass"); + if (IS_ERR(imx8qm_ldb->clk_bypass)) + return PTR_ERR(imx8qm_ldb->clk_bypass); + + for_each_child_of_node(np, child) { + ret = of_property_read_u32(child, "reg", &i); + if (ret || i < 0 || i > 1) + return -EINVAL; + + if (!of_device_is_available(child)) + continue; + + encoder[i] = &imx8qm_ldb->channel[i].encoder; + + drm_encoder_helper_add(encoder[i], + &imx8qm_ldb_encoder_helper_funcs); + drm_simple_encoder_init(drm, encoder[i], DRM_MODE_ENCODER_LVDS); + } + + pm_runtime_enable(dev); + + ret = ldb_bind(ldb, encoder); + if (ret) + goto disable_pm_runtime; + + for_each_child_of_node(np, child) { + struct imx8qm_ldb_channel *imx8qm_ldb_ch; + bool auxiliary_ch = false; + + ret = of_property_read_u32(child, "reg", &i); + if (ret || i < 0 || i > 1) { + ret = -EINVAL; + goto free_child; + } + + if (ldb->dual && i > 0) { + auxiliary_ch = true; + imx8qm_ldb_ch = &imx8qm_ldb->channel[i]; + goto get_phy; + } + + if (!of_device_is_available(child)) + continue; + + imx8qm_ldb_ch = &imx8qm_ldb->channel[i]; +get_phy: + imx8qm_ldb_ch->phy = devm_of_phy_get(dev, child, "ldb_phy"); + if (IS_ERR(imx8qm_ldb_ch->phy)) { + ret = PTR_ERR(imx8qm_ldb_ch->phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "can't get channel%d phy: %d\n", + i, ret); + goto free_child; + } + + ret = phy_init(imx8qm_ldb_ch->phy); + if (ret < 0) { + dev_err(dev, "failed to initialize channel%d phy: %d\n", + i, ret); + goto free_child; + } + + if (auxiliary_ch) + continue; + } + + for (i = 0; i < LDB_CH_NUM; i++) { + ldb_ch = &imx8qm_ldb->channel[i].base; + + if (!ldb_ch->is_valid) + continue; + + ret = imx_drm_encoder_parse_of(drm, encoder[i], ldb_ch->child); + if (ret) + goto disable_pm_runtime; + } + + return 0; + +free_child: + of_node_put(child); +disable_pm_runtime: + pm_runtime_disable(dev); + + return ret; +} + +static void imx8qm_ldb_unbind(struct device *dev, struct device *master, + void *data) +{ + struct imx8qm_ldb *imx8qm_ldb = dev_get_drvdata(dev); + int i; + + for (i = 0; i < LDB_CH_NUM; i++) { + struct imx8qm_ldb_channel *imx8qm_ldb_ch = + &imx8qm_ldb->channel[i]; + + if (imx8qm_ldb_ch->phy_is_on) + phy_power_off(imx8qm_ldb_ch->phy); + + phy_exit(imx8qm_ldb_ch->phy); + } + + pm_runtime_disable(dev); +} + +static const struct component_ops imx8qm_ldb_ops = { + .bind = imx8qm_ldb_bind, + .unbind = imx8qm_ldb_unbind, +}; + +static int imx8qm_ldb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx8qm_ldb *imx8qm_ldb; + + imx8qm_ldb = devm_kzalloc(dev, sizeof(*imx8qm_ldb), GFP_KERNEL); + if (!imx8qm_ldb) + return -ENOMEM; + + dev_set_drvdata(dev, imx8qm_ldb); + + return component_add(dev, &imx8qm_ldb_ops); +} + +static int imx8qm_ldb_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &imx8qm_ldb_ops); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int imx8qm_ldb_suspend(struct device *dev) +{ + struct imx8qm_ldb *imx8qm_ldb = dev_get_drvdata(dev); + int i; + + if (imx8qm_ldb == NULL) + return 0; + + for (i = 0; i < LDB_CH_NUM; i++) + phy_exit(imx8qm_ldb->channel[i].phy); + + return 0; +} + +static int imx8qm_ldb_resume(struct device *dev) +{ + struct imx8qm_ldb *imx8qm_ldb = dev_get_drvdata(dev); + int i; + + if (imx8qm_ldb == NULL) + return 0; + + for (i = 0; i < LDB_CH_NUM; i++) + phy_init(imx8qm_ldb->channel[i].phy); + + return 0; +} +#endif + +static const struct dev_pm_ops imx8qm_ldb_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(imx8qm_ldb_suspend, imx8qm_ldb_resume) +}; + +static struct platform_driver imx8qm_ldb_driver = { + .probe = imx8qm_ldb_probe, + .remove = imx8qm_ldb_remove, + .driver = { + .of_match_table = imx8qm_ldb_dt_ids, + .name = DRIVER_NAME, + .pm = &imx8qm_ldb_pm_ops, + }, +}; + +module_platform_driver(imx8qm_ldb_driver); + +MODULE_DESCRIPTION("i.MX8QM LVDS driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/imx/imx8qxp-ldb.c b/drivers/gpu/drm/imx/imx8qxp-ldb.c new file mode 100644 index 000000000000..b48c4dbb923d --- /dev/null +++ b/drivers/gpu/drm/imx/imx8qxp-ldb.c @@ -0,0 +1,882 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2020,2022 NXP + */ + +#include <dt-bindings/firmware/imx/rsrc.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/firmware/imx/sci.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/phy/phy-mixel-lvds-combo.h> +#include <linux/pm_domain.h> +#include <linux/regmap.h> + +#include <drm/bridge/fsl_imx_ldb.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> + +#include "imx-drm.h" + +#define DRIVER_NAME "imx8qxp-ldb" + +#define LDB_CH0_MODE_EN_TO_DI0 (1 << 0) +#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0) +#define LDB_CH0_MODE_EN_MASK (3 << 0) +#define LDB_CH1_MODE_EN_TO_DI0 (1 << 2) +#define LDB_CH1_MODE_EN_TO_DI1 (3 << 2) +#define LDB_CH1_MODE_EN_MASK (3 << 2) +#define LDB_BIT_MAP_CH0_JEIDA (1 << 6) +#define LDB_BIT_MAP_CH1_JEIDA (1 << 8) +#define LDB_DI0_VS_POL_ACT_LOW (1 << 9) +#define LDB_DI1_VS_POL_ACT_LOW (1 << 10) +#define LDB_CH0_10BIT_EN (1 << 22) +#define LDB_CH1_10BIT_EN (1 << 23) +#define LDB_CH0_DATA_WIDTH_24BIT (1 << 24) +#define LDB_CH1_DATA_WIDTH_24BIT (1 << 26) +#define LDB_CH0_DATA_WIDTH_30BIT (2 << 24) +#define LDB_CH1_DATA_WIDTH_30BIT (2 << 26) +#define LDB_CH_SEL (1 << 28) + +struct imx8qxp_ldb; + +struct imx8qxp_ldb_channel { + struct ldb_channel base; + struct imx8qxp_ldb *imx8qxp_ldb; + + struct drm_connector connector; + struct drm_encoder encoder; + + struct phy *phy; + struct phy *aux_phy; + bool phy_is_on; + + u32 bus_flags; +}; + +static inline struct imx8qxp_ldb_channel * +con_to_imx8qxp_ldb_ch(struct drm_connector *c) +{ + return container_of(c, struct imx8qxp_ldb_channel, connector); +} + +static inline struct imx8qxp_ldb_channel * +enc_to_imx8qxp_ldb_ch(struct drm_encoder *e) +{ + return container_of(e, struct imx8qxp_ldb_channel, encoder); +} + +struct imx8qxp_ldb { + struct ldb base; + struct regmap *aux_regmap; + struct imx8qxp_ldb_channel channel[LDB_CH_NUM]; + struct clk *clk_pixel; + struct clk *clk_bypass; + struct clk *clk_aux_pixel; + struct clk *clk_aux_bypass; + struct imx_sc_ipc *handle; + + struct device *pd_main_dev; + struct device *pd_aux_dev; + struct device_link *pd_main_link; + struct device_link *pd_aux_link; + + int id; +}; + +static void +imx8qxp_ldb_ch_set_bus_format(struct imx8qxp_ldb_channel *imx8qxp_ldb_ch, + u32 bus_format) +{ + struct imx8qxp_ldb *imx8qxp_ldb = imx8qxp_ldb_ch->imx8qxp_ldb; + struct ldb *ldb = &imx8qxp_ldb->base; + struct ldb_channel *ldb_ch = &imx8qxp_ldb_ch->base; + + switch (bus_format) { + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + if (ldb_ch->chno == 0 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH0_DATA_WIDTH_24BIT; + if (ldb_ch->chno == 1 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH1_DATA_WIDTH_24BIT; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + if (ldb_ch->chno == 0 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH0_DATA_WIDTH_24BIT; + if (ldb_ch->chno == 1 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH1_DATA_WIDTH_24BIT; + break; + case MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG: + if (ldb_ch->chno == 0 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH0_10BIT_EN | + LDB_CH0_DATA_WIDTH_30BIT; + if (ldb_ch->chno == 1 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH1_10BIT_EN | + LDB_CH1_DATA_WIDTH_30BIT; + break; + case MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA: + if (ldb_ch->chno == 0 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH0_10BIT_EN | + LDB_CH0_DATA_WIDTH_30BIT | + LDB_BIT_MAP_CH0_JEIDA; + if (ldb_ch->chno == 1 || ldb->dual) + ldb->ldb_ctrl |= LDB_CH1_10BIT_EN | + LDB_CH1_DATA_WIDTH_30BIT | + LDB_BIT_MAP_CH1_JEIDA; + break; + } +} + +static struct drm_encoder *imx8qxp_ldb_connector_best_encoder( + struct drm_connector *connector) +{ + struct imx8qxp_ldb_channel *imx8qxp_ldb_ch = + con_to_imx8qxp_ldb_ch(connector); + + return &imx8qxp_ldb_ch->encoder; +} + +static void imx8qxp_ldb_pxlink_enable(struct imx8qxp_ldb *imx8qxp_ldb, + int stream_id, bool enable) +{ + u8 ctrl = stream_id ? + IMX_SC_C_PXL_LINK_MST2_ENB : IMX_SC_C_PXL_LINK_MST1_ENB; + + imx_sc_misc_set_control(imx8qxp_ldb->handle, + IMX_SC_R_DC_0, ctrl, enable); +} + +static void imx8qxp_ldb_pxlink_set_mst_valid(struct imx8qxp_ldb *imx8qxp_ldb, + int stream_id, bool enable) +{ + u8 ctrl = stream_id ? + IMX_SC_C_PXL_LINK_MST2_VLD : IMX_SC_C_PXL_LINK_MST1_VLD; + + imx_sc_misc_set_control(imx8qxp_ldb->handle, + IMX_SC_R_DC_0, ctrl, enable); +} + +static void imx8qxp_ldb_pxlink_set_sync_ctrl(struct imx8qxp_ldb *imx8qxp_ldb, + int stream_id, bool enable) +{ + u8 ctrl = stream_id ? IMX_SC_C_SYNC_CTRL1 : IMX_SC_C_SYNC_CTRL0; + + imx_sc_misc_set_control(imx8qxp_ldb->handle, + IMX_SC_R_DC_0, ctrl, enable); +} + +static void imx8qxp_ldb_encoder_enable(struct drm_encoder *encoder) +{ + struct imx8qxp_ldb_channel *imx8qxp_ldb_ch = + enc_to_imx8qxp_ldb_ch(encoder); + struct imx8qxp_ldb *imx8qxp_ldb = imx8qxp_ldb_ch->imx8qxp_ldb; + struct ldb *ldb = &imx8qxp_ldb->base; + + clk_prepare_enable(imx8qxp_ldb->clk_pixel); + clk_prepare_enable(imx8qxp_ldb->clk_bypass); + + if (ldb->dual) { + clk_prepare_enable(imx8qxp_ldb->clk_aux_pixel); + clk_prepare_enable(imx8qxp_ldb->clk_aux_bypass); + } + + /* + * LDB frontend doesn't know if the auxiliary LDB is used or not. + * Enable pixel link after dual or single LDB clocks are enabled + * so that the dual LDBs are synchronized. + */ + imx8qxp_ldb_pxlink_enable(imx8qxp_ldb, imx8qxp_ldb->id, true); + + if (imx8qxp_ldb_ch == &imx8qxp_ldb->channel[0] || ldb->dual) { + ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; + ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0; + } + if (imx8qxp_ldb_ch == &imx8qxp_ldb->channel[1] || ldb->dual) { + ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; + ldb->ldb_ctrl |= ldb->dual ? + LDB_CH1_MODE_EN_TO_DI0 : LDB_CH1_MODE_EN_TO_DI1; + } + + pm_runtime_get_sync(ldb->dev); + + regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl); + if (ldb->dual) + regmap_write(imx8qxp_ldb->aux_regmap, ldb->ctrl_reg, + ldb->ldb_ctrl | LDB_CH_SEL); + + if (ldb->dual) { + phy_power_on(imx8qxp_ldb->channel[0].phy); + phy_power_on(imx8qxp_ldb->channel[0].aux_phy); + + imx8qxp_ldb->channel[0].phy_is_on = true; + } else { + phy_power_on(imx8qxp_ldb_ch->phy); + + imx8qxp_ldb_ch->phy_is_on = true; + } + + imx8qxp_ldb_pxlink_set_mst_valid(imx8qxp_ldb, imx8qxp_ldb->id, true); + imx8qxp_ldb_pxlink_set_sync_ctrl(imx8qxp_ldb, imx8qxp_ldb->id, true); +} + +static void +imx8qxp_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *connector_state) +{ + struct imx8qxp_ldb_channel *imx8qxp_ldb_ch = + enc_to_imx8qxp_ldb_ch(encoder); + struct imx8qxp_ldb *imx8qxp_ldb = imx8qxp_ldb_ch->imx8qxp_ldb; + struct ldb_channel *ldb_ch = &imx8qxp_ldb_ch->base; + struct ldb *ldb = &imx8qxp_ldb->base; + struct drm_display_mode *mode = &crtc_state->adjusted_mode; + unsigned long di_clk = mode->clock * 1000; + + if (mode->clock > 300000) { + dev_warn(ldb->dev, + "%s: mode exceeds 300 MHz pixel clock\n", __func__); + } + if (mode->clock > 150000 && !ldb->dual) { + dev_warn(ldb->dev, + "%s: mode exceeds 150 MHz pixel clock\n", __func__); + } + + clk_set_rate(imx8qxp_ldb->clk_bypass, di_clk); + clk_set_rate(imx8qxp_ldb->clk_pixel, di_clk); + + if (ldb->dual) { + clk_set_rate(imx8qxp_ldb->clk_aux_bypass, di_clk); + clk_set_rate(imx8qxp_ldb->clk_aux_pixel, di_clk); + } + + if (ldb->dual) { + mixel_phy_combo_lvds_set_phy_speed(imx8qxp_ldb->channel[0].phy, + di_clk / 2); + mixel_phy_combo_lvds_set_phy_speed(imx8qxp_ldb->channel[0].aux_phy, + di_clk / 2); + } else { + mixel_phy_combo_lvds_set_phy_speed(imx8qxp_ldb_ch->phy, di_clk); + } + + if (imx8qxp_ldb_ch == &imx8qxp_ldb->channel[0]) + ldb->ldb_ctrl &= ~LDB_CH_SEL; + if (imx8qxp_ldb_ch == &imx8qxp_ldb->channel[1]) + ldb->ldb_ctrl |= LDB_CH_SEL; + + /* FIXME - assumes straight connections DI0 --> CH0, DI1 --> CH1 */ + if (imx8qxp_ldb_ch == &imx8qxp_ldb->channel[0] || ldb->dual) { + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + ldb->ldb_ctrl |= LDB_DI0_VS_POL_ACT_LOW; + else if (mode->flags & DRM_MODE_FLAG_PVSYNC) + ldb->ldb_ctrl &= ~LDB_DI0_VS_POL_ACT_LOW; + } + if (imx8qxp_ldb_ch == &imx8qxp_ldb->channel[1] || ldb->dual) { + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + ldb->ldb_ctrl |= LDB_DI1_VS_POL_ACT_LOW; + else if (mode->flags & DRM_MODE_FLAG_PVSYNC) + ldb->ldb_ctrl &= ~LDB_DI1_VS_POL_ACT_LOW; + } + + pm_runtime_get_sync(ldb->dev); + + /* settle vsync polarity and channel selection down early */ + if (ldb->dual) { + regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl); + regmap_write(imx8qxp_ldb->aux_regmap, ldb->ctrl_reg, + ldb->ldb_ctrl | LDB_CH_SEL); + } + + pm_runtime_put(ldb->dev); + + if (ldb->dual) { + /* VSYNC */ + if (mode->flags & DRM_MODE_FLAG_NVSYNC) { + mixel_phy_combo_lvds_set_vsync_pol( + imx8qxp_ldb->channel[0].phy, false); + mixel_phy_combo_lvds_set_vsync_pol( + imx8qxp_ldb->channel[0].aux_phy, false); + } else { + mixel_phy_combo_lvds_set_vsync_pol( + imx8qxp_ldb->channel[0].phy, true); + mixel_phy_combo_lvds_set_vsync_pol( + imx8qxp_ldb->channel[0].aux_phy, true); + } + /* HSYNC */ + if (mode->flags & DRM_MODE_FLAG_NHSYNC) { + mixel_phy_combo_lvds_set_hsync_pol( + imx8qxp_ldb->channel[0].phy, false); + mixel_phy_combo_lvds_set_hsync_pol( + imx8qxp_ldb->channel[0].aux_phy, false); + } else { + mixel_phy_combo_lvds_set_hsync_pol( + imx8qxp_ldb->channel[0].phy, true); + mixel_phy_combo_lvds_set_hsync_pol( + imx8qxp_ldb->channel[0].aux_phy, true); + } + } else { + /* VSYNC */ + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + mixel_phy_combo_lvds_set_vsync_pol(imx8qxp_ldb_ch->phy, + false); + else if (mode->flags & DRM_MODE_FLAG_PVSYNC) + mixel_phy_combo_lvds_set_vsync_pol(imx8qxp_ldb_ch->phy, + true); + /* HSYNC */ + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + mixel_phy_combo_lvds_set_hsync_pol(imx8qxp_ldb_ch->phy, + false); + else if (mode->flags & DRM_MODE_FLAG_PHSYNC) + mixel_phy_combo_lvds_set_hsync_pol(imx8qxp_ldb_ch->phy, + true); + } + + if (!ldb_ch->bus_format) { + struct drm_connector *connector = connector_state->connector; + struct drm_display_info *di = &connector->display_info; + + if (di->num_bus_formats) + ldb_ch->bus_format = di->bus_formats[0]; + } + imx8qxp_ldb_ch_set_bus_format(imx8qxp_ldb_ch, ldb_ch->bus_format); +} + +static void imx8qxp_ldb_encoder_disable(struct drm_encoder *encoder) +{ + struct imx8qxp_ldb_channel *imx8qxp_ldb_ch = + enc_to_imx8qxp_ldb_ch(encoder); + struct imx8qxp_ldb *imx8qxp_ldb = imx8qxp_ldb_ch->imx8qxp_ldb; + struct ldb *ldb = &imx8qxp_ldb->base; + + imx8qxp_ldb_pxlink_set_mst_valid(imx8qxp_ldb, imx8qxp_ldb->id, false); + imx8qxp_ldb_pxlink_set_sync_ctrl(imx8qxp_ldb, imx8qxp_ldb->id, false); + + if (ldb->dual) { + phy_power_off(imx8qxp_ldb->channel[0].phy); + phy_power_off(imx8qxp_ldb->channel[0].aux_phy); + + imx8qxp_ldb->channel[0].phy_is_on = false; + } else { + phy_power_off(imx8qxp_ldb_ch->phy); + + imx8qxp_ldb_ch->phy_is_on = false; + } + + if (ldb->dual) + regmap_write(imx8qxp_ldb->aux_regmap, + ldb->ctrl_reg, ldb->ldb_ctrl); + + pm_runtime_put(ldb->dev); + + clk_disable_unprepare(imx8qxp_ldb->clk_bypass); + clk_disable_unprepare(imx8qxp_ldb->clk_pixel); + + if (ldb->dual) { + clk_disable_unprepare(imx8qxp_ldb->clk_aux_bypass); + clk_disable_unprepare(imx8qxp_ldb->clk_aux_pixel); + } + + imx8qxp_ldb_pxlink_enable(imx8qxp_ldb, imx8qxp_ldb->id, false); +} + +static int +imx8qxp_ldb_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); + struct imx8qxp_ldb_channel *imx8qxp_ldb_ch = + enc_to_imx8qxp_ldb_ch(encoder); + struct ldb_channel *ldb_ch = &imx8qxp_ldb_ch->base; + struct drm_display_info *di = &conn_state->connector->display_info; + u32 bus_format = ldb_ch->bus_format; + + /* Bus format description in DT overrides connector display info. */ + if (!bus_format && di->num_bus_formats) { + bus_format = di->bus_formats[0]; + imx_crtc_state->bus_flags = di->bus_flags; + } else { + bus_format = ldb_ch->bus_format; + imx_crtc_state->bus_flags = imx8qxp_ldb_ch->bus_flags; + } + switch (bus_format) { + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB666_1X30_PADLO; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X30_PADLO; + break; + case MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG: + case MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA: + imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB101010_1X30; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct drm_connector_funcs imx8qxp_ldb_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = imx_drm_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_connector_helper_funcs +imx8qxp_ldb_connector_helper_funcs = { + .best_encoder = imx8qxp_ldb_connector_best_encoder, +}; + +static const struct drm_encoder_helper_funcs +imx8qxp_ldb_encoder_helper_funcs = { + .atomic_mode_set = imx8qxp_ldb_encoder_atomic_mode_set, + .enable = imx8qxp_ldb_encoder_enable, + .disable = imx8qxp_ldb_encoder_disable, + .atomic_check = imx8qxp_ldb_encoder_atomic_check, +}; + +static const struct of_device_id imx8qxp_ldb_dt_ids[] = { + { .compatible = "fsl,imx8qxp-ldb", }, + { } +}; +MODULE_DEVICE_TABLE(of, imx8qxp_ldb_dt_ids); + +static void imx8qxp_ldb_detach_pm_domains(struct imx8qxp_ldb *imx8qxp_ldb) +{ + if (imx8qxp_ldb->pd_aux_link && !IS_ERR(imx8qxp_ldb->pd_aux_link)) + device_link_del(imx8qxp_ldb->pd_aux_link); + if (imx8qxp_ldb->pd_aux_dev && !IS_ERR(imx8qxp_ldb->pd_aux_dev)) + dev_pm_domain_detach(imx8qxp_ldb->pd_aux_dev, true); + + if (imx8qxp_ldb->pd_main_link && !IS_ERR(imx8qxp_ldb->pd_main_link)) + device_link_del(imx8qxp_ldb->pd_main_link); + if (imx8qxp_ldb->pd_main_dev && !IS_ERR(imx8qxp_ldb->pd_main_dev)) + dev_pm_domain_detach(imx8qxp_ldb->pd_main_dev, true); + + imx8qxp_ldb->pd_aux_dev = NULL; + imx8qxp_ldb->pd_aux_link = NULL; + imx8qxp_ldb->pd_main_dev = NULL; + imx8qxp_ldb->pd_main_link = NULL; +} + +static int +imx8qxp_ldb_attach_pm_domains(struct imx8qxp_ldb *imx8qxp_ldb, bool dual) +{ + struct ldb *ldb = &imx8qxp_ldb->base; + struct device *dev = ldb->dev; + u32 flags = DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE; + int ret = 0; + + imx8qxp_ldb->pd_main_dev = dev_pm_domain_attach_by_name(dev, "main"); + if (IS_ERR(imx8qxp_ldb->pd_main_dev)) { + ret = PTR_ERR(imx8qxp_ldb->pd_main_dev); + dev_err(dev, "Failed to attach main pd dev: %d\n", ret); + goto fail; + } + imx8qxp_ldb->pd_main_link = device_link_add(dev, + imx8qxp_ldb->pd_main_dev, flags); + if (IS_ERR(imx8qxp_ldb->pd_main_link)) { + ret = PTR_ERR(imx8qxp_ldb->pd_main_link); + dev_err(dev, "Failed to add device link to main pd dev: %d\n", + ret); + goto fail; + } + + if (!dual) + goto out; + + imx8qxp_ldb->pd_aux_dev = dev_pm_domain_attach_by_name(dev, "aux"); + if (IS_ERR(imx8qxp_ldb->pd_aux_dev)) { + ret = PTR_ERR(imx8qxp_ldb->pd_aux_dev); + dev_err(dev, "Failed to attach aux pd dev: %d\n", ret); + goto fail; + } + imx8qxp_ldb->pd_aux_link = device_link_add(dev, + imx8qxp_ldb->pd_aux_dev, flags); + if (IS_ERR(imx8qxp_ldb->pd_aux_link)) { + ret = PTR_ERR(imx8qxp_ldb->pd_aux_link); + dev_err(dev, "Failed to add device link to aux pd dev: %d\n", + ret); + goto fail; + } + +out: + return ret; +fail: + imx8qxp_ldb_detach_pm_domains(imx8qxp_ldb); + return ret; +} + +static int imx8qxp_ldb_init_sc_misc(int ldb_id, bool dual) +{ + struct imx_sc_ipc *handle; + u32 rsc; + bool is_aux = false; + int ret = 0; + + imx_scu_get_handle(&handle); + +again: + rsc = ldb_id ? IMX_SC_R_MIPI_1 : IMX_SC_R_MIPI_0; + + ret |= imx_sc_misc_set_control(handle, + rsc, IMX_SC_C_MODE, 1); + ret |= imx_sc_misc_set_control(handle, + rsc, IMX_SC_C_DUAL_MODE, is_aux); + ret |= imx_sc_misc_set_control(handle, + rsc, IMX_SC_C_PXL_LINK_SEL, is_aux); + + if (dual && !is_aux) { + ldb_id ^= 1; + is_aux = true; + goto again; + } + + return ret; +} + +static struct phy *imx8qxp_ldb_get_aux_phy(struct device_node *auxldb_np) +{ + struct device_node *child; + struct phy *phy = NULL; + int ret, i; + + for_each_child_of_node(auxldb_np, child) { + ret = of_property_read_u32(child, "reg", &i); + if (ret || i < 0) { + of_node_put(child); + return ERR_PTR(-ENODEV); + } + + if (i != 1) + continue; + + phy = of_phy_get(child, "ldb_phy"); + } + + of_node_put(child); + + return phy; +} + +static int +imx8qxp_ldb_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm = data; + struct device_node *np = dev->of_node; + struct device_node *auxldb_np = NULL, *child; + struct imx8qxp_ldb *imx8qxp_ldb = dev_get_drvdata(dev); + struct ldb *ldb; + struct ldb_channel *ldb_ch; + struct drm_encoder *encoder[LDB_CH_NUM]; + bool dual; + int ret; + int i; + + ldb = &imx8qxp_ldb->base; + ldb->dev = dev; + ldb->ctrl_reg = 0xe0; + ldb->output_port = 1; + + for (i = 0; i < LDB_CH_NUM; i++) { + imx8qxp_ldb->channel[i].imx8qxp_ldb = imx8qxp_ldb; + ldb->channel[i] = &imx8qxp_ldb->channel[i].base; + } + + ret = imx_scu_get_handle(&imx8qxp_ldb->handle); + if (ret) { + dev_err(dev, "failed to get scu ipc handle %d\n", ret); + return ret; + } + + imx8qxp_ldb->id = of_alias_get_id(np, "ldb"); + + for_each_child_of_node(np, child) { + ret = of_property_read_u32(child, "reg", &i); + if (ret || i < 0 || i > 1) + return -EINVAL; + + if (!of_device_is_available(child)) + continue; + + encoder[i] = &imx8qxp_ldb->channel[i].encoder; + + drm_encoder_helper_add(encoder[i], + &imx8qxp_ldb_encoder_helper_funcs); + drm_simple_encoder_init(drm, encoder[i], DRM_MODE_ENCODER_LVDS); + } + + dual = of_property_read_bool(np, "fsl,dual-channel"); + + ret = imx8qxp_ldb_attach_pm_domains(imx8qxp_ldb, dual); + if (ret) { + dev_err(dev, "failed to attach pm domains %d\n", ret); + return ret; + } + + pm_runtime_enable(dev); + + ret = ldb_bind(ldb, encoder); + if (ret) + goto disable_pm_runtime; + + ret = imx8qxp_ldb_init_sc_misc(imx8qxp_ldb->id, ldb->dual); + if (ret) { + dev_err(dev, "failed to initialize sc misc %d\n", ret); + goto disable_pm_runtime; + } + + imx8qxp_ldb->clk_pixel = devm_clk_get(dev, "pixel"); + if (IS_ERR(imx8qxp_ldb->clk_pixel)) { + ret = PTR_ERR(imx8qxp_ldb->clk_pixel); + goto disable_pm_runtime; + } + + imx8qxp_ldb->clk_bypass = devm_clk_get(dev, "bypass"); + if (IS_ERR(imx8qxp_ldb->clk_bypass)) { + ret = PTR_ERR(imx8qxp_ldb->clk_bypass); + goto disable_pm_runtime; + } + + if (ldb->dual) { + imx8qxp_ldb->clk_aux_pixel = devm_clk_get(dev, "aux_pixel"); + if (IS_ERR(imx8qxp_ldb->clk_aux_pixel)) { + ret = PTR_ERR(imx8qxp_ldb->clk_aux_pixel); + goto disable_pm_runtime; + } + + imx8qxp_ldb->clk_aux_bypass = devm_clk_get(dev, "aux_bypass"); + if (IS_ERR(imx8qxp_ldb->clk_aux_bypass)) { + ret = PTR_ERR(imx8qxp_ldb->clk_aux_bypass); + goto disable_pm_runtime; + } + + auxldb_np = of_parse_phandle(np, "fsl,auxldb", 0); + if (!auxldb_np) { + dev_err(dev, + "failed to find aux LDB node in device tree\n"); + ret = -ENODEV; + goto disable_pm_runtime; + } + + if (of_device_is_available(auxldb_np)) { + dev_err(dev, "aux LDB node is already in use\n"); + of_node_put(auxldb_np); + ret = -ENODEV; + goto disable_pm_runtime; + } + + imx8qxp_ldb->aux_regmap = + syscon_regmap_lookup_by_phandle(auxldb_np, "gpr"); + if (IS_ERR(imx8qxp_ldb->aux_regmap)) { + ret = PTR_ERR(imx8qxp_ldb->aux_regmap); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get aux regmap\n"); + of_node_put(auxldb_np); + goto disable_pm_runtime; + } + + pm_runtime_get_sync(dev); + regmap_write(imx8qxp_ldb->aux_regmap, ldb->ctrl_reg, 0); + pm_runtime_put(dev); + } + + for_each_child_of_node(np, child) { + struct imx8qxp_ldb_channel *imx8qxp_ldb_ch; + + ret = of_property_read_u32(child, "reg", &i); + if (ret || i < 0 || i > 1) { + ret = -EINVAL; + goto free_child; + } + + if (!of_device_is_available(child)) + continue; + + imx8qxp_ldb_ch = &imx8qxp_ldb->channel[i]; + + imx8qxp_ldb_ch->phy = devm_of_phy_get(dev, child, "ldb_phy"); + if (IS_ERR(imx8qxp_ldb_ch->phy)) { + ret = PTR_ERR(imx8qxp_ldb_ch->phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "can't get channel%d phy: %d\n", + i, ret); + goto free_child; + } + + ret = phy_init(imx8qxp_ldb_ch->phy); + if (ret < 0) { + dev_err(dev, "failed to initialize channel%d phy: %d\n", + i, ret); + goto free_child; + } + + if (ldb->dual) { + imx8qxp_ldb_ch->aux_phy = + imx8qxp_ldb_get_aux_phy(auxldb_np); + if (IS_ERR(imx8qxp_ldb_ch->aux_phy)) { + ret = PTR_ERR(imx8qxp_ldb_ch->aux_phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, + "can't get channel0 aux phy: %d\n", + ret); + goto free_child; + } + + ret = phy_init(imx8qxp_ldb_ch->aux_phy); + if (ret < 0) { + dev_err(dev, + "failed to initialize channel0 aux phy: %d\n", + ret); + goto free_child; + } + } + } + + if (ldb->dual) + of_node_put(auxldb_np); + + for (i = 0; i < LDB_CH_NUM; i++) { + ldb_ch = &imx8qxp_ldb->channel[i].base; + + if (!ldb_ch->is_valid) + continue; + + ret = imx_drm_encoder_parse_of(drm, encoder[i], ldb_ch->child); + if (ret) + goto disable_pm_runtime; + } + + return 0; + +free_child: + of_node_put(child); + if (ldb->dual) + of_node_put(auxldb_np); +disable_pm_runtime: + pm_runtime_disable(dev); + imx8qxp_ldb_detach_pm_domains(imx8qxp_ldb); + + return ret; +} + +static void imx8qxp_ldb_unbind(struct device *dev, struct device *master, + void *data) +{ + struct imx8qxp_ldb *imx8qxp_ldb = dev_get_drvdata(dev); + struct ldb *ldb = &imx8qxp_ldb->base; + int i; + + for (i = 0; i < LDB_CH_NUM; i++) { + struct imx8qxp_ldb_channel *imx8qxp_ldb_ch = + &imx8qxp_ldb->channel[i]; + + if (imx8qxp_ldb_ch->phy_is_on) { + phy_power_off(imx8qxp_ldb_ch->phy); + if (ldb->dual) + phy_power_off(imx8qxp_ldb_ch->aux_phy); + } + + phy_exit(imx8qxp_ldb_ch->phy); + if (ldb->dual && i == 0) + phy_exit(imx8qxp_ldb_ch->aux_phy); + } + + imx8qxp_ldb_detach_pm_domains(imx8qxp_ldb); +} + +static const struct component_ops imx8qxp_ldb_ops = { + .bind = imx8qxp_ldb_bind, + .unbind = imx8qxp_ldb_unbind, +}; + +static int imx8qxp_ldb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx8qxp_ldb *imx8qxp_ldb; + + imx8qxp_ldb = devm_kzalloc(dev, sizeof(*imx8qxp_ldb), GFP_KERNEL); + if (!imx8qxp_ldb) + return -ENOMEM; + + dev_set_drvdata(dev, imx8qxp_ldb); + + return component_add(dev, &imx8qxp_ldb_ops); +} + +static int imx8qxp_ldb_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &imx8qxp_ldb_ops); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int imx8qxp_ldb_suspend(struct device *dev) +{ + struct imx8qxp_ldb *imx8qxp_ldb = dev_get_drvdata(dev); + struct ldb *ldb = &imx8qxp_ldb->base; + int i; + + if (imx8qxp_ldb == NULL) + return 0; + + for (i = 0; i < LDB_CH_NUM; i++) { + phy_exit(imx8qxp_ldb->channel[i].phy); + + if (ldb->dual && i == 0) + phy_exit(imx8qxp_ldb->channel[i].aux_phy); + } + + return 0; +} + +static int imx8qxp_ldb_resume(struct device *dev) +{ + struct imx8qxp_ldb *imx8qxp_ldb = dev_get_drvdata(dev); + struct ldb *ldb = &imx8qxp_ldb->base; + int i; + + if (imx8qxp_ldb == NULL) + return 0; + + for (i = 0; i < LDB_CH_NUM; i++) { + phy_init(imx8qxp_ldb->channel[i].phy); + + if (ldb->dual && i == 0) + phy_init(imx8qxp_ldb->channel[i].aux_phy); + } + + imx8qxp_ldb_init_sc_misc(imx8qxp_ldb->id, ldb->dual); + + return 0; +} +#endif + +static const struct dev_pm_ops imx8qxp_ldb_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(imx8qxp_ldb_suspend, imx8qxp_ldb_resume) +}; + +static struct platform_driver imx8qxp_ldb_driver = { + .probe = imx8qxp_ldb_probe, + .remove = imx8qxp_ldb_remove, + .driver = { + .of_match_table = imx8qxp_ldb_dt_ids, + .name = DRIVER_NAME, + .pm = &imx8qxp_ldb_pm_ops, + }, +}; + +module_platform_driver(imx8qxp_ldb_driver); + +MODULE_DESCRIPTION("i.MX8QXP LVDS driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/imx/imx93-ldb.c b/drivers/gpu/drm/imx/imx93-ldb.c new file mode 100644 index 000000000000..fc02a1970874 --- /dev/null +++ b/drivers/gpu/drm/imx/imx93-ldb.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 NXP + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> + +#include <drm/bridge/fsl_imx_ldb.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> + +#include "imx-drm.h" + +#define DRIVER_NAME "imx93-ldb" + +#define LDB_CH0_MODE_EN_TO_DI0 (1 << 0) +#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0) +#define LDB_CH0_MODE_EN_MASK (3 << 0) +#define LDB_REG_CH0_FIFO_RESET (1 << 11) +#define LDB_REG_ASYNC_FIFO_EN (1 << 24) +#define LDB_FIFO_THRESHOLD (4 << 25) + +struct imx93_ldb; + +struct imx93_ldb_channel { + struct ldb_channel base; + struct imx93_ldb *imx93_ldb; + + struct drm_connector connector; + struct drm_encoder encoder; + + struct phy *phy; + bool phy_is_on; + + u32 bus_flags; +}; + +static inline struct imx93_ldb_channel * +con_to_imx93_ldb_ch(struct drm_connector *c) +{ + return container_of(c, struct imx93_ldb_channel, connector); +} + +static inline struct imx93_ldb_channel * +enc_to_imx93_ldb_ch(struct drm_encoder *e) +{ + return container_of(e, struct imx93_ldb_channel, encoder); +} + +struct imx93_ldb { + struct ldb base; + struct imx93_ldb_channel channel; + struct clk *clk_root; +}; + +static struct drm_encoder * +imx93_ldb_connector_best_encoder(struct drm_connector *connector) +{ + struct imx93_ldb_channel *imx93_ldb_ch = con_to_imx93_ldb_ch(connector); + + return &imx93_ldb_ch->encoder; +} + +static void imx93_ldb_encoder_enable(struct drm_encoder *encoder) +{ + struct imx93_ldb_channel *imx93_ldb_ch = enc_to_imx93_ldb_ch(encoder); + struct imx93_ldb *imx93_ldb = imx93_ldb_ch->imx93_ldb; + struct ldb *ldb = &imx93_ldb->base; + + clk_prepare_enable(imx93_ldb->clk_root); + + ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; + ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0; + + phy_power_on(imx93_ldb_ch->phy); + imx93_ldb_ch->phy_is_on = true; +} + +static void +imx93_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *connector_state) +{ + struct imx93_ldb_channel *imx93_ldb_ch = enc_to_imx93_ldb_ch(encoder); + struct imx93_ldb *imx93_ldb = imx93_ldb_ch->imx93_ldb; + struct ldb *ldb = &imx93_ldb->base; + struct drm_display_mode *mode = &crtc_state->adjusted_mode; + unsigned long serial_clk; + + if (mode->clock > 80000) + dev_warn(ldb->dev, + "%s: mode exceeds 80 MHz pixel clock\n", __func__); + + serial_clk = mode->clock * 7000UL; + clk_set_rate(imx93_ldb->clk_root, serial_clk); +} + +static void imx93_ldb_encoder_disable(struct drm_encoder *encoder) +{ + struct imx93_ldb_channel *imx93_ldb_ch = enc_to_imx93_ldb_ch(encoder); + struct imx93_ldb *imx93_ldb = imx93_ldb_ch->imx93_ldb; + + phy_power_off(imx93_ldb_ch->phy); + imx93_ldb_ch->phy_is_on = false; + + clk_disable_unprepare(imx93_ldb->clk_root); +} + +static int +imx93_ldb_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); + struct imx93_ldb_channel *imx93_ldb_ch = enc_to_imx93_ldb_ch(encoder); + struct ldb_channel *ldb_ch = &imx93_ldb_ch->base; + struct drm_display_info *di = &conn_state->connector->display_info; + + ldb_ch->bus_format = di->bus_formats[0]; + imx_crtc_state->bus_flags = di->bus_flags; + + switch (ldb_ch->bus_format) { + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB666_1X18; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct drm_connector_funcs imx93_ldb_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = imx_drm_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_connector_helper_funcs +imx93_ldb_connector_helper_funcs = { + .best_encoder = imx93_ldb_connector_best_encoder, +}; + +static const struct drm_encoder_helper_funcs imx93_ldb_encoder_helper_funcs = { + .atomic_mode_set = imx93_ldb_encoder_atomic_mode_set, + .enable = imx93_ldb_encoder_enable, + .disable = imx93_ldb_encoder_disable, + .atomic_check = imx93_ldb_encoder_atomic_check, +}; + +static const struct of_device_id imx93_ldb_dt_ids[] = { + { .compatible = "fsl,imx93-ldb", }, + { } +}; +MODULE_DEVICE_TABLE(of, imx93_ldb_dt_ids); + +static int +imx93_ldb_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm = data; + struct device_node *np = dev->of_node; + struct device_node *child; + struct imx93_ldb *imx93_ldb = dev_get_drvdata(dev); + struct ldb *ldb = &imx93_ldb->base; + struct imx93_ldb_channel *imx93_ldb_ch = &imx93_ldb->channel; + struct ldb_channel *ldb_ch = &imx93_ldb_ch->base; + struct drm_encoder *encoder[0]; + int ret; + int i; + + ret = of_get_child_count(np); + if (ret > 1) { + dev_err(dev, "invalid child/LVDS channel count: %d\n", ret); + return -EINVAL; + } + + ldb->dev = dev; + ldb->ctrl_reg = 0x20; + ldb->output_port = 1; + + imx93_ldb_ch->imx93_ldb = imx93_ldb; + ldb->channel[0] = ldb_ch; + + imx93_ldb->clk_root = devm_clk_get(dev, "ldb"); + if (IS_ERR(imx93_ldb->clk_root)) + return PTR_ERR(imx93_ldb->clk_root); + + *encoder = &imx93_ldb_ch->encoder; + drm_encoder_helper_add(*encoder, &imx93_ldb_encoder_helper_funcs); + drm_simple_encoder_init(drm, *encoder, DRM_MODE_ENCODER_LVDS); + + pm_runtime_enable(dev); + + ret = ldb_bind(ldb, encoder); + if (ret) + goto disable_pm_runtime; + + child = of_get_next_child(np, NULL); + if (!child) { + dev_err(dev, "no child node for LVDS channel\n"); + ret = -ENODEV; + goto disable_pm_runtime; + } + + ret = of_property_read_u32(child, "reg", &i); + if (ret || i != 0) { + dev_err(dev, "invalid LVDS channel number\n"); + ret = -EINVAL; + goto free_child; + } + + if (!of_device_is_available(child)) { + dev_info(dev, "LVDS channel is not available\n"); + goto free_child; + } + + imx93_ldb_ch->phy = devm_of_phy_get(dev, child, "ldb_phy"); + if (IS_ERR(imx93_ldb_ch->phy)) { + ret = PTR_ERR(imx93_ldb_ch->phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get LVDS channel phy: %d\n", + ret); + goto free_child; + } + + ret = phy_init(imx93_ldb_ch->phy); + if (ret < 0) { + dev_err(dev, "failed to initialize LVDS channel phy: %d\n", + ret); + goto free_child; + } + + if (!ldb_ch->is_valid) { + drm_encoder_cleanup(*encoder); + goto free_child; + } + + ret = imx_drm_encoder_parse_of(drm, *encoder, child); + if (ret) + goto free_child; + + return 0; + +free_child: + of_node_put(child); +disable_pm_runtime: + pm_runtime_disable(dev); + + return ret; +} + +static void imx93_ldb_unbind(struct device *dev, struct device *master, + void *data) +{ + struct imx93_ldb *imx93_ldb = dev_get_drvdata(dev); + struct imx93_ldb_channel *imx93_ldb_ch = &imx93_ldb->channel; + + if (imx93_ldb_ch->phy_is_on) + phy_power_off(imx93_ldb_ch->phy); + + phy_exit(imx93_ldb_ch->phy); + + pm_runtime_disable(dev); +} + +static const struct component_ops imx93_ldb_ops = { + .bind = imx93_ldb_bind, + .unbind = imx93_ldb_unbind, +}; + +static int imx93_ldb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx93_ldb *imx93_ldb; + + imx93_ldb = devm_kzalloc(dev, sizeof(*imx93_ldb), GFP_KERNEL); + if (!imx93_ldb) + return -ENOMEM; + + dev_set_drvdata(dev, imx93_ldb); + + return component_add(dev, &imx93_ldb_ops); +} + +static int imx93_ldb_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &imx93_ldb_ops); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int imx93_ldb_suspend(struct device *dev) +{ + struct imx93_ldb *imx93_ldb = dev_get_drvdata(dev); + + if (!imx93_ldb) + return 0; + + phy_exit(imx93_ldb->channel.phy); + + return 0; +} + +static int imx93_ldb_resume(struct device *dev) +{ + struct imx93_ldb *imx93_ldb = dev_get_drvdata(dev); + + if (!imx93_ldb) + return 0; + + phy_init(imx93_ldb->channel.phy); + + return 0; +} +#endif + +static const struct dev_pm_ops imx93_ldb_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(imx93_ldb_suspend, imx93_ldb_resume) +}; + +static struct platform_driver imx93_ldb_driver = { + .probe = imx93_ldb_probe, + .remove = imx93_ldb_remove, + .driver = { + .of_match_table = imx93_ldb_dt_ids, + .name = DRIVER_NAME, + .pm = &imx93_ldb_pm_ops, + }, +}; + +module_platform_driver(imx93_ldb_driver); + +MODULE_DESCRIPTION("i.MX93 LVDS driver"); +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/imx/imx93-parallel-disp-fmt.c b/drivers/gpu/drm/imx/imx93-parallel-disp-fmt.c new file mode 100644 index 000000000000..8f60690255d8 --- /dev/null +++ b/drivers/gpu/drm/imx/imx93-parallel-disp-fmt.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Copyright 2022 NXP + */ + +#include <linux/component.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_of.h> +#include <drm/drm_simple_kms_helper.h> + +#include "imx-drm.h" + +#define DISPLAY_MUX_CTRL 0x60 +#define PARALLEL_DISP_FORMAT 0x700 + +enum imx93_pdf_format { + RGB888_TO_RGB888 = 0x0, + RGB888_TO_RGB666 = 0x1 << 8, + RGB565_TO_RGB565 = 0x2 << 8, +}; + +struct imx93_pdf { + struct drm_encoder encoder; + struct device *dev; + struct regmap *regmap; + struct drm_bridge *bridge; + enum imx93_pdf_format format; + u32 bus_format; +}; + +static inline struct imx93_pdf *enc_to_pdf(struct drm_encoder *e) +{ + return container_of(e, struct imx93_pdf, encoder); +} + +static void imx93_pdf_encoder_enable(struct drm_encoder *encoder) +{ + struct imx93_pdf *pdf = enc_to_pdf(encoder); + + regmap_update_bits(pdf->regmap, DISPLAY_MUX_CTRL, PARALLEL_DISP_FORMAT, + pdf->format); +} + +static int +imx93_pdf_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct imx93_pdf *pdf = enc_to_pdf(encoder); + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); + struct drm_display_info *di = &conn_state->connector->display_info; + u32 bus_format; + + if (!pdf->bus_format) + pdf->bus_format = di->bus_formats[0]; + + switch (pdf->bus_format) { + case MEDIA_BUS_FMT_RGB565_1X16: + pdf->format = RGB565_TO_RGB565; + bus_format = pdf->bus_format; + break; + case MEDIA_BUS_FMT_RGB666_1X18: + pdf->format = RGB888_TO_RGB666; + bus_format = MEDIA_BUS_FMT_RGB888_1X24; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + pdf->format = RGB888_TO_RGB888; + bus_format = pdf->bus_format; + break; + default: + dev_dbg(pdf->dev, "invalid bus format 0x%x\n", pdf->bus_format); + return -EINVAL; + } + + imx_crtc_state->bus_format = bus_format; + imx_crtc_state->bus_flags = di->bus_flags; + + return 0; +} + +static const struct drm_encoder_helper_funcs imx93_pdf_encoder_helper_funcs = { + .atomic_check = imx93_pdf_encoder_atomic_check, + .enable = imx93_pdf_encoder_enable, +}; + +static int imx93_pdf_register(struct drm_device *drm, struct imx93_pdf *pdf) +{ + struct drm_encoder *encoder = &pdf->encoder; + int ret; + + ret = imx_drm_encoder_parse_of(drm, encoder, pdf->dev->of_node); + if (ret) + return ret; + + drm_encoder_helper_add(encoder, &imx93_pdf_encoder_helper_funcs); + drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_DPI); + + ret = drm_bridge_attach(encoder, pdf->bridge, NULL, 0); + if (ret < 0) { + dev_err(pdf->dev, "failed to attach bridge: %d\n", ret); + return ret; + } + + return 0; +} + +static int imx93_pdf_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm = data; + struct device_node *np = dev->of_node; + struct imx93_pdf *pdf = dev_get_drvdata(dev); + struct drm_panel *panel; + const char *fmt; + u32 bus_format = 0; + int ret; + + pdf->regmap = syscon_node_to_regmap(np->parent); + if (IS_ERR(pdf->regmap)) + return dev_err_probe(dev, PTR_ERR(pdf->regmap), + "failed to get regmap\n"); + + pdf->dev = dev; + + ret = of_property_read_string(np, "fsl,interface-pix-fmt", &fmt); + if (!ret) { + if (!strcmp(fmt, "rgb565")) + bus_format = MEDIA_BUS_FMT_RGB565_1X16; + else if (!strcmp(fmt, "rgb666")) + bus_format = MEDIA_BUS_FMT_RGB666_1X18; + else if (!strcmp(fmt, "rgb888")) + bus_format = MEDIA_BUS_FMT_RGB888_1X24; + } + pdf->bus_format = bus_format; + + /* port@1 is the output port */ + ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, &pdf->bridge); + if (ret) + return ret; + + if (panel) { + pdf->bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(pdf->bridge)) { + ret = PTR_ERR(pdf->bridge); + dev_err(dev, "failed to add panel bridge %d\n", ret); + return ret; + } + } + + return imx93_pdf_register(drm, pdf); +} + +static const struct component_ops imx93_pdf_ops = { + .bind = imx93_pdf_bind, +}; + +static int imx93_pdf_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx93_pdf *pdf; + + pdf = devm_kzalloc(dev, sizeof(*pdf), GFP_KERNEL); + if (!pdf) + return -ENOMEM; + + dev_set_drvdata(dev, pdf); + + return component_add(dev, &imx93_pdf_ops); +} + +static int imx93_pdf_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &imx93_pdf_ops); + + return 0; +} + +static const struct of_device_id imx93_pdf_dt_ids[] = { + { .compatible = "fsl,imx93-parallel-display-format", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx93_pdf_dt_ids); + +static struct platform_driver imx93_pdf_driver = { + .probe = imx93_pdf_probe, + .remove = imx93_pdf_remove, + .driver = { + .of_match_table = imx93_pdf_dt_ids, + .name = "imx93-parallel-display-format", + }, +}; + +module_platform_driver(imx93_pdf_driver); + +MODULE_DESCRIPTION("i.MX93 parallel display format driver"); +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx93-parallel-display-format"); diff --git a/drivers/gpu/drm/imx/ipuv3/Kconfig b/drivers/gpu/drm/imx/ipuv3/Kconfig new file mode 100644 index 000000000000..718da3784070 --- /dev/null +++ b/drivers/gpu/drm/imx/ipuv3/Kconfig @@ -0,0 +1,6 @@ +config DRM_IMX_IPUV3 + tristate + depends on DRM_IMX + depends on IMX_IPUV3_CORE + default y if DRM_IMX=y + default m if DRM_IMX=m diff --git a/drivers/gpu/drm/imx/ipuv3/Makefile b/drivers/gpu/drm/imx/ipuv3/Makefile new file mode 100644 index 000000000000..11643dfd23ce --- /dev/null +++ b/drivers/gpu/drm/imx/ipuv3/Makefile @@ -0,0 +1,4 @@ +ccflags-y += -I $(srctree)/$(src)/../ + +imx-ipuv3-crtc-objs := ipuv3-crtc.o ipuv3-plane.o ipuv3-kms.o +obj-$(CONFIG_DRM_IMX_IPUV3) += imx-ipuv3-crtc.o diff --git a/drivers/gpu/drm/imx/ipuv3-crtc.c b/drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c index ba5b16618c23..665204d39b30 100644 --- a/drivers/gpu/drm/imx/ipuv3-crtc.c +++ b/drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c @@ -25,6 +25,7 @@ #include <drm/drm_vblank.h> #include "imx-drm.h" +#include "ipuv3-kms.h" #include "ipuv3-plane.h" #define DRIVER_DESC "i.MX IPUv3 Graphics" @@ -108,7 +109,7 @@ static void ipu_crtc_atomic_disable(struct drm_crtc *crtc, spin_unlock_irq(&crtc->dev->event_lock); } -static void imx_drm_crtc_reset(struct drm_crtc *crtc) +static void ipu_drm_crtc_reset(struct drm_crtc *crtc) { struct imx_crtc_state *state; @@ -123,7 +124,7 @@ static void imx_drm_crtc_reset(struct drm_crtc *crtc) __drm_atomic_helper_crtc_reset(crtc, &state->base); } -static struct drm_crtc_state *imx_drm_crtc_duplicate_state(struct drm_crtc *crtc) +static struct drm_crtc_state *ipu_drm_crtc_duplicate_state(struct drm_crtc *crtc) { struct imx_crtc_state *state; @@ -139,7 +140,7 @@ static struct drm_crtc_state *imx_drm_crtc_duplicate_state(struct drm_crtc *crtc return &state->base; } -static void imx_drm_crtc_destroy_state(struct drm_crtc *crtc, +static void ipu_drm_crtc_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *state) { __drm_atomic_helper_crtc_destroy_state(state); @@ -165,9 +166,9 @@ static void ipu_disable_vblank(struct drm_crtc *crtc) static const struct drm_crtc_funcs ipu_crtc_funcs = { .set_config = drm_atomic_helper_set_config, .page_flip = drm_atomic_helper_page_flip, - .reset = imx_drm_crtc_reset, - .atomic_duplicate_state = imx_drm_crtc_duplicate_state, - .atomic_destroy_state = imx_drm_crtc_destroy_state, + .reset = ipu_drm_crtc_reset, + .atomic_duplicate_state = ipu_drm_crtc_duplicate_state, + .atomic_destroy_state = ipu_drm_crtc_destroy_state, .enable_vblank = ipu_enable_vblank, .disable_vblank = ipu_disable_vblank, }; @@ -448,10 +449,16 @@ static int ipu_drm_remove(struct platform_device *pdev) return 0; } -struct platform_driver ipu_drm_driver = { +static struct platform_driver ipu_drm_driver = { .driver = { .name = "imx-ipuv3-crtc", }, .probe = ipu_drm_probe, .remove = ipu_drm_remove, }; +module_platform_driver(ipu_drm_driver); + +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ipuv3-crtc"); diff --git a/drivers/gpu/drm/imx/ipuv3/ipuv3-kms.c b/drivers/gpu/drm/imx/ipuv3/ipuv3-kms.c new file mode 100644 index 000000000000..c36a9a2eda49 --- /dev/null +++ b/drivers/gpu/drm/imx/ipuv3/ipuv3-kms.c @@ -0,0 +1,94 @@ +/* + * Copyright 2019 NXP + * + * 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 <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_vblank.h> +#include "imx-drm.h" +#include "ipuv3-plane.h" + +static int ipuv3_drm_atomic_check(struct drm_device *dev, + struct drm_atomic_state *state) +{ + int ret; + + ret = drm_atomic_helper_check(dev, state); + if (ret) + return ret; + + /* + * Check modeset again in case crtc_state->mode_changed is + * updated in plane's ->atomic_check callback. + */ + ret = drm_atomic_helper_check_modeset(dev, state); + if (ret) + return ret; + + /* Assign PRG/PRE channels and check if all constrains are satisfied. */ + ret = ipu_planes_assign_pre(dev, state); + if (ret) + return ret; + + return ret; +} + +const struct drm_mode_config_funcs ipuv3_drm_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = ipuv3_drm_atomic_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static void ipuv3_drm_atomic_commit_tail(struct drm_atomic_state *state) +{ + struct drm_device *dev = state->dev; + struct drm_plane *plane; + struct drm_plane_state *old_plane_state, *new_plane_state; + bool plane_disabling = false; + int i; + + drm_atomic_helper_commit_modeset_disables(dev, state); + + drm_atomic_helper_commit_planes(dev, state, + DRM_PLANE_COMMIT_ACTIVE_ONLY | + DRM_PLANE_COMMIT_NO_DISABLE_AFTER_MODESET); + + drm_atomic_helper_commit_modeset_enables(dev, state); + + for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) { + if (drm_atomic_plane_disabling(old_plane_state, new_plane_state)) + plane_disabling = true; + } + + /* + * The flip done wait is only strictly required by imx-drm if a deferred + * plane disable is in-flight. As the core requires blocking commits + * to wait for the flip it is done here unconditionally. This keeps the + * workitem around a bit longer than required for the majority of + * non-blocking commits, but we accept that for the sake of simplicity. + */ + drm_atomic_helper_wait_for_flip_done(dev, state); + + if (plane_disabling) { + for_each_old_plane_in_state(state, plane, old_plane_state, i) + ipu_plane_disable_deferred(plane); + + } + + drm_atomic_helper_commit_hw_done(state); +} + +const struct drm_mode_config_helper_funcs ipuv3_drm_mode_config_helpers = { + .atomic_commit_tail = ipuv3_drm_atomic_commit_tail, +}; diff --git a/drivers/gpu/drm/imx/ipuv3/ipuv3-kms.h b/drivers/gpu/drm/imx/ipuv3/ipuv3-kms.h new file mode 100644 index 000000000000..72f80a91e33e --- /dev/null +++ b/drivers/gpu/drm/imx/ipuv3/ipuv3-kms.h @@ -0,0 +1,21 @@ +/* + * Copyright 2019 NXP + * + * 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. + */ + +#ifndef _IPUV3_KMS_H_ +#define _IPUV3_KMS_H_ + +extern const struct drm_mode_config_funcs ipuv3_drm_mode_config_funcs; +extern struct drm_mode_config_helper_funcs ipuv3_drm_mode_config_helpers; + +#endif diff --git a/drivers/gpu/drm/imx/ipuv3-plane.c b/drivers/gpu/drm/imx/ipuv3/ipuv3-plane.c index 924a66f53951..924a66f53951 100644 --- a/drivers/gpu/drm/imx/ipuv3-plane.c +++ b/drivers/gpu/drm/imx/ipuv3/ipuv3-plane.c diff --git a/drivers/gpu/drm/imx/ipuv3-plane.h b/drivers/gpu/drm/imx/ipuv3/ipuv3-plane.h index 6d544e6ce63f..f646121cc751 100644 --- a/drivers/gpu/drm/imx/ipuv3-plane.h +++ b/drivers/gpu/drm/imx/ipuv3/ipuv3-plane.h @@ -47,4 +47,7 @@ void ipu_plane_disable(struct ipu_plane *ipu_plane, bool disable_dp_channel); void ipu_plane_disable_deferred(struct drm_plane *plane); bool ipu_plane_atomic_update_pending(struct drm_plane *plane); +int ipu_planes_assign_pre(struct drm_device *dev, + struct drm_atomic_state *state); + #endif diff --git a/drivers/gpu/drm/imx/lcdif-mux-display.c b/drivers/gpu/drm/imx/lcdif-mux-display.c new file mode 100644 index 000000000000..7e218269407d --- /dev/null +++ b/drivers/gpu/drm/imx/lcdif-mux-display.c @@ -0,0 +1,254 @@ +/* + * Copyright 2020 NXP + * + * 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 <linux/clk.h> +#include <linux/component.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_simple_kms_helper.h> + +#include "imx-drm.h" + +#define LCDIF_MUX_MODE_MASK 0x3 + +enum lcdif_mux_mode { + LCDIF_MUX_MODE_LCDIF, + LCDIF_MUX_MODE_PL_RGB888, + LCDIF_MUX_MODE_PL_RGB666, + LCDIF_MUX_MODE_PL_RGB565, +}; + +struct imx_lcdif_mux_display { + struct drm_encoder encoder; + struct device *dev; + struct regmap *regmap; + struct clk *clk_bypass_div; + struct clk *clk_pixel; + struct drm_bridge *bridge; + u32 bus_format; + enum lcdif_mux_mode mux_mode; +}; + +static inline struct imx_lcdif_mux_display *enc_to_lmuxd(struct drm_encoder *e) +{ + return container_of(e, struct imx_lcdif_mux_display, encoder); +} + +static void imx_lmuxd_encoder_enable(struct drm_encoder *encoder) +{ + struct imx_lcdif_mux_display *lmuxd = enc_to_lmuxd(encoder); + + clk_prepare_enable(lmuxd->clk_pixel); +} + +static void imx_lmuxd_encoder_disable(struct drm_encoder *encoder) +{ + struct imx_lcdif_mux_display *lmuxd = enc_to_lmuxd(encoder); + + clk_disable_unprepare(lmuxd->clk_pixel); +} + +static void +imx_lmuxd_encoder_atomic_mode_set(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct imx_lcdif_mux_display *lmuxd = enc_to_lmuxd(encoder); + struct drm_display_mode *mode = &crtc_state->adjusted_mode; + unsigned long pixel_clk = mode->clock * 1000; + + clk_set_rate(lmuxd->clk_bypass_div, pixel_clk); + clk_set_rate(lmuxd->clk_pixel, pixel_clk); + + regmap_update_bits(lmuxd->regmap, + 0x0, LCDIF_MUX_MODE_MASK, lmuxd->mux_mode); +} + +static int +imx_lmuxd_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct imx_lcdif_mux_display *lmuxd = enc_to_lmuxd(encoder); + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); + struct drm_display_info *di = &conn_state->connector->display_info; + u32 bus_format; + + imx_crtc_state->bus_flags = di->bus_flags; + + if (!lmuxd->bus_format) + lmuxd->bus_format = di->bus_formats[0]; + + switch (lmuxd->bus_format) { + case MEDIA_BUS_FMT_RGB565_1X16: + bus_format = MEDIA_BUS_FMT_RGB565_1X30_PADLO; + lmuxd->mux_mode = LCDIF_MUX_MODE_PL_RGB565; + break; + case MEDIA_BUS_FMT_RGB666_1X18: + bus_format = MEDIA_BUS_FMT_RGB666_1X30_PADLO; + lmuxd->mux_mode = LCDIF_MUX_MODE_PL_RGB666; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + bus_format = MEDIA_BUS_FMT_RGB888_1X30_PADLO; + lmuxd->mux_mode = LCDIF_MUX_MODE_PL_RGB888; + break; + default: + return -EINVAL; + } + + imx_crtc_state->bus_format = bus_format; + + return 0; +} + +static const struct drm_encoder_helper_funcs imx_lmuxd_encoder_helper_funcs = { + .enable = imx_lmuxd_encoder_enable, + .disable = imx_lmuxd_encoder_disable, + .atomic_mode_set = imx_lmuxd_encoder_atomic_mode_set, + .atomic_check = imx_lmuxd_encoder_atomic_check, +}; + +static int imx_lmuxd_register(struct drm_device *drm, + struct imx_lcdif_mux_display *lmuxd) +{ + struct drm_encoder *encoder = &lmuxd->encoder; + int ret; + + ret = imx_drm_encoder_parse_of(drm, encoder, lmuxd->dev->of_node); + if (ret) + return ret; + + drm_encoder_helper_add(encoder, &imx_lmuxd_encoder_helper_funcs); + drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_DPI); + + ret = drm_bridge_attach(encoder, lmuxd->bridge, NULL, 0); + if (ret < 0) { + dev_err(lmuxd->dev, "failed to attach bridge: %d\n", ret); + return ret; + } + + return 0; +} + +static int imx_lmuxd_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm = data; + struct device_node *np = dev->of_node; + struct imx_lcdif_mux_display *lmuxd = dev_get_drvdata(dev); + struct drm_panel *panel; + const char *fmt; + u32 bus_format = 0; + int ret; + + lmuxd->regmap = + syscon_regmap_lookup_by_phandle(np, "fsl,lcdif-mux-regs"); + if (IS_ERR(lmuxd->regmap)) { + dev_err(dev, "failed to get lcdif mux regmap\n"); + return PTR_ERR(lmuxd->regmap); + } + + lmuxd->clk_bypass_div = devm_clk_get(dev, "bypass_div"); + if (IS_ERR(lmuxd->clk_bypass_div)) + return PTR_ERR(lmuxd->clk_bypass_div); + + lmuxd->clk_pixel = devm_clk_get(dev, "pixel"); + if (IS_ERR(lmuxd->clk_bypass_div)) + return PTR_ERR(lmuxd->clk_bypass_div); + + ret = of_property_read_string(np, "fsl,interface-pix-fmt", &fmt); + if (!ret) { + if (!strcmp(fmt, "rgb565")) + bus_format = MEDIA_BUS_FMT_RGB565_1X16; + else if (!strcmp(fmt, "rgb666")) + bus_format = MEDIA_BUS_FMT_RGB666_1X18; + else if (!strcmp(fmt, "rgb888")) + bus_format = MEDIA_BUS_FMT_RGB888_1X24; + } + lmuxd->bus_format = bus_format; + + /* port@1 is the output port */ + ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, &lmuxd->bridge); + if (ret) + return ret; + + if (panel) { + lmuxd->bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(lmuxd->bridge)) { + ret = PTR_ERR(lmuxd->bridge); + dev_err(dev, "failed to add panel bridge %d\n", ret); + return ret; + } + } + + lmuxd->dev = dev; + + return imx_lmuxd_register(drm, lmuxd); +} + +static void imx_lmuxd_unbind(struct device *dev, struct device *master, + void *data) +{ +} + +static const struct component_ops imx_lmuxd_ops = { + .bind = imx_lmuxd_bind, + .unbind = imx_lmuxd_unbind, +}; + +static int imx_lmuxd_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx_lcdif_mux_display *lmuxd; + + lmuxd = devm_kzalloc(dev, sizeof(*lmuxd), GFP_KERNEL); + if (!lmuxd) + return -ENOMEM; + + dev_set_drvdata(dev, lmuxd); + + return component_add(dev, &imx_lmuxd_ops); +} + +static int imx_lmuxd_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &imx_lmuxd_ops); + + return 0; +} + +static const struct of_device_id imx_lmuxd_dt_ids[] = { + { .compatible = "fsl,imx-lcdif-mux-display", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_lmuxd_dt_ids); + +static struct platform_driver imx_lmuxd_driver = { + .probe = imx_lmuxd_probe, + .remove = imx_lmuxd_remove, + .driver = { + .of_match_table = imx_lmuxd_dt_ids, + .name = "imx-lcdif-mux-display", + }, +}; + +module_platform_driver(imx_lmuxd_driver); + +MODULE_DESCRIPTION("i.MX LCDIF mux display driver"); +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-lcdif-mux-display"); diff --git a/drivers/gpu/drm/imx/lcdif/Kconfig b/drivers/gpu/drm/imx/lcdif/Kconfig new file mode 100644 index 000000000000..4460ffacd1f7 --- /dev/null +++ b/drivers/gpu/drm/imx/lcdif/Kconfig @@ -0,0 +1,8 @@ +config DRM_IMX_LCDIF + tristate "i.MX LCDIF controller DRM driver" + depends on DRM_IMX + depends on IMX_LCDIF_CORE + default y if DRM_IMX=y + default m if DRM_IMX=m + help + enable i.MX LCDIF controller DRM driver under DRM_IMX. diff --git a/drivers/gpu/drm/imx/lcdif/Makefile b/drivers/gpu/drm/imx/lcdif/Makefile new file mode 100644 index 000000000000..59fe9be6d78e --- /dev/null +++ b/drivers/gpu/drm/imx/lcdif/Makefile @@ -0,0 +1,4 @@ +ccflags-y += -I $(srctree)/$(src)/../ + +imx-lcdif-crtc-objs := lcdif-crtc.o lcdif-plane.o lcdif-kms.o +obj-$(CONFIG_DRM_IMX_LCDIF) += imx-lcdif-crtc.o diff --git a/drivers/gpu/drm/imx/lcdif/lcdif-crtc.c b/drivers/gpu/drm/imx/lcdif/lcdif-crtc.c new file mode 100644 index 000000000000..195555343158 --- /dev/null +++ b/drivers/gpu/drm/imx/lcdif/lcdif-crtc.c @@ -0,0 +1,461 @@ +/* + * Copyright 2018,2021-2022 NXP + * + * 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 <linux/component.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <drm/drm_atomic.h> +#include <drm/drm_vblank.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_self_refresh_helper.h> +#include <video/imx-lcdif.h> +#include <video/videomode.h> + +#include "imx-drm.h" +#include "lcdif-plane.h" +#include "lcdif-kms.h" + +struct lcdif_crtc { + struct device *dev; + + struct drm_crtc base; + struct lcdif_plane *plane[2]; + + int vbl_irq; + u32 pix_fmt; /* drm fourcc */ +}; + +#define to_lcdif_crtc(crtc) container_of(crtc, struct lcdif_crtc, base) + +static void lcdif_crtc_reset(struct drm_crtc *crtc) +{ + struct imx_crtc_state *state; + + if (crtc->state) { + __drm_atomic_helper_crtc_destroy_state(crtc->state); + + state = to_imx_crtc_state(crtc->state); + kfree(state); + crtc->state = NULL; + } + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return; + + crtc->state = &state->base; + crtc->state->crtc = crtc; +} + +static struct drm_crtc_state *lcdif_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct imx_crtc_state *state, *orig_state; + + if (WARN_ON(!crtc->state)) + return NULL; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base); + + orig_state = to_imx_crtc_state(crtc->state); + state->bus_format = orig_state->bus_format; + state->bus_flags = orig_state->bus_flags; + state->di_hsync_pin = orig_state->di_hsync_pin; + state->di_vsync_pin = orig_state->di_vsync_pin; + + return &state->base; +} + +static void lcdif_crtc_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + __drm_atomic_helper_crtc_destroy_state(state); + kfree(to_imx_crtc_state(state)); +} + +static int lcdif_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc); + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); + struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, + crtc); + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); + + /* Don't check 'bus_format' when CRTC is + * going to be disabled. + */ + if (!crtc_state->enable) + return 0; + + /* For the commit that the CRTC is active + * without planes attached to it should be + * invalid. + */ + if (crtc_state->active && !crtc_state->plane_mask) + return -EINVAL; + + /* check the requested bus format can be + * supported by LCDIF CTRC or not + */ + switch (imx_crtc_state->bus_format) { + case MEDIA_BUS_FMT_RGB565_1X16: + case MEDIA_BUS_FMT_RGB666_1X18: + case MEDIA_BUS_FMT_RGB888_1X24: + break; + default: + dev_err(lcdif_crtc->dev, + "unsupported bus format: %#x\n", + imx_crtc_state->bus_format); + return -EINVAL; + } + + /* + * Force the connectors_changed flag of the new CRTC state to true, + * if the active flag of the new CRTC state is set to false in the + * self refresh mode. This makes it possible for relevant encoder + * and bridges to be disabled if the entire display pipeline needs + * to be disabled in the self refresh mode, e.g., the fb emulation + * is to be blanked. + */ + if (old_crtc_state->self_refresh_active && !crtc_state->active) + crtc_state->connectors_changed = true; + + return 0; +} + +static void lcdif_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + drm_crtc_vblank_on(crtc); + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event) { + WARN_ON(drm_crtc_vblank_get(crtc)); + drm_crtc_arm_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); +} + +static void lcdif_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + /* LCDIF doesn't have command buffer */ + return; +} + +static void lcdif_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc); + struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent); + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc->state); + struct videomode vm; + bool use_i80 = lcdif_drm_connector_is_self_refresh_aware(state); + + drm_display_mode_to_videomode(mode, &vm); + + if (imx_crtc_state->bus_flags & DRM_BUS_FLAG_DE_HIGH) + vm.flags |= DISPLAY_FLAGS_DE_HIGH; + else + vm.flags |= DISPLAY_FLAGS_DE_LOW; + + if (imx_crtc_state->bus_flags & DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE) + vm.flags |= DISPLAY_FLAGS_PIXDATA_POSEDGE; + else + vm.flags |= DISPLAY_FLAGS_PIXDATA_NEGEDGE; + + pm_runtime_get_sync(lcdif_crtc->dev->parent); + + lcdif_set_mode(lcdif, &vm, use_i80); + + /* config LCDIF output bus format */ + lcdif_set_bus_fmt(lcdif, imx_crtc_state->bus_format); + + /* defer the lcdif controller enable to plane update, + * since until then the lcdif config is complete to + * enable the controller to run actually. + */ +} + +static void lcdif_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, + crtc); + struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc); + struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent); + bool use_i80 = lcdif_drm_connector_is_self_refresh_aware(state); + + if (old_crtc_state->self_refresh_active) + return; + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event) { + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); + + drm_crtc_vblank_off(crtc); + + lcdif_disable_controller(lcdif, use_i80); + + pm_runtime_put(lcdif_crtc->dev->parent); +} + +static enum drm_mode_status lcdif_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + u8 vic; + long rate; + struct drm_display_mode *dmt, copy; + struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc); + struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent); + + /* check CEA-861 mode */ + vic = drm_match_cea_mode(mode); + if (vic) + goto check_pix_clk; + + /* check DMT mode */ + dmt = drm_mode_find_dmt(crtc->dev, mode->hdisplay, mode->vdisplay, + drm_mode_vrefresh(mode), false); + if (dmt) { + drm_mode_copy(©, dmt); + drm_mode_destroy(crtc->dev, dmt); + + if (drm_mode_equal(mode, ©)) + goto check_pix_clk; + } + + return MODE_OK; + +check_pix_clk: + rate = lcdif_pix_clk_round_rate(lcdif, mode->clock * 1000); + + if (rate <= 0 || rate != mode->clock * 1000) + return MODE_BAD; + + return MODE_OK; +} + +static const struct drm_crtc_helper_funcs lcdif_helper_funcs = { + .atomic_check = lcdif_crtc_atomic_check, + .atomic_begin = lcdif_crtc_atomic_begin, + .atomic_flush = lcdif_crtc_atomic_flush, + .atomic_enable = lcdif_crtc_atomic_enable, + .atomic_disable = lcdif_crtc_atomic_disable, + .mode_valid = lcdif_crtc_mode_valid, +}; + +static int lcdif_enable_vblank(struct drm_crtc *crtc) +{ + struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc); + struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent); + + lcdif_vblank_irq_enable(lcdif); + enable_irq(lcdif_crtc->vbl_irq); + + return 0; +} + +static void lcdif_disable_vblank(struct drm_crtc *crtc) +{ + struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc); + struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent); + + disable_irq_nosync(lcdif_crtc->vbl_irq); + lcdif_vblank_irq_disable(lcdif); +} + +static const struct drm_crtc_funcs lcdif_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .destroy = drm_crtc_cleanup, + .page_flip = drm_atomic_helper_page_flip, + .reset = lcdif_crtc_reset, + .atomic_duplicate_state = lcdif_crtc_duplicate_state, + .atomic_destroy_state = lcdif_crtc_destroy_state, + .enable_vblank = lcdif_enable_vblank, + .disable_vblank = lcdif_disable_vblank, +}; + +static irqreturn_t lcdif_crtc_vblank_irq_handler(int irq, void *dev_id) +{ + struct lcdif_crtc *lcdif_crtc = dev_id; + struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent); + + drm_crtc_handle_vblank(&lcdif_crtc->base); + + lcdif_vblank_irq_clear(lcdif); + + return IRQ_HANDLED; +} + +static int lcdif_crtc_init(struct lcdif_crtc *lcdif_crtc, + struct lcdif_client_platformdata *pdata, + struct drm_device *drm) +{ + int ret; + struct lcdif_plane *primary = lcdif_crtc->plane[0]; + struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent); + + /* Primary plane + * The 'possible_crtcs' of primary plane will be + * recalculated during the 'crtc' initialization + * later. + */ + primary = lcdif_plane_init(drm, lcdif, 0, DRM_PLANE_TYPE_PRIMARY, 0); + if (IS_ERR(primary)) + return PTR_ERR(primary); + lcdif_crtc->plane[0] = primary; + + /* TODO: Overlay plane */ + + lcdif_crtc->base.port = pdata->of_node; + drm_crtc_helper_add(&lcdif_crtc->base, &lcdif_helper_funcs); + ret = drm_crtc_init_with_planes(drm, &lcdif_crtc->base, + &lcdif_crtc->plane[0]->base, NULL, + &lcdif_crtc_funcs, NULL); + if (ret) { + dev_err(lcdif_crtc->dev, "failed to init crtc\n"); + goto primary_plane_deinit; + } + + lcdif_crtc->vbl_irq = lcdif_vblank_irq_get(lcdif); + WARN_ON(lcdif_crtc->vbl_irq < 0); + + ret = devm_request_irq(lcdif_crtc->dev, lcdif_crtc->vbl_irq, + lcdif_crtc_vblank_irq_handler, 0, + dev_name(lcdif_crtc->dev), lcdif_crtc); + if (ret) { + dev_err(lcdif_crtc->dev, + "vblank irq request failed: %d\n", ret); + goto primary_plane_deinit; + } + + disable_irq(lcdif_crtc->vbl_irq); + + ret = drm_self_refresh_helper_init(&lcdif_crtc->base); + if (ret) { + dev_err(lcdif_crtc->dev, + "failed to init self refresh helper: %d\n", ret); + goto primary_plane_deinit; + } + + return 0; + +primary_plane_deinit: + lcdif_plane_deinit(drm, primary); + + return ret; +} + +static int lcdif_crtc_bind(struct device *dev, struct device *master, + void *data) +{ + int ret; + struct drm_device *drm = data; + struct lcdif_crtc *lcdif_crtc = dev_get_drvdata(dev); + struct lcdif_client_platformdata *pdata = dev->platform_data; + + dev_dbg(dev, "%s: lcdif crtc bind begin\n", __func__); + + ret = lcdif_crtc_init(lcdif_crtc, pdata, drm); + if (ret) + return ret; + + if (!drm->mode_config.funcs) + drm->mode_config.funcs = &lcdif_drm_mode_config_funcs; + + if (!drm->mode_config.helper_private) + drm->mode_config.helper_private = &lcdif_drm_mode_config_helpers; + + /* limit the max width and height */ + drm->mode_config.max_width = 1920; + drm->mode_config.max_height = 1920; + + dev_dbg(dev, "%s: lcdif crtc bind end\n", __func__); + + return 0; +} + +static void lcdif_crtc_unbind(struct device *dev, struct device *master, + void *data) +{ + struct drm_device *drm = data; + struct lcdif_crtc *lcdif_crtc = dev_get_drvdata(dev); + + drm_self_refresh_helper_cleanup(&lcdif_crtc->base); + + lcdif_plane_deinit(drm, lcdif_crtc->plane[0]); +} + +static const struct component_ops lcdif_crtc_ops = { + .bind = lcdif_crtc_bind, + .unbind = lcdif_crtc_unbind, +}; + +static int lcdif_crtc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct lcdif_crtc *lcdif_crtc; + + dev_dbg(&pdev->dev, "%s: lcdif crtc probe begin\n", __func__); + + lcdif_crtc = devm_kzalloc(dev, sizeof(*lcdif_crtc), GFP_KERNEL); + if (!lcdif_crtc) + return -ENOMEM; + + lcdif_crtc->dev = dev; + + if (!dev->platform_data) { + dev_err(dev, "no platform data\n"); + return -EINVAL; + } + + dev_set_drvdata(dev, lcdif_crtc); + + return component_add(dev, &lcdif_crtc_ops); +} + +static int lcdif_crtc_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &lcdif_crtc_ops); + + return 0; +} + +static struct platform_driver lcdif_crtc_driver = { + .probe = lcdif_crtc_probe, + .remove = lcdif_crtc_remove, + .driver = { + .name = "imx-lcdif-crtc", + }, +}; +module_platform_driver(lcdif_crtc_driver); + +MODULE_DESCRIPTION("NXP i.MX LCDIF DRM CRTC driver"); +MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/imx/lcdif/lcdif-kms.c b/drivers/gpu/drm/imx/lcdif/lcdif-kms.c new file mode 100644 index 000000000000..f4c83ed8d802 --- /dev/null +++ b/drivers/gpu/drm/imx/lcdif/lcdif-kms.c @@ -0,0 +1,47 @@ +/* + * Copyright 2018 NXP + * + * 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 <drm/drm_vblank.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> + +static void lcdif_drm_atomic_commit_tail(struct drm_atomic_state *state) +{ + struct drm_device *dev = state->dev; + + drm_atomic_helper_commit_modeset_disables(dev, state); + + drm_atomic_helper_commit_modeset_enables(dev, state); + + drm_atomic_helper_commit_planes(dev, state, DRM_PLANE_COMMIT_ACTIVE_ONLY); + + drm_atomic_helper_commit_hw_done(state); + + drm_atomic_helper_wait_for_vblanks(dev, state); + + drm_atomic_helper_cleanup_planes(dev, state); +} + +const struct drm_mode_config_funcs lcdif_drm_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +struct drm_mode_config_helper_funcs lcdif_drm_mode_config_helpers = { + .atomic_commit_tail = lcdif_drm_atomic_commit_tail, +}; diff --git a/drivers/gpu/drm/imx/lcdif/lcdif-kms.h b/drivers/gpu/drm/imx/lcdif/lcdif-kms.h new file mode 100644 index 000000000000..cbe5f7f4fa3f --- /dev/null +++ b/drivers/gpu/drm/imx/lcdif/lcdif-kms.h @@ -0,0 +1,39 @@ +/* + * Copyright 2018,2021 NXP + * + * 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. + */ + +#ifndef __LCDIF_KMS_H +#define __LCDIF_KMS_H + +#include <drm/drm_atomic.h> +#include <drm/drm_connector.h> + +extern const struct drm_mode_config_funcs lcdif_drm_mode_config_funcs; +extern struct drm_mode_config_helper_funcs lcdif_drm_mode_config_helpers; + +static inline bool +lcdif_drm_connector_is_self_refresh_aware(struct drm_atomic_state *state) +{ + struct drm_connector *conn; + struct drm_connector_state *conn_state; + int i; + + for_each_new_connector_in_state(state, conn, conn_state, i) { + if (conn_state->self_refresh_aware) + return true; + } + + return false; +} + +#endif diff --git a/drivers/gpu/drm/imx/lcdif/lcdif-plane.c b/drivers/gpu/drm/imx/lcdif/lcdif-plane.c new file mode 100644 index 000000000000..4a938bb15415 --- /dev/null +++ b/drivers/gpu/drm/imx/lcdif/lcdif-plane.c @@ -0,0 +1,261 @@ +/* + * Copyright 2018,2021 NXP + * + * 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 <linux/module.h> +#include <drm/drm_vblank.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane.h> +#include <drm/drm_plane_helper.h> +#include <drm/drm_rect.h> +#include <video/imx-lcdif.h> + +#include "lcdif-plane.h" +#include "lcdif-kms.h" + +static uint32_t lcdif_pixel_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGB565, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_BGR565, +}; + +static int lcdif_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + int ret; + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_plane_state *old_state = plane->state; + struct drm_framebuffer *fb = plane_state->fb; + struct drm_framebuffer *old_fb = old_state->fb; + struct drm_crtc_state *crtc_state; + struct drm_display_mode *mode; + struct drm_rect clip = { 0 }; + bool use_i80; + + /* 'fb' should also be NULL which has been checked in + * the core sanity check function 'drm_atomic_plane_check()' + */ + if (!plane_state->crtc) { + WARN_ON(fb); + return 0; + } + + /* lcdif crtc can only display from (0,0) for each plane */ + if (plane_state->crtc_x || plane_state->crtc_y) + return -EINVAL; + + crtc_state = drm_atomic_get_existing_crtc_state(state, + plane_state->crtc); + mode = &crtc_state->adjusted_mode; + + clip.x2 = mode->hdisplay; + clip.y2 = mode->vdisplay; + + ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state, + DRM_PLANE_HELPER_NO_SCALING, + DRM_PLANE_HELPER_NO_SCALING, + false, true); + + if (ret) + return ret; + + if (!plane_state->visible) + return -EINVAL; + + /* force 'mode_changed' when fb pitches or format + * changed, since the pitch and format related + * registers configuration of LCDIF can not be + * done when LCDIF is running and 'mode_changed' + * means a full modeset is required. + */ + if (old_fb && likely(!crtc_state->mode_changed)) { + if (old_fb->pitches[0] != fb->pitches[0] || + old_fb->format->format != fb->format->format) + crtc_state->mode_changed = true; + } + + /* Add affected connectors to check if we use i80 mode or not. */ + ret = drm_atomic_add_affected_connectors(state, plane_state->crtc); + if (ret) + return ret; + + use_i80 = lcdif_drm_connector_is_self_refresh_aware(state); + + /* Do not support cropping in i80 mode. */ + if (use_i80 && (plane_state->src_w >> 16 != fb->width)) + return -EINVAL; + + return 0; +} + +static void lcdif_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct lcdif_plane *lcdif_plane = to_lcdif_plane(plane); + struct lcdif_soc *lcdif = lcdif_plane->lcdif; + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_framebuffer *fb = new_plane_state->fb; + struct drm_gem_cma_object *gem_obj = NULL; + u32 fb_addr, src_off, src_w, fb_idx, cpp, stride; + bool crop; + bool use_i80 = lcdif_drm_connector_is_self_refresh_aware(state); + + /* plane and crtc is disabling */ + if (!fb) + return; + + /* TODO: for now we just update the next buf addr + * and the fb pixel format, since the mode set will + * be done in crtc's ->enable() helper func + */ + switch (plane->type) { + case DRM_PLANE_TYPE_PRIMARY: + /* TODO: only support RGB */ + gem_obj = drm_fb_cma_get_gem_obj(fb, 0); + src_off = (new_plane_state->src_y >> 16) * fb->pitches[0] + + (new_plane_state->src_x >> 16) * fb->format->cpp[0]; + fb_addr = gem_obj->paddr + fb->offsets[0] + src_off; + fb_idx = 0; + break; + default: + /* TODO: add overlay later */ + return; + } + + lcdif_set_fb_addr(lcdif, fb_idx, fb_addr, use_i80); + + /* Config pixel format and horizontal cropping + * if CRTC needs a full modeset which needs to + * enable LCDIF to run at the end. + */ + if (unlikely(drm_atomic_crtc_needs_modeset(new_plane_state->crtc->state))) { + if (plane->type == DRM_PLANE_TYPE_PRIMARY) + lcdif_set_pix_fmt(lcdif, fb->format->format); + + cpp = fb->format->cpp[0]; + stride = DIV_ROUND_UP(fb->pitches[0], cpp); + + src_w = new_plane_state->src_w >> 16; + WARN_ON(src_w > fb->width); + + crop = src_w != stride ? true : false; + lcdif_set_fb_hcrop(lcdif, src_w, stride, crop); + + lcdif_enable_controller(lcdif, use_i80); + } else if (use_i80) { + lcdif_enable_controller(lcdif, use_i80); + } +} + +static void lcdif_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_framebuffer *fb = new_plane_state->fb; + + WARN_ON(fb); + + /* TODO: CRTC disabled has been done by CRTC helper function, + * so it seems that no more required, the only possible thing + * is to set next buf addr to 0 in CRTC + */ +} + +static const struct drm_plane_helper_funcs lcdif_plane_helper_funcs = { + .atomic_check = lcdif_plane_atomic_check, + .atomic_update = lcdif_plane_atomic_update, + .atomic_disable = lcdif_plane_atomic_disable, +}; + +static void lcdif_plane_destroy(struct drm_plane *plane) +{ + struct lcdif_plane *lcdif_plane = to_lcdif_plane(plane); + + drm_plane_cleanup(plane); + kfree(lcdif_plane); +} + +static const struct drm_plane_funcs lcdif_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = lcdif_plane_destroy, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +struct lcdif_plane *lcdif_plane_init(struct drm_device *dev, + struct lcdif_soc *lcdif, + unsigned int possible_crtcs, + enum drm_plane_type type, + unsigned int zpos) +{ + int ret; + struct lcdif_plane *lcdif_plane; + + /* lcdif doesn't support fb modifiers */ + if (zpos || dev->mode_config.allow_fb_modifiers) + return ERR_PTR(-EINVAL); + + lcdif_plane = kzalloc(sizeof(*lcdif_plane), GFP_KERNEL); + if (!lcdif_plane) + return ERR_PTR(-ENOMEM); + + lcdif_plane->lcdif = lcdif; + + drm_plane_helper_add(&lcdif_plane->base, &lcdif_plane_helper_funcs); + ret = drm_universal_plane_init(dev, &lcdif_plane->base, possible_crtcs, + &lcdif_plane_funcs, lcdif_pixel_formats, + ARRAY_SIZE(lcdif_pixel_formats), NULL, + type, NULL); + if (ret) { + kfree(lcdif_plane); + return ERR_PTR(ret); + } + + ret = drm_plane_create_zpos_immutable_property(&lcdif_plane->base, zpos); + if (ret) { + kfree(lcdif_plane); + return ERR_PTR(ret); + } + + return lcdif_plane; +} + +void lcdif_plane_deinit(struct drm_device *dev, + struct lcdif_plane *lcdif_plane) +{ + struct drm_plane *plane = &lcdif_plane->base; + + if (plane->zpos_property) + drm_property_destroy(dev, plane->zpos_property); + + lcdif_plane_destroy(plane); +} diff --git a/drivers/gpu/drm/imx/lcdif/lcdif-plane.h b/drivers/gpu/drm/imx/lcdif/lcdif-plane.h new file mode 100644 index 000000000000..acd7aead606a --- /dev/null +++ b/drivers/gpu/drm/imx/lcdif/lcdif-plane.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018 NXP + * + * 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. + */ + +#ifndef __LCDIF_PLANE_H +#define __LCDIF_PLANE_H + +#include <drm/drm_plane.h> +#include <video/imx-lcdif.h> + +struct lcdif_plane { + struct drm_plane base; + struct lcdif_soc *lcdif; +}; + +#define to_lcdif_plane(plane) container_of(plane, struct lcdif_plane, base) + +struct lcdif_plane *lcdif_plane_init(struct drm_device *drm, + struct lcdif_soc *lcdif, + unsigned int possible_crtcs, + enum drm_plane_type type, + unsigned int zpos); + +void lcdif_plane_deinit(struct drm_device *dev, + struct lcdif_plane *lcdif_plane); + +#endif diff --git a/drivers/gpu/drm/imx/lcdifv3/Kconfig b/drivers/gpu/drm/imx/lcdifv3/Kconfig new file mode 100644 index 000000000000..6d64c29778b9 --- /dev/null +++ b/drivers/gpu/drm/imx/lcdifv3/Kconfig @@ -0,0 +1,8 @@ +config DRM_IMX_LCDIFV3 + tristate "i.MX LCDIFV3 controller DRM driver" + depends on DRM_IMX + depends on IMX_LCDIFV3_CORE + default y if DRM_IMX=y + default m if DRM_IMX=m + help + enable i.MX LCDIFV3 controller DRM driver under DRM_IMX. diff --git a/drivers/gpu/drm/imx/lcdifv3/Makefile b/drivers/gpu/drm/imx/lcdifv3/Makefile new file mode 100644 index 000000000000..2f2b91078dba --- /dev/null +++ b/drivers/gpu/drm/imx/lcdifv3/Makefile @@ -0,0 +1,4 @@ +ccflags-y += -I $(srctree)/$(src)/../ + +imx-lcdifv3-crtc-objs := lcdifv3-crtc.o lcdifv3-plane.o lcdifv3-kms.o +obj-$(CONFIG_DRM_IMX_LCDIFV3) += imx-lcdifv3-crtc.o diff --git a/drivers/gpu/drm/imx/lcdifv3/lcdifv3-crtc.c b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-crtc.c new file mode 100644 index 000000000000..5a7133743850 --- /dev/null +++ b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-crtc.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2019,2020,2022 NXP + */ + +#include <linux/component.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_vblank.h> +#include <video/imx-lcdifv3.h> +#include <video/videomode.h> + +#include "imx-drm.h" +#include "lcdifv3-plane.h" +#include "lcdifv3-kms.h" + +struct lcdifv3_crtc { + struct device *dev; + + struct drm_crtc base; + struct lcdifv3_plane *plane[2]; + + int vbl_irq; + u32 pix_fmt; /* drm fourcc */ +}; + +#define to_lcdifv3_crtc(crtc) container_of(crtc, struct lcdifv3_crtc, base) + +static void lcdifv3_crtc_reset(struct drm_crtc *crtc) +{ + struct imx_crtc_state *state; + + if (crtc->state) { + __drm_atomic_helper_crtc_destroy_state(crtc->state); + + state = to_imx_crtc_state(crtc->state); + kfree(state); + crtc->state = NULL; + } + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return; + + crtc->state = &state->base; + crtc->state->crtc = crtc; +} + +static struct drm_crtc_state *lcdifv3_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct imx_crtc_state *state, *orig_state; + + if (WARN_ON(!crtc->state)) + return NULL; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base); + + orig_state = to_imx_crtc_state(crtc->state); + state->bus_format = orig_state->bus_format; + state->bus_flags = orig_state->bus_flags; + state->di_hsync_pin = orig_state->di_hsync_pin; + state->di_vsync_pin = orig_state->di_vsync_pin; + + return &state->base; +} + +static void lcdifv3_crtc_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + __drm_atomic_helper_crtc_destroy_state(state); + kfree(to_imx_crtc_state(state)); +} + +static int lcdifv3_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc); + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); + + /* Don't check 'bus_format' when CRTC is + * going to be disabled. + */ + if (!crtc_state->enable) + return 0; + + /* For the commit that the CRTC is active + * without planes attached to it should be + * invalid. + */ + if (crtc_state->active && !crtc_state->plane_mask) + return -EINVAL; + + /* check the requested bus format can be + * supported by LCDIF CTRC or not + */ + switch (imx_crtc_state->bus_format) { + case MEDIA_BUS_FMT_RGB565_1X16: + case MEDIA_BUS_FMT_RGB666_1X18: + case MEDIA_BUS_FMT_RGB888_1X24: + break; + default: + dev_err(lcdifv3_crtc->dev, + "unsupported bus format: %#x\n", + imx_crtc_state->bus_format); + return -EINVAL; + } + + return 0; +} + +static void lcdifv3_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + drm_crtc_vblank_on(crtc); + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event) { + WARN_ON(drm_crtc_vblank_get(crtc)); + drm_crtc_arm_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); +} + +static void lcdifv3_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc); + struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent); + + /* kick shadow load for plane config */ + lcdifv3_en_shadow_load(lcdifv3); +} + +static void lcdifv3_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc); + struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent); + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc->state); + struct videomode vm; + + drm_display_mode_to_videomode(mode, &vm); + + if (imx_crtc_state->bus_flags & DRM_BUS_FLAG_DE_HIGH) + vm.flags |= DISPLAY_FLAGS_DE_HIGH; + else + vm.flags |= DISPLAY_FLAGS_DE_LOW; + + if (imx_crtc_state->bus_flags & DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE) + vm.flags |= DISPLAY_FLAGS_PIXDATA_POSEDGE; + else + vm.flags |= DISPLAY_FLAGS_PIXDATA_NEGEDGE; + + pm_runtime_get_sync(lcdifv3_crtc->dev->parent); + + lcdifv3_set_mode(lcdifv3, &vm); + + /* config LCDIF output bus format */ + lcdifv3_set_bus_fmt(lcdifv3, imx_crtc_state->bus_format); + + /* run LCDIFv3 */ + lcdifv3_enable_controller(lcdifv3); +} + +static void lcdifv3_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc); + struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent); + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event) { + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); + + drm_crtc_vblank_off(crtc); + + lcdifv3_disable_controller(lcdifv3); + + pm_runtime_put(lcdifv3_crtc->dev->parent); +} + +static enum drm_mode_status lcdifv3_crtc_mode_valid(struct drm_crtc * crtc, + const struct drm_display_mode *mode) +{ + u8 vic; + long rounded_rate; + unsigned long pclk_rate; + struct drm_display_mode *dmt, copy; + struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc); + struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent); + + /* check CEA-861 mode */ + vic = drm_match_cea_mode(mode); + if (vic) + goto check_pix_clk; + + /* check DMT mode */ + dmt = drm_mode_find_dmt(crtc->dev, mode->hdisplay, mode->vdisplay, + drm_mode_vrefresh(mode), false); + if (dmt) { + drm_mode_copy(©, dmt); + drm_mode_destroy(crtc->dev, dmt); + + if (drm_mode_equal(mode, ©)) + goto check_pix_clk; + } + + return MODE_OK; + +check_pix_clk: + pclk_rate = mode->clock * 1000; + + rounded_rate = lcdifv3_pix_clk_round_rate(lcdifv3, pclk_rate); + + if (rounded_rate <= 0) + return MODE_BAD; + + /* allow +/-0.5% HDMI pixel clock rate shift */ + if (rounded_rate < pclk_rate * 995 / 1000 || + rounded_rate > pclk_rate * 1005 / 1000) + return MODE_BAD; + + return MODE_OK; +} + +static const struct drm_crtc_helper_funcs lcdifv3_helper_funcs = { + .atomic_check = lcdifv3_crtc_atomic_check, + .atomic_begin = lcdifv3_crtc_atomic_begin, + .atomic_flush = lcdifv3_crtc_atomic_flush, + .atomic_enable = lcdifv3_crtc_atomic_enable, + .atomic_disable = lcdifv3_crtc_atomic_disable, + .mode_valid = lcdifv3_crtc_mode_valid, +}; + +static int lcdifv3_enable_vblank(struct drm_crtc *crtc) +{ + struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc); + struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent); + + lcdifv3_vblank_irq_enable(lcdifv3); + enable_irq(lcdifv3_crtc->vbl_irq); + + return 0; +} + +static void lcdifv3_disable_vblank(struct drm_crtc *crtc) +{ + struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc); + struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent); + + disable_irq_nosync(lcdifv3_crtc->vbl_irq); + lcdifv3_vblank_irq_disable(lcdifv3); +} + +static const struct drm_crtc_funcs lcdifv3_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .destroy = drm_crtc_cleanup, + .page_flip = drm_atomic_helper_page_flip, + .reset = lcdifv3_crtc_reset, + .atomic_duplicate_state = lcdifv3_crtc_duplicate_state, + .atomic_destroy_state = lcdifv3_crtc_destroy_state, + .enable_vblank = lcdifv3_enable_vblank, + .disable_vblank = lcdifv3_disable_vblank, +}; + +static irqreturn_t lcdifv3_crtc_vblank_irq_handler(int irq, void *dev_id) +{ + struct lcdifv3_crtc *lcdifv3_crtc = dev_id; + struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent); + + drm_crtc_handle_vblank(&lcdifv3_crtc->base); + + lcdifv3_vblank_irq_clear(lcdifv3); + + return IRQ_HANDLED; +} + +static int lcdifv3_crtc_init(struct lcdifv3_crtc *lcdifv3_crtc, + struct lcdifv3_client_platformdata *pdata, + struct drm_device *drm) +{ + int ret; + struct lcdifv3_plane *primary = lcdifv3_crtc->plane[0]; + struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent); + + /* Primary plane + * The 'possible_crtcs' of primary plane will be + * recalculated during the 'crtc' initialization + * later. + */ + primary = lcdifv3_plane_init(drm, lcdifv3, 0, DRM_PLANE_TYPE_PRIMARY, 0); + if (IS_ERR(primary)) + return PTR_ERR(primary); + lcdifv3_crtc->plane[0] = primary; + + /* TODO: Overlay plane */ + + lcdifv3_crtc->base.port = pdata->of_node; + drm_crtc_helper_add(&lcdifv3_crtc->base, &lcdifv3_helper_funcs); + ret = drm_crtc_init_with_planes(drm, &lcdifv3_crtc->base, + &lcdifv3_crtc->plane[0]->base, NULL, + &lcdifv3_crtc_funcs, NULL); + if (ret) { + dev_err(lcdifv3_crtc->dev, "failed to init crtc\n"); + return ret; + } + + lcdifv3_crtc->vbl_irq = lcdifv3_vblank_irq_get(lcdifv3); + WARN_ON(lcdifv3_crtc->vbl_irq < 0); + + ret = devm_request_irq(lcdifv3_crtc->dev, lcdifv3_crtc->vbl_irq, + lcdifv3_crtc_vblank_irq_handler, 0, + dev_name(lcdifv3_crtc->dev), lcdifv3_crtc); + if (ret) { + dev_err(lcdifv3_crtc->dev, + "vblank irq request failed: %d\n", ret); + return ret; + } + + disable_irq(lcdifv3_crtc->vbl_irq); + + return 0; +} + +static int lcdifv3_crtc_bind(struct device *dev, struct device *master, + void *data) +{ + int ret; + struct drm_device *drm = data; + struct lcdifv3_crtc *lcdifv3_crtc = dev_get_drvdata(dev); + struct lcdifv3_client_platformdata *pdata = dev->platform_data; + + dev_dbg(dev, "%s: lcdifv3 crtc bind begin\n", __func__); + + ret = lcdifv3_crtc_init(lcdifv3_crtc, pdata, drm); + if (ret) + return ret; + + if (!drm->mode_config.funcs) + drm->mode_config.funcs = &lcdifv3_drm_mode_config_funcs; + + if (!drm->mode_config.helper_private) + drm->mode_config.helper_private = &lcdifv3_drm_mode_config_helpers; + + /* limit the max width and height */ + drm->mode_config.max_width = 4096; + drm->mode_config.max_height = 4096; + + dev_dbg(dev, "%s: lcdifv3 crtc bind end\n", __func__); + + return 0; +} + +static void lcdifv3_crtc_unbind(struct device *dev, struct device *master, + void *data) +{ + /* No special to be done */ +} + +static const struct component_ops lcdifv3_crtc_ops = { + .bind = lcdifv3_crtc_bind, + .unbind = lcdifv3_crtc_unbind, +}; + +static int lcdifv3_crtc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct lcdifv3_crtc *lcdifv3_crtc; + + dev_dbg(&pdev->dev, "%s: lcdifv3 crtc probe begin\n", __func__); + + if (!dev->platform_data) { + dev_err(dev, "no platform data\n"); + return -EINVAL; + } + + lcdifv3_crtc = devm_kzalloc(dev, sizeof(*lcdifv3_crtc), GFP_KERNEL); + if (!lcdifv3_crtc) + return -ENOMEM; + + lcdifv3_crtc->dev = dev; + dev_set_drvdata(dev, lcdifv3_crtc); + + return component_add(dev, &lcdifv3_crtc_ops); +} + +static int lcdifv3_crtc_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &lcdifv3_crtc_ops); + + return 0; +} + +static struct platform_driver lcdifv3_crtc_driver = { + .probe = lcdifv3_crtc_probe, + .remove = lcdifv3_crtc_remove, + .driver = { + .name = "imx-lcdifv3-crtc", + }, +}; +module_platform_driver(lcdifv3_crtc_driver); + +MODULE_DESCRIPTION("NXP i.MX LCDIFV3 DRM CRTC driver"); +MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/imx/lcdifv3/lcdifv3-kms.c b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-kms.c new file mode 100644 index 000000000000..3d08a3215d53 --- /dev/null +++ b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-kms.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2019 NXP + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> + +static void lcdifv3_drm_atomic_commit_tail(struct drm_atomic_state *state) +{ + struct drm_device *dev = state->dev; + + drm_atomic_helper_commit_modeset_disables(dev, state); + + drm_atomic_helper_commit_modeset_enables(dev, state); + + drm_atomic_helper_commit_planes(dev, state, DRM_PLANE_COMMIT_ACTIVE_ONLY); + + drm_atomic_helper_commit_hw_done(state); + + drm_atomic_helper_wait_for_vblanks(dev, state); + + drm_atomic_helper_cleanup_planes(dev, state); +} + +const struct drm_mode_config_funcs lcdifv3_drm_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +struct drm_mode_config_helper_funcs lcdifv3_drm_mode_config_helpers = { + .atomic_commit_tail = lcdifv3_drm_atomic_commit_tail, +}; diff --git a/drivers/gpu/drm/imx/lcdifv3/lcdifv3-kms.h b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-kms.h new file mode 100644 index 000000000000..9a7caf4b0c2d --- /dev/null +++ b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-kms.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2019 NXP + */ + +#ifndef __LCDIFV3_KMS_H +#define __LCDIFV3_KMS_H + +extern const struct drm_mode_config_funcs lcdifv3_drm_mode_config_funcs; +extern struct drm_mode_config_helper_funcs lcdifv3_drm_mode_config_helpers; + +#endif diff --git a/drivers/gpu/drm/imx/lcdifv3/lcdifv3-plane.c b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-plane.c new file mode 100644 index 000000000000..6f8425333ee0 --- /dev/null +++ b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-plane.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2019,2020 NXP + */ + +#include <linux/module.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane.h> +#include <drm/drm_plane_helper.h> +#include <drm/drm_rect.h> +#include <drm/drm_vblank.h> +#include <video/imx-lcdifv3.h> + +#include "lcdifv3-plane.h" + +static uint32_t lcdifv3_pixel_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGB565, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, +}; + +static int lcdifv3_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + int ret; + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_plane_state *old_state = plane->state; + struct drm_framebuffer *fb = plane_state->fb; + struct drm_framebuffer *old_fb = old_state->fb; + struct drm_crtc_state *crtc_state; + struct drm_display_mode *mode; + + /* 'fb' should also be NULL which has been checked in + * the core sanity check function 'drm_atomic_plane_check()' + */ + if (!plane_state->crtc) { + WARN_ON(fb); + return 0; + } + + /* lcdifv3 crtc can only display from (0,0) for each plane */ + if (plane_state->crtc_x || plane_state->crtc_y) + return -EINVAL; + + crtc_state = drm_atomic_get_existing_crtc_state(state, + plane_state->crtc); + mode = &crtc_state->adjusted_mode; + + ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state, + DRM_PLANE_HELPER_NO_SCALING, + DRM_PLANE_HELPER_NO_SCALING, + false, true); + if (ret) + return ret; + + if (!plane_state->visible) + return -EINVAL; + + /* force 'mode_changed' when fb pitches changed, since + * the pitch related registers configuration of LCDIF + * can not be done when LCDIF is running. + */ + if (old_fb && likely(!crtc_state->mode_changed)) { + if (old_fb->pitches[0] != fb->pitches[0]) + crtc_state->mode_changed = true; + } + + return 0; +} + +static void lcdifv3_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct lcdifv3_plane *lcdifv3_plane = to_lcdifv3_plane(plane); + struct lcdifv3_soc *lcdifv3 = lcdifv3_plane->lcdifv3; + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_framebuffer *fb = new_plane_state->fb; + struct drm_gem_cma_object *gem_obj = NULL; + u32 fb_addr, src_off, src_w, fb_idx, cpp, stride; + bool crop; + + /* plane and crtc is disabling */ + if (!fb) + return; + + /* TODO: for now we just update the next buf addr + * and the fb pixel format, since the mode set will + * be done in crtc's ->enable() helper func + */ + if (plane->type == DRM_PLANE_TYPE_PRIMARY) + lcdifv3_set_pix_fmt(lcdifv3, fb->format->format); + + switch (plane->type) { + case DRM_PLANE_TYPE_PRIMARY: + /* TODO: only support RGB */ + gem_obj = drm_fb_cma_get_gem_obj(fb, 0); + src_off = (new_plane_state->src_y >> 16) * fb->pitches[0] + + (new_plane_state->src_x >> 16) * fb->format->cpp[0]; + fb_addr = gem_obj->paddr + fb->offsets[0] + src_off; + fb_idx = 0; + break; + default: + /* TODO: add overlay later */ + return; + } + + lcdifv3_set_fb_addr(lcdifv3, fb_idx, fb_addr); + + /* config horizontal cropping if crtc needs modeset */ + if (unlikely(drm_atomic_crtc_needs_modeset(new_plane_state->crtc->state))) { + cpp = fb->format->cpp[0]; + stride = DIV_ROUND_UP(fb->pitches[0], cpp); + + src_w = new_plane_state->src_w >> 16; + WARN_ON(src_w > fb->width); + + crop = src_w != stride ? true : false; + lcdifv3_set_fb_hcrop(lcdifv3, src_w, fb->pitches[0], crop); + } +} + +static void lcdifv3_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_framebuffer *fb = new_plane_state->fb; + + WARN_ON(fb); + + /* TODO: CRTC disabled has been done by CRTC helper function, + * so it seems that no more required, the only possible thing + * is to set next buf addr to 0 in CRTC + */ +} + +static const struct drm_plane_helper_funcs lcdifv3_plane_helper_funcs = { + .atomic_check = lcdifv3_plane_atomic_check, + .atomic_update = lcdifv3_plane_atomic_update, + .atomic_disable = lcdifv3_plane_atomic_disable, +}; + +static void lcdifv3_plane_destroy(struct drm_plane *plane) +{ + struct lcdifv3_plane *lcdifv3_plane = to_lcdifv3_plane(plane); + + drm_plane_cleanup(plane); + kfree(lcdifv3_plane); +} + +static const struct drm_plane_funcs lcdifv3_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = lcdifv3_plane_destroy, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +struct lcdifv3_plane *lcdifv3_plane_init(struct drm_device *dev, + struct lcdifv3_soc *lcdifv3, + unsigned int possible_crtcs, + enum drm_plane_type type, + unsigned int zpos) +{ + int ret; + struct lcdifv3_plane *lcdifv3_plane; + + /* lcdifv3 doesn't support fb modifiers */ + if (zpos || dev->mode_config.allow_fb_modifiers) + return ERR_PTR(-EINVAL); + + lcdifv3_plane = kzalloc(sizeof(*lcdifv3_plane), GFP_KERNEL); + if (!lcdifv3_plane) + return ERR_PTR(-ENOMEM); + + lcdifv3_plane->lcdifv3 = lcdifv3; + + drm_plane_helper_add(&lcdifv3_plane->base, &lcdifv3_plane_helper_funcs); + ret = drm_universal_plane_init(dev, &lcdifv3_plane->base, possible_crtcs, + &lcdifv3_plane_funcs, lcdifv3_pixel_formats, + ARRAY_SIZE(lcdifv3_pixel_formats), NULL, + type, NULL); + if (ret) { + kfree(lcdifv3_plane); + return ERR_PTR(ret); + } + + ret = drm_plane_create_zpos_immutable_property(&lcdifv3_plane->base, zpos); + if (ret) { + kfree(lcdifv3_plane); + return ERR_PTR(ret); + } + + return lcdifv3_plane; +} diff --git a/drivers/gpu/drm/imx/lcdifv3/lcdifv3-plane.h b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-plane.h new file mode 100644 index 000000000000..437b7d97e69f --- /dev/null +++ b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-plane.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2019,2020 NXP + */ + +#ifndef __LCDIFV3_PLANE_H +#define __LCDIFV3_PLANE_H + +#include <drm/drm_plane.h> +#include <video/imx-lcdifv3.h> + +struct lcdifv3_plane { + struct drm_plane base; + struct lcdifv3_soc *lcdifv3; +}; + +#define to_lcdifv3_plane(plane) container_of(plane, struct lcdifv3_plane, base) + +struct lcdifv3_plane *lcdifv3_plane_init(struct drm_device *drm, + struct lcdifv3_soc *lcdifv3, + unsigned int possible_crtcs, + enum drm_plane_type type, + unsigned int zpos); +#endif diff --git a/drivers/gpu/drm/imx/mhdp/Kconfig b/drivers/gpu/drm/imx/mhdp/Kconfig new file mode 100644 index 000000000000..225ccc3638a8 --- /dev/null +++ b/drivers/gpu/drm/imx/mhdp/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config DRM_IMX_CDNS_MHDP + tristate "NXP i.MX MX8 DRM HDMI/DP" + select DRM_CDNS_MHDP + select DRM_CDNS_DP + select DRM_CDNS_HDMI + select DRM_CDNS_AUDIO + select DRM_CDNS_HDMI_HDCP + depends on DRM_IMX + help + Choose this if you want to use HDMI on i.MX8. diff --git a/drivers/gpu/drm/imx/mhdp/Makefile b/drivers/gpu/drm/imx/mhdp/Makefile new file mode 100644 index 000000000000..235fa2d515e9 --- /dev/null +++ b/drivers/gpu/drm/imx/mhdp/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 + +cdns_mhdp_imx-objs := cdns-mhdp-imxdrv.o cdns-mhdp-dp-phy.o \ + cdns-mhdp-hdmi-phy.o cdns-mhdp-imx8qm.o cdns-mhdp-ls1028a.o +obj-$(CONFIG_DRM_IMX_CDNS_MHDP) += cdns_mhdp_imx.o diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-dp-phy.c b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-dp-phy.c new file mode 100644 index 000000000000..190bd25f5cbb --- /dev/null +++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-dp-phy.c @@ -0,0 +1,534 @@ +/* + * Cadence Display Port Interface (DP) PHY driver + * + * Copyright 2019 NXP + * + * 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. + * + */ +#include <linux/clk.h> +#include <linux/kernel.h> +#include <drm/drm_print.h> +#include <drm/drm_dp_helper.h> +#include <drm/bridge/cdns-mhdp.h> +#include "cdns-mhdp-phy.h" + +enum dp_link_rate { + RATE_1_6 = 162000, + RATE_2_1 = 216000, + RATE_2_4 = 243000, + RATE_2_7 = 270000, + RATE_3_2 = 324000, + RATE_4_3 = 432000, + RATE_5_4 = 540000, + RATE_8_1 = 810000, +}; + +struct phy_pll_reg { + u16 val[7]; + u32 addr; +}; + +static const struct phy_pll_reg phy_pll_27m_cfg[] = { + /* 1.62 2.16 2.43 2.7 3.24 4.32 5.4 register address */ + {{ 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E }, CMN_PLL0_VCOCAL_INIT_TMR }, + {{ 0x001B, 0x001B, 0x001B, 0x001B, 0x001B, 0x001B, 0x001B }, CMN_PLL0_VCOCAL_ITER_TMR }, + {{ 0x30B9, 0x3087, 0x3096, 0x30B4, 0x30B9, 0x3087, 0x30B4 }, CMN_PLL0_VCOCAL_START }, + {{ 0x0077, 0x009F, 0x00B3, 0x00C7, 0x0077, 0x009F, 0x00C7 }, CMN_PLL0_INTDIV }, + {{ 0xF9DA, 0xF7CD, 0xF6C7, 0xF5C1, 0xF9DA, 0xF7CD, 0xF5C1 }, CMN_PLL0_FRACDIV }, + {{ 0x001E, 0x0028, 0x002D, 0x0032, 0x001E, 0x0028, 0x0032 }, CMN_PLL0_HIGH_THR }, + {{ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020 }, CMN_PLL0_DSM_DIAG }, + {{ 0x0000, 0x1000, 0x1000, 0x1000, 0x0000, 0x1000, 0x1000 }, CMN_PLLSM0_USER_DEF_CTRL }, + {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_OVRD }, + {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_FBH_OVRD }, + {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_FBL_OVRD }, + {{ 0x0006, 0x0007, 0x0007, 0x0007, 0x0006, 0x0007, 0x0007 }, CMN_DIAG_PLL0_V2I_TUNE }, + {{ 0x0043, 0x0043, 0x0043, 0x0042, 0x0043, 0x0043, 0x0042 }, CMN_DIAG_PLL0_CP_TUNE }, + {{ 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008 }, CMN_DIAG_PLL0_LF_PROG }, + {{ 0x0100, 0x0001, 0x0001, 0x0001, 0x0100, 0x0001, 0x0001 }, CMN_DIAG_PLL0_PTATIS_TUNE1 }, + {{ 0x0007, 0x0001, 0x0001, 0x0001, 0x0007, 0x0001, 0x0001 }, CMN_DIAG_PLL0_PTATIS_TUNE2 }, + {{ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020 }, CMN_DIAG_PLL0_TEST_MODE}, + {{ 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016 }, CMN_PSM_CLK_CTRL } +}; + +static const struct phy_pll_reg phy_pll_24m_cfg[] = { + /* 1.62 2.16 2.43 2.7 3.24 4.32 5.4 register address */ + {{ 0x00F0, 0x00F0, 0x00F0, 0x00F0, 0x00F0, 0x00F0, 0x00F0 }, CMN_PLL0_VCOCAL_INIT_TMR }, + {{ 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018 }, CMN_PLL0_VCOCAL_ITER_TMR }, + {{ 0x3061, 0x3092, 0x30B3, 0x30D0, 0x3061, 0x3092, 0x30D0 }, CMN_PLL0_VCOCAL_START }, + {{ 0x0086, 0x00B3, 0x00CA, 0x00E0, 0x0086, 0x00B3, 0x00E0 }, CMN_PLL0_INTDIV }, + {{ 0xF917, 0xF6C7, 0x75A1, 0xF479, 0xF917, 0xF6C7, 0xF479 }, CMN_PLL0_FRACDIV }, + {{ 0x0022, 0x002D, 0x0033, 0x0038, 0x0022, 0x002D, 0x0038 }, CMN_PLL0_HIGH_THR }, + {{ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020 }, CMN_PLL0_DSM_DIAG }, + {{ 0x0000, 0x1000, 0x1000, 0x1000, 0x0000, 0x1000, 0x1000 }, CMN_PLLSM0_USER_DEF_CTRL }, + {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_OVRD }, + {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_FBH_OVRD }, + {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_FBL_OVRD }, + {{ 0x0006, 0x0007, 0x0007, 0x0007, 0x0006, 0x0007, 0x0007 }, CMN_DIAG_PLL0_V2I_TUNE }, + {{ 0x0026, 0x0029, 0x0029, 0x0029, 0x0026, 0x0029, 0x0029 }, CMN_DIAG_PLL0_CP_TUNE }, + {{ 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008 }, CMN_DIAG_PLL0_LF_PROG }, + {{ 0x008C, 0x008C, 0x008C, 0x008C, 0x008C, 0x008C, 0x008C }, CMN_DIAG_PLL0_PTATIS_TUNE1 }, + {{ 0x002E, 0x002E, 0x002E, 0x002E, 0x002E, 0x002E, 0x002E }, CMN_DIAG_PLL0_PTATIS_TUNE2 }, + {{ 0x0022, 0x0022, 0x0022, 0x0022, 0x0022, 0x0022, 0x0022 }, CMN_DIAG_PLL0_TEST_MODE}, + {{ 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016 }, CMN_PSM_CLK_CTRL } +}; + +static int link_rate_index(u32 rate) +{ + switch (rate) { + case RATE_1_6: + return 0; + case RATE_2_1: + return 1; + case RATE_2_4: + return 2; + case RATE_2_7: + return 3; + case RATE_3_2: + return 4; + case RATE_4_3: + return 5; + case RATE_5_4: + return 6; + default: + return -1; + } +} + +static void dp_aux_cfg(struct cdns_mhdp_device *mhdp) +{ + /* Power up Aux */ + cdns_phy_reg_write(mhdp, TXDA_CYA_AUXDA_CYA, 1); + + cdns_phy_reg_write(mhdp, TX_DIG_CTRL_REG_1, 0x3); + ndelay(150); + cdns_phy_reg_write(mhdp, TX_DIG_CTRL_REG_2, 36); + ndelay(150); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x0100); + ndelay(150); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x0300); + ndelay(150); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_3, 0x0000); + ndelay(150); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0x2008); + ndelay(150); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0x2018); + ndelay(150); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0xA018); + ndelay(150); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x030C); + ndelay(150); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_5, 0x0000); + ndelay(150); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_4, 0x1001); + ndelay(150); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0xA098); + ndelay(150); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0xA198); + ndelay(150); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x030d); + ndelay(150); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x030f); +} + +/* PMA common configuration for 24MHz */ +static void dp_phy_pma_cmn_cfg_24mhz(struct cdns_mhdp_device *mhdp) +{ + int k; + u32 num_lanes = 4; + u16 val; + + val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1); + val &= 0xFFF7; + val |= 0x0008; + cdns_phy_reg_write(mhdp, PHY_PMA_CMN_CTRL1, val); + + for (k = 0; k < num_lanes; k++) { + /* Transceiver control and diagnostic registers */ + cdns_phy_reg_write(mhdp, XCVR_DIAG_LANE_FCM_EN_MGN_TMR | (k << 9), 0x0090); + /* Transmitter receiver detect registers */ + cdns_phy_reg_write(mhdp, TX_RCVDET_EN_TMR | (k << 9), 0x0960); + cdns_phy_reg_write(mhdp, TX_RCVDET_ST_TMR | (k << 9), 0x0030); + } +} + +/* Valid for 24 MHz only */ +static void dp_phy_pma_cmn_pll0_24mhz(struct cdns_mhdp_device *mhdp) +{ + u32 num_lanes = 4; + u32 link_rate = mhdp->dp.rate; + u16 val; + int index, i, k; + + /* + * PLL reference clock source select + * for single ended reference clock val |= 0x0030; + * for differential clock val |= 0x0000; + */ + val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1); + val = val & 0xFF8F; + val = val | 0x0030; + cdns_phy_reg_write(mhdp, PHY_PMA_CMN_CTRL1, val); + + /* DP PLL data rate 0/1 clock divider value */ + val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL); + val &= 0x00FF; + if (link_rate <= RATE_2_7) + val |= 0x2400; + else + val |= 0x1200; + cdns_phy_reg_write(mhdp, PHY_HDP_CLK_CTL, val); + + /* High speed clock 0/1 div */ + val = cdns_phy_reg_read(mhdp, CMN_DIAG_HSCLK_SEL); + val &= 0xFFCC; + if (link_rate <= RATE_2_7) + val |= 0x0011; + cdns_phy_reg_write(mhdp, CMN_DIAG_HSCLK_SEL, val); + + for (k = 0; k < num_lanes; k = k + 1) { + val = cdns_phy_reg_read(mhdp, (XCVR_DIAG_HSCLK_SEL | (k << 9))); + val &= 0xCFFF; + if (link_rate <= RATE_2_7) + val |= 0x1000; + cdns_phy_reg_write(mhdp, (XCVR_DIAG_HSCLK_SEL | (k << 9)), val); + } + + /* DP PHY PLL 24MHz configuration */ + index = link_rate_index(link_rate); + if (index < 0) { + dev_err(mhdp->dev, "wrong link rate index\n"); + return; + } + for (i = 0; i < ARRAY_SIZE(phy_pll_24m_cfg); i++) + cdns_phy_reg_write(mhdp, phy_pll_24m_cfg[i].addr, phy_pll_24m_cfg[i].val[index]); + + /* Transceiver control and diagnostic registers */ + for (k = 0; k < num_lanes; k = k + 1) { + val = cdns_phy_reg_read(mhdp, (XCVR_DIAG_PLLDRC_CTRL | (k << 9))); + val &= 0x8FFF; + if (link_rate <= RATE_2_7) + val |= 0x2000; + else + val |= 0x1000; + cdns_phy_reg_write(mhdp, (XCVR_DIAG_PLLDRC_CTRL | (k << 9)), val); + } + + for (k = 0; k < num_lanes; k = k + 1) { + cdns_phy_reg_write(mhdp, (XCVR_PSM_RCTRL | (k << 9)), 0xBEFC); + cdns_phy_reg_write(mhdp, (TX_PSC_A0 | (k << 9)), 0x6799); + cdns_phy_reg_write(mhdp, (TX_PSC_A1 | (k << 9)), 0x6798); + cdns_phy_reg_write(mhdp, (TX_PSC_A2 | (k << 9)), 0x0098); + cdns_phy_reg_write(mhdp, (TX_PSC_A3 | (k << 9)), 0x0098); + } +} + +/* PMA common configuration for 27MHz */ +static void dp_phy_pma_cmn_cfg_27mhz(struct cdns_mhdp_device *mhdp) +{ + u32 num_lanes = 4; + u16 val; + int k; + + val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1); + val &= 0xFFF7; + val |= 0x0008; + cdns_phy_reg_write(mhdp, PHY_PMA_CMN_CTRL1, val); + + /* Startup state machine registers */ + cdns_phy_reg_write(mhdp, CMN_SSM_BIAS_TMR, 0x0087); + cdns_phy_reg_write(mhdp, CMN_PLLSM0_PLLEN_TMR, 0x001B); + cdns_phy_reg_write(mhdp, CMN_PLLSM0_PLLPRE_TMR, 0x0036); + cdns_phy_reg_write(mhdp, CMN_PLLSM0_PLLVREF_TMR, 0x001B); + cdns_phy_reg_write(mhdp, CMN_PLLSM0_PLLLOCK_TMR, 0x006C); + + /* Current calibration registers */ + cdns_phy_reg_write(mhdp, CMN_ICAL_INIT_TMR, 0x0044); + cdns_phy_reg_write(mhdp, CMN_ICAL_ITER_TMR, 0x0006); + cdns_phy_reg_write(mhdp, CMN_ICAL_ADJ_INIT_TMR, 0x0022); + cdns_phy_reg_write(mhdp, CMN_ICAL_ADJ_ITER_TMR, 0x0006); + + /* Resistor calibration registers */ + cdns_phy_reg_write(mhdp, CMN_TXPUCAL_INIT_TMR, 0x0022); + cdns_phy_reg_write(mhdp, CMN_TXPUCAL_ITER_TMR, 0x0006); + cdns_phy_reg_write(mhdp, CMN_TXPU_ADJ_INIT_TMR, 0x0022); + cdns_phy_reg_write(mhdp, CMN_TXPU_ADJ_ITER_TMR, 0x0006); + cdns_phy_reg_write(mhdp, CMN_TXPDCAL_INIT_TMR, 0x0022); + cdns_phy_reg_write(mhdp, CMN_TXPDCAL_ITER_TMR, 0x0006); + cdns_phy_reg_write(mhdp, CMN_TXPD_ADJ_INIT_TMR, 0x0022); + cdns_phy_reg_write(mhdp, CMN_TXPD_ADJ_ITER_TMR, 0x0006); + cdns_phy_reg_write(mhdp, CMN_RXCAL_INIT_TMR, 0x0022); + cdns_phy_reg_write(mhdp, CMN_RXCAL_ITER_TMR, 0x0006); + cdns_phy_reg_write(mhdp, CMN_RX_ADJ_INIT_TMR, 0x0022); + cdns_phy_reg_write(mhdp, CMN_RX_ADJ_ITER_TMR, 0x0006); + + for (k = 0; k < num_lanes; k = k + 1) { + /* Power state machine registers */ + cdns_phy_reg_write(mhdp, XCVR_PSM_CAL_TMR | (k << 9), 0x016D); + cdns_phy_reg_write(mhdp, XCVR_PSM_A0IN_TMR | (k << 9), 0x016D); + /* Transceiver control and diagnostic registers */ + cdns_phy_reg_write(mhdp, XCVR_DIAG_LANE_FCM_EN_MGN_TMR | (k << 9), 0x00A2); + cdns_phy_reg_write(mhdp, TX_DIAG_BGREF_PREDRV_DELAY | (k << 9), 0x0097); + /* Transmitter receiver detect registers */ + cdns_phy_reg_write(mhdp, TX_RCVDET_EN_TMR | (k << 9), 0x0A8C); + cdns_phy_reg_write(mhdp, TX_RCVDET_ST_TMR | (k << 9), 0x0036); + } +} + +static void dp_phy_pma_cmn_pll0_27mhz(struct cdns_mhdp_device *mhdp) +{ + u32 num_lanes = 4; + u32 link_rate = mhdp->dp.rate; + u16 val; + int index, i, k; + + /* + * PLL reference clock source select + * for single ended reference clock val |= 0x0030; + * for differential clock val |= 0x0000; + */ + val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1); + val &= 0xFF8F; + cdns_phy_reg_write(mhdp, PHY_PMA_CMN_CTRL1, val); + + /* for differential clock on the refclk_p and refclk_m off chip pins: + * CMN_DIAG_ACYA[8]=1'b1 + */ + cdns_phy_reg_write(mhdp, CMN_DIAG_ACYA, 0x0100); + + /* DP PLL data rate 0/1 clock divider value */ + val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL); + val &= 0x00FF; + if (link_rate <= RATE_2_7) + val |= 0x2400; + else + val |= 0x1200; + cdns_phy_reg_write(mhdp, PHY_HDP_CLK_CTL, val); + + /* High speed clock 0/1 div */ + val = cdns_phy_reg_read(mhdp, CMN_DIAG_HSCLK_SEL); + val &= 0xFFCC; + if (link_rate <= RATE_2_7) + val |= 0x0011; + cdns_phy_reg_write(mhdp, CMN_DIAG_HSCLK_SEL, val); + + for (k = 0; k < num_lanes; k++) { + val = cdns_phy_reg_read(mhdp, (XCVR_DIAG_HSCLK_SEL | (k << 9))); + val = val & 0xCFFF; + if (link_rate <= RATE_2_7) + val |= 0x1000; + cdns_phy_reg_write(mhdp, (XCVR_DIAG_HSCLK_SEL | (k << 9)), val); + } + + /* DP PHY PLL 27MHz configuration */ + index = link_rate_index(link_rate); + if (index < 0) { + dev_err(mhdp->dev, "wrong link rate index\n"); + return; + } + for (i = 0; i < ARRAY_SIZE(phy_pll_27m_cfg); i++) + cdns_phy_reg_write(mhdp, phy_pll_27m_cfg[i].addr, phy_pll_27m_cfg[i].val[index]); + + /* Transceiver control and diagnostic registers */ + for (k = 0; k < num_lanes; k++) { + val = cdns_phy_reg_read(mhdp, (XCVR_DIAG_PLLDRC_CTRL | (k << 9))); + val = val & 0x8FFF; + if (link_rate <= RATE_2_7) + val |= 0x2000; + else + val |= 0x1000; + cdns_phy_reg_write(mhdp, (XCVR_DIAG_PLLDRC_CTRL | (k << 9)), val); + } + + for (k = 0; k < num_lanes; k = k + 1) { + /* Power state machine registers */ + cdns_phy_reg_write(mhdp, (XCVR_PSM_RCTRL | (k << 9)), 0xBEFC); + cdns_phy_reg_write(mhdp, (TX_PSC_A0 | (k << 9)), 0x6799); + cdns_phy_reg_write(mhdp, (TX_PSC_A1 | (k << 9)), 0x6798); + cdns_phy_reg_write(mhdp, (TX_PSC_A2 | (k << 9)), 0x0098); + cdns_phy_reg_write(mhdp, (TX_PSC_A3 | (k << 9)), 0x0098); + /* Receiver calibration power state definition register */ + val = cdns_phy_reg_read(mhdp, RX_PSC_CAL | (k << 9)); + val &= 0xFFBB; + cdns_phy_reg_write(mhdp, (RX_PSC_CAL | (k << 9)), val); + val = cdns_phy_reg_read(mhdp, RX_PSC_A0 | (k << 9)); + val &= 0xFFBB; + cdns_phy_reg_write(mhdp, (RX_PSC_A0 | (k << 9)), val); + } +} + +static void dp_phy_power_down(struct cdns_mhdp_device *mhdp) +{ + u16 val; + int i; + + if (!mhdp->power_up) + return; + + /* Place the PHY lanes in the A3 power state. */ + cdns_phy_reg_write(mhdp, PHY_HDP_MODE_CTRL, 0x8); + /* Wait for Power State A3 Ack */ + for (i = 0; i < 10; i++) { + val = cdns_phy_reg_read(mhdp, PHY_HDP_MODE_CTRL); + if (val & (1 << 7)) + break; + } + if (i == 10) { + dev_err(mhdp->dev, "Wait A3 Ack failed\n"); + return; + } + DRM_DEBUG_DRIVER("Wait A3 Ack count - %d", i); + + /* Disable HDP PLL’s data rate and full rate clocks out of PMA. */ + val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL); + val &= ~(1 << 2); + cdns_phy_reg_write(mhdp, PHY_HDP_CLK_CTL, val); + /* Wait for PLL clock gate ACK */ + for (i = 0; i < 10; i++) { + val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL); + if (!(val & (1 << 3))) + break; + } + if (i == 10) { + dev_err(mhdp->dev, "Wait PLL clock gate Ack failed\n"); + return; + } + DRM_DEBUG_DRIVER("Wait PLL clock gate - %d", i); + + /* Disable HDP PLL’s for high speed clocks */ + val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL); + val &= ~(1 << 0); + cdns_phy_reg_write(mhdp, PHY_HDP_CLK_CTL, val); + /* Wait for PLL disable ACK */ + for (i = 0; i < 10; i++) { + val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL); + if (!(val & (1 << 1))) + break; + } + if (i == 10) { + dev_err(mhdp->dev, "Wait PLL disable Ack failed\n"); + return; + } + DRM_DEBUG_DRIVER("Wait PLL disable - %d", i); + +} + +int cdns_dp_phy_power_up(struct cdns_mhdp_device *mhdp) +{ + u32 val, i; + + /* Enable HDP PLL’s for high speed clocks */ + val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL); + val |= (1 << 0); + cdns_phy_reg_write(mhdp, PHY_HDP_CLK_CTL, val); + /* Wait for PLL ready ACK */ + for (i = 0; i < 10; i++) { + val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL); + if (val & (1 << 1)) + break; + msleep(20); + } + if (i == 10) { + dev_err(mhdp->dev, "Wait PLL Ack failed\n"); + return -1; + } + + /* Enable HDP PLL’s data rate and full rate clocks out of PMA. */ + val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL); + val |= (1 << 2); + cdns_phy_reg_write(mhdp, PHY_HDP_CLK_CTL, val); + /* Wait for PLL clock enable ACK */ + for (i = 0; i < 10; i++) { + val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL); + if (val & (1 << 3)) + break; + msleep(20); + } + if (i == 10) { + dev_err(mhdp->dev, "Wait PLL clock enable ACk failed\n"); + return -1; + } + + /* Configure PHY in A2 Mode */ + cdns_phy_reg_write(mhdp, PHY_HDP_MODE_CTRL, 0x0004); + /* Wait for Power State A2 Ack */ + for (i = 0; i < 10; i++) { + val = cdns_phy_reg_read(mhdp, PHY_HDP_MODE_CTRL); + if (val & (1 << 6)) + break; + msleep(20); + } + if (i == 10) { + dev_err(mhdp->dev, "Wait A2 Ack failed\n"); + return -1; + } + + /* Configure PHY in A0 mode (PHY must be in the A0 power + * state in order to transmit data) + */ + cdns_phy_reg_write(mhdp, PHY_HDP_MODE_CTRL, 0x0101); + + /* Wait for Power State A0 Ack */ + for (i = 0; i < 10; i++) { + val = cdns_phy_reg_read(mhdp, PHY_HDP_MODE_CTRL); + if (val & (1 << 4)) + break; + msleep(20); + } + if (i == 10) { + dev_err(mhdp->dev, "Wait A0 Ack failed\n"); + return -1; + } + + mhdp->power_up = true; + + return 0; +} + +int cdns_dp_phy_set_imx8mq(struct cdns_mhdp_device *mhdp) +{ + /* Disable phy clock if PHY in power up state */ + dp_phy_power_down(mhdp); + + dp_phy_pma_cmn_cfg_27mhz(mhdp); + + dp_phy_pma_cmn_pll0_27mhz(mhdp); + + cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_0, 1); + cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_1, 1); + cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_2, 1); + cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_3, 1); + + dp_aux_cfg(mhdp); + + return true; +} + +int cdns_dp_phy_set_imx8qm(struct cdns_mhdp_device *mhdp) +{ + /* Disable phy clock if PHY in power up state */ + dp_phy_power_down(mhdp); + + dp_phy_pma_cmn_cfg_24mhz(mhdp); + + dp_phy_pma_cmn_pll0_24mhz(mhdp); + + cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_0, 1); + cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_1, 1); + cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_2, 1); + cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_3, 1); + + dp_aux_cfg(mhdp); + + return true; +} + +int cdns_dp_phy_shutdown(struct cdns_mhdp_device *mhdp) +{ + dp_phy_power_down(mhdp); + DRM_DEBUG_DRIVER("dp phy shutdown complete\n"); + return 0; +} diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-hdmi-phy.c b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-hdmi-phy.c new file mode 100644 index 000000000000..dbcb14a6f98f --- /dev/null +++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-hdmi-phy.c @@ -0,0 +1,796 @@ +/* + * Cadence High-Definition Multimedia Interface (HDMI) driver + * + * Copyright 2019-2021 NXP + * + * 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. + * + */ +#include <drm/drm_of.h> +#include <drm/drm_vblank.h> +#include <drm/drm_print.h> +#include <drm/drm_crtc_helper.h> +#include <linux/io.h> +#include <drm/drm_edid.h> +#include <drm/drm_encoder_slave.h> +#include <drm/drm_atomic.h> +#include <linux/io.h> + +#include <drm/bridge/cdns-mhdp.h> +#include "cdns-mhdp-phy.h" +#include "cdns-mhdp-imx.h" + +/* HDMI TX clock control settings */ +struct hdmi_ctrl { + u32 pixel_clk_freq_min; + u32 pixel_clk_freq_max; + u32 feedback_factor; + u32 data_range_kbps_min; + u32 data_range_kbps_max; + u32 cmnda_pll0_ip_div; + u32 cmn_ref_clk_dig_div; + u32 ref_clk_divider_scaler; + u32 pll_fb_div_total; + u32 cmnda_pll0_fb_div_low; + u32 cmnda_pll0_fb_div_high; + u32 pixel_div_total; + u32 cmnda_pll0_pxdiv_low; + u32 cmnda_pll0_pxdiv_high; + u32 vco_freq_min; + u32 vco_freq_max; + u32 vco_ring_select; + u32 cmnda_hs_clk_0_sel; + u32 cmnda_hs_clk_1_sel; + u32 hsclk_div_at_xcvr; + u32 hsclk_div_tx_sub_rate; + u32 cmnda_pll0_hs_sym_div_sel; + u32 cmnda_pll0_clk_freq_min; + u32 cmnda_pll0_clk_freq_max; +}; + +/* HDMI TX clock control settings, pixel clock is output */ +static const struct hdmi_ctrl imx8mq_ctrl_table[] = { +/*Minclk Maxclk Fdbak DR_min DR_max ip_d dig DS Totl */ +{ 27000, 27000, 1000, 270000, 270000, 0x03, 0x1, 0x1, 240, 0x0BC, 0x030, 80, 0x026, 0x026, 2160000, 2160000, 0, 2, 2, 2, 4, 0x3, 27000, 27000}, +{ 27000, 27000, 1250, 337500, 337500, 0x03, 0x1, 0x1, 300, 0x0EC, 0x03C, 100, 0x030, 0x030, 2700000, 2700000, 0, 2, 2, 2, 4, 0x3, 33750, 33750}, +{ 27000, 27000, 1500, 405000, 405000, 0x03, 0x1, 0x1, 360, 0x11C, 0x048, 120, 0x03A, 0x03A, 3240000, 3240000, 0, 2, 2, 2, 4, 0x3, 40500, 40500}, +{ 27000, 27000, 2000, 540000, 540000, 0x03, 0x1, 0x1, 240, 0x0BC, 0x030, 80, 0x026, 0x026, 2160000, 2160000, 0, 2, 2, 2, 4, 0x2, 54000, 54000}, +{ 54000, 54000, 1000, 540000, 540000, 0x03, 0x1, 0x1, 480, 0x17C, 0x060, 80, 0x026, 0x026, 4320000, 4320000, 1, 2, 2, 2, 4, 0x3, 54000, 54000}, +{ 54000, 54000, 1250, 675000, 675000, 0x04, 0x1, 0x1, 400, 0x13C, 0x050, 50, 0x017, 0x017, 2700000, 2700000, 0, 1, 1, 2, 4, 0x2, 67500, 67500}, +{ 54000, 54000, 1500, 810000, 810000, 0x04, 0x1, 0x1, 480, 0x17C, 0x060, 60, 0x01C, 0x01C, 3240000, 3240000, 0, 2, 2, 2, 2, 0x2, 81000, 81000}, +{ 54000, 54000, 2000, 1080000, 1080000, 0x03, 0x1, 0x1, 240, 0x0BC, 0x030, 40, 0x012, 0x012, 2160000, 2160000, 0, 2, 2, 2, 1, 0x1, 108000, 108000}, +{ 74250, 74250, 1000, 742500, 742500, 0x03, 0x1, 0x1, 660, 0x20C, 0x084, 80, 0x026, 0x026, 5940000, 5940000, 1, 2, 2, 2, 4, 0x3, 74250, 74250}, +{ 74250, 74250, 1250, 928125, 928125, 0x04, 0x1, 0x1, 550, 0x1B4, 0x06E, 50, 0x017, 0x017, 3712500, 3712500, 1, 1, 1, 2, 4, 0x2, 92812, 92812}, +{ 74250, 74250, 1500, 1113750, 1113750, 0x04, 0x1, 0x1, 660, 0x20C, 0x084, 60, 0x01C, 0x01C, 4455000, 4455000, 1, 2, 2, 2, 2, 0x2, 111375, 111375}, +{ 74250, 74250, 2000, 1485000, 1485000, 0x03, 0x1, 0x1, 330, 0x104, 0x042, 40, 0x012, 0x012, 2970000, 2970000, 0, 2, 2, 2, 1, 0x1, 148500, 148500}, +{ 99000, 99000, 1000, 990000, 990000, 0x03, 0x1, 0x1, 440, 0x15C, 0x058, 40, 0x012, 0x012, 3960000, 3960000, 1, 2, 2, 2, 2, 0x2, 99000, 99000}, +{ 99000, 99000, 1250, 1237500, 1237500, 0x03, 0x1, 0x1, 275, 0x0D8, 0x037, 25, 0x00B, 0x00A, 2475000, 2475000, 0, 1, 1, 2, 2, 0x1, 123750, 123750}, +{ 99000, 99000, 1500, 1485000, 1485000, 0x03, 0x1, 0x1, 330, 0x104, 0x042, 30, 0x00D, 0x00D, 2970000, 2970000, 0, 2, 2, 2, 1, 0x1, 148500, 148500}, +{ 99000, 99000, 2000, 1980000, 1980000, 0x03, 0x1, 0x1, 440, 0x15C, 0x058, 40, 0x012, 0x012, 3960000, 3960000, 1, 2, 2, 2, 1, 0x1, 198000, 198000}, +{148500, 148500, 1000, 1485000, 1485000, 0x03, 0x1, 0x1, 660, 0x20C, 0x084, 40, 0x012, 0x012, 5940000, 5940000, 1, 2, 2, 2, 2, 0x2, 148500, 148500}, +{148500, 148500, 1250, 1856250, 1856250, 0x04, 0x1, 0x1, 550, 0x1B4, 0x06E, 25, 0x00B, 0x00A, 3712500, 3712500, 1, 1, 1, 2, 2, 0x1, 185625, 185625}, +{148500, 148500, 1500, 2227500, 2227500, 0x03, 0x1, 0x1, 495, 0x188, 0x063, 30, 0x00D, 0x00D, 4455000, 4455000, 1, 1, 1, 2, 2, 0x1, 222750, 222750}, +{148500, 148500, 2000, 2970000, 2970000, 0x03, 0x1, 0x1, 660, 0x20C, 0x084, 40, 0x012, 0x012, 5940000, 5940000, 1, 2, 2, 2, 1, 0x1, 297000, 297000}, +{198000, 198000, 1000, 1980000, 1980000, 0x03, 0x1, 0x1, 220, 0x0AC, 0x02C, 10, 0x003, 0x003, 1980000, 1980000, 0, 1, 1, 2, 1, 0x0, 198000, 198000}, +{198000, 198000, 1250, 2475000, 2475000, 0x03, 0x1, 0x1, 550, 0x1B4, 0x06E, 25, 0x00B, 0x00A, 4950000, 4950000, 1, 1, 1, 2, 2, 0x1, 247500, 247500}, +{198000, 198000, 1500, 2970000, 2970000, 0x03, 0x1, 0x1, 330, 0x104, 0x042, 15, 0x006, 0x005, 2970000, 2970000, 0, 1, 1, 2, 1, 0x0, 297000, 297000}, +{198000, 198000, 2000, 3960000, 3960000, 0x03, 0x1, 0x1, 440, 0x15C, 0x058, 20, 0x008, 0x008, 3960000, 3960000, 1, 1, 1, 2, 1, 0x0, 396000, 396000}, +{297000, 297000, 1000, 2970000, 2970000, 0x03, 0x1, 0x1, 330, 0x104, 0x042, 10, 0x003, 0x003, 2970000, 2970000, 0, 1, 1, 2, 1, 0x0, 297000, 297000}, +{297000, 297000, 1500, 4455000, 4455000, 0x03, 0x1, 0x1, 495, 0x188, 0x063, 15, 0x006, 0x005, 4455000, 4455000, 1, 1, 1, 2, 1, 0x0, 445500, 445500}, +{297000, 297000, 2000, 5940000, 5940000, 0x03, 0x1, 0x1, 660, 0x20C, 0x084, 20, 0x008, 0x008, 5940000, 5940000, 1, 1, 1, 2, 1, 0x0, 594000, 594000}, +{594000, 594000, 1000, 5940000, 5940000, 0x03, 0x1, 0x1, 660, 0x20C, 0x084, 10, 0x003, 0x003, 5940000, 5940000, 1, 1, 1, 2, 1, 0x0, 594000, 594000}, +{594000, 594000, 750, 4455000, 4455000, 0x03, 0x1, 0x1, 495, 0x188, 0x063, 10, 0x003, 0x003, 4455000, 4455000, 1, 1, 1, 2, 1, 0x0, 445500, 445500}, +{594000, 594000, 625, 3712500, 3712500, 0x04, 0x1, 0x1, 550, 0x1B4, 0x06E, 10, 0x003, 0x003, 3712500, 3712500, 1, 1, 1, 2, 1, 0x0, 371250, 371250}, +{594000, 594000, 500, 2970000, 2970000, 0x03, 0x1, 0x1, 660, 0x20C, 0x084, 10, 0x003, 0x003, 5940000, 5940000, 1, 1, 1, 2, 2, 0x1, 297000, 297000}, +}; + +/* HDMI TX clock control settings, pixel clock is input */ +static const struct hdmi_ctrl imx8qm_ctrl_table[] = { +/*pclk_l pclk_h fd DRR_L DRR_H PLLD */ +{ 25000, 42500, 1000, 250000, 425000, 0x05, 0x01, 0x01, 400, 0x182, 0x00A, 0, 0, 0, 2000000, 3400000, 0, 2, 2, 2, 4, 0x03, 25000, 42500}, +{ 42500, 85000, 1000, 425000, 850000, 0x08, 0x03, 0x01, 320, 0x132, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 4, 0x02, 42500, 85000}, +{ 85000, 170000, 1000, 850000, 1700000, 0x11, 0x00, 0x07, 340, 0x146, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 2, 0x01, 85000, 170000}, +{170000, 340000, 1000, 1700000, 3400000, 0x22, 0x01, 0x07, 340, 0x146, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 1, 0x00, 170000, 340000}, +{340000, 600000, 1000, 3400000, 6000000, 0x3C, 0x03, 0x06, 600, 0x24A, 0x00A, 0, 0, 0, 3400000, 6000000, 1, 1, 1, 2, 1, 0x00, 340000, 600000}, +{ 25000, 34000, 1250, 312500, 425000, 0x04, 0x01, 0x01, 400, 0x182, 0x00A, 0, 0, 0, 2500000, 3400000, 0, 2, 2, 2, 4, 0x03, 31250, 42500}, +{ 34000, 68000, 1250, 425000, 850000, 0x06, 0x02, 0x01, 300, 0x11E, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 4, 0x02, 42500, 85000}, +{ 68000, 136000, 1250, 850000, 1700000, 0x0D, 0x02, 0x02, 325, 0x137, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 2, 0x01, 85000, 170000}, +{136000, 272000, 1250, 1700000, 3400000, 0x1A, 0x02, 0x04, 325, 0x137, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 1, 0x00, 170000, 340000}, +{272000, 480000, 1250, 3400000, 6000000, 0x30, 0x03, 0x05, 600, 0x24A, 0x00A, 0, 0, 0, 3400000, 6000000, 1, 1, 1, 2, 1, 0x00, 340000, 600000}, +{ 25000, 28000, 1500, 375000, 420000, 0x03, 0x01, 0x01, 360, 0x15A, 0x00A, 0, 0, 0, 3000000, 3360000, 0, 2, 2, 2, 4, 0x03, 37500, 42000}, +{ 28000, 56000, 1500, 420000, 840000, 0x06, 0x02, 0x01, 360, 0x15A, 0x00A, 0, 0, 0, 1680000, 3360000, 0, 1, 1, 2, 4, 0x02, 42000, 84000}, +{ 56000, 113000, 1500, 840000, 1695000, 0x0B, 0x00, 0x05, 330, 0x13C, 0x00A, 0, 0, 0, 1680000, 3390000, 0, 1, 1, 2, 2, 0x01, 84000, 169500}, +{113000, 226000, 1500, 1695000, 3390000, 0x16, 0x01, 0x05, 330, 0x13C, 0x00A, 0, 0, 0, 1695000, 3390000, 0, 1, 1, 2, 1, 0x00, 169500, 339000}, +{226000, 400000, 1500, 3390000, 6000000, 0x28, 0x03, 0x04, 600, 0x24A, 0x00A, 0, 0, 0, 3390000, 6000000, 1, 1, 1, 2, 1, 0x00, 339000, 600000}, +{ 25000, 42500, 2000, 500000, 850000, 0x05, 0x01, 0x01, 400, 0x182, 0x00A, 0, 0, 0, 2000000, 3400000, 0, 1, 1, 2, 4, 0x02, 50000, 85000}, +{ 42500, 85000, 2000, 850000, 1700000, 0x08, 0x03, 0x01, 320, 0x132, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 2, 0x01, 85000, 170000}, +{ 85000, 170000, 2000, 1700000, 3400000, 0x11, 0x00, 0x07, 340, 0x146, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 1, 0x00, 170000, 340000}, +{170000, 300000, 2000, 3400000, 6000000, 0x22, 0x01, 0x06, 680, 0x29A, 0x00A, 0, 0, 0, 3400000, 6000000, 1, 1, 1, 2, 1, 0x00, 340000, 600000}, +{594000, 594000, 5000, 2970000, 2970000, 0x3C, 0x03, 0x06, 600, 0x24A, 0x00A, 0, 0, 0, 5940000, 5940000, 1, 1, 1, 2, 2, 0x01, 297000, 297000}, +{594000, 594000, 6250, 3712500, 3712500, 0x3C, 0x03, 0x06, 375, 0x169, 0x00A, 0, 0, 0, 3712500, 3712500, 1, 1, 1, 2, 1, 0x00, 371250, 371250}, +{594000, 594000, 7500, 4455000, 4455000, 0x3C, 0x03, 0x06, 450, 0x1B4, 0x00A, 0, 0, 0, 4455000, 4455000, 1, 1, 1, 2, 1, 0x00, 445500, 445500}, +}; + +/* HDMI TX PLL tuning settings */ +struct hdmi_pll_tuning { + u32 vco_freq_bin; + u32 vco_freq_min; + u32 vco_freq_max; + u32 volt_to_current_coarse; + u32 volt_to_current; + u32 ndac_ctrl; + u32 pmos_ctrl; + u32 ptat_ndac_ctrl; + u32 feedback_div_total; + u32 charge_pump_gain; + u32 coarse_code; + u32 v2i_code; + u32 vco_cal_code; +}; + +/* HDMI TX PLL tuning settings, pixel clock is output */ +static const struct hdmi_pll_tuning imx8mq_pll_table[] = { +/* bin VCO_freq min/max coar cod NDAC PMOS PTAT div-T P-Gain Coa V2I CAL */ + { 1, 1980000, 1980000, 0x4, 0x3, 0x0, 0x09, 0x09, 220, 0x42, 160, 5, 183 }, + { 2, 2160000, 2160000, 0x4, 0x3, 0x0, 0x09, 0x09, 240, 0x42, 166, 6, 208 }, + { 3, 2475000, 2475000, 0x5, 0x3, 0x1, 0x00, 0x07, 275, 0x42, 167, 6, 209 }, + { 4, 2700000, 2700000, 0x5, 0x3, 0x1, 0x00, 0x07, 300, 0x42, 188, 6, 230 }, + { 4, 2700000, 2700000, 0x5, 0x3, 0x1, 0x00, 0x07, 400, 0x4C, 188, 6, 230 }, + { 5, 2970000, 2970000, 0x6, 0x3, 0x1, 0x00, 0x07, 330, 0x42, 183, 6, 225 }, + { 6, 3240000, 3240000, 0x6, 0x3, 0x1, 0x00, 0x07, 360, 0x42, 203, 7, 256 }, + { 6, 3240000, 3240000, 0x6, 0x3, 0x1, 0x00, 0x07, 480, 0x4C, 203, 7, 256 }, + { 7, 3712500, 3712500, 0x4, 0x3, 0x0, 0x07, 0x0F, 550, 0x4C, 212, 7, 257 }, + { 8, 3960000, 3960000, 0x5, 0x3, 0x0, 0x07, 0x0F, 440, 0x42, 184, 6, 226 }, + { 9, 4320000, 4320000, 0x5, 0x3, 0x1, 0x07, 0x0F, 480, 0x42, 205, 7, 258 }, + { 10, 4455000, 4455000, 0x5, 0x3, 0x0, 0x07, 0x0F, 495, 0x42, 219, 7, 272 }, + { 10, 4455000, 4455000, 0x5, 0x3, 0x0, 0x07, 0x0F, 660, 0x4C, 219, 7, 272 }, + { 11, 4950000, 4950000, 0x6, 0x3, 0x1, 0x00, 0x07, 550, 0x42, 213, 7, 258 }, + { 12, 5940000, 5940000, 0x7, 0x3, 0x1, 0x00, 0x07, 660, 0x42, 244, 8, 292 }, +}; + +/* HDMI TX PLL tuning settings, pixel clock is input */ +static const struct hdmi_pll_tuning imx8qm_pll_table[] = { +/* bin VCO_freq min/max coar cod NDAC PMOS PTAT div-T P-Gain pad only */ + { 0, 1700000, 2000000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 300, 0x08D, 0, 0, 0 }, + { 0, 1700000, 2000000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 320, 0x08E, 0, 0, 0 }, + { 0, 1700000, 2000000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 325, 0x08E, 0, 0, 0 }, + { 0, 1700000, 2000000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 330, 0x08E, 0, 0, 0 }, + { 0, 1700000, 2000000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 340, 0x08F, 0, 0, 0 }, + { 0, 1700000, 2000000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 360, 0x0A7, 0, 0, 0 }, + { 0, 1700000, 2000000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 400, 0x0C5, 0, 0, 0 }, + { 1, 2000000, 2400000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 300, 0x086, 0, 0, 0 }, + { 1, 2000000, 2400000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 320, 0x087, 0, 0, 0 }, + { 1, 2000000, 2400000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 325, 0x087, 0, 0, 0 }, + { 1, 2000000, 2400000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 330, 0x104, 0, 0, 0 }, + { 1, 2000000, 2400000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 340, 0x08B, 0, 0, 0 }, + { 1, 2000000, 2400000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 360, 0x08D, 0, 0, 0 }, + { 1, 2000000, 2400000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 400, 0x0A6, 0, 0, 0 }, + { 2, 2400000, 2800000, 0x3, 0x1, 0x0, 0x04, 0x0D, 300, 0x04E, 0, 0, 0 }, + { 2, 2400000, 2800000, 0x3, 0x1, 0x0, 0x04, 0x0D, 320, 0x04F, 0, 0, 0 }, + { 2, 2400000, 2800000, 0x3, 0x1, 0x0, 0x04, 0x0D, 325, 0x04F, 0, 0, 0 }, + { 2, 2400000, 2800000, 0x3, 0x1, 0x0, 0x04, 0x0D, 330, 0x085, 0, 0, 0 }, + { 2, 2400000, 2800000, 0x3, 0x1, 0x0, 0x04, 0x0D, 340, 0x085, 0, 0, 0 }, + { 2, 2400000, 2800000, 0x3, 0x1, 0x0, 0x04, 0x0D, 360, 0x086, 0, 0, 0 }, + { 2, 2400000, 2800000, 0x3, 0x1, 0x0, 0x04, 0x0D, 400, 0x08B, 0, 0, 0 }, + { 3, 2800000, 3400000, 0x3, 0x1, 0x0, 0x04, 0x0D, 300, 0x047, 0, 0, 0 }, + { 3, 2800000, 3400000, 0x3, 0x1, 0x0, 0x04, 0x0D, 320, 0x04B, 0, 0, 0 }, + { 3, 2800000, 3400000, 0x3, 0x1, 0x0, 0x04, 0x0D, 325, 0x04B, 0, 0, 0 }, + { 3, 2800000, 3400000, 0x3, 0x1, 0x0, 0x04, 0x0D, 330, 0x04B, 0, 0, 0 }, + { 3, 2800000, 3400000, 0x3, 0x1, 0x0, 0x04, 0x0D, 340, 0x04D, 0, 0, 0 }, + { 3, 2800000, 3400000, 0x3, 0x1, 0x0, 0x04, 0x0D, 360, 0x04E, 0, 0, 0 }, + { 3, 2800000, 3400000, 0x3, 0x1, 0x0, 0x04, 0x0D, 400, 0x085, 0, 0, 0 }, + { 4, 3400000, 3900000, 0x7, 0x1, 0x0, 0x8E, 0x2F, 375, 0x041, 0, 0, 0 }, + { 4, 3400000, 3900000, 0x7, 0x1, 0x0, 0x8E, 0x2F, 600, 0x08D, 0, 0, 0 }, + { 4, 3400000, 3900000, 0x7, 0x1, 0x0, 0x8E, 0x2F, 680, 0x0A6, 0, 0, 0 }, + { 5, 3900000, 4500000, 0x7, 0x1, 0x0, 0x8E, 0x2F, 450, 0x041, 0, 0, 0 }, + { 5, 3900000, 4500000, 0x7, 0x1, 0x0, 0x8E, 0x2F, 600, 0x087, 0, 0, 0 }, + { 5, 3900000, 4500000, 0x7, 0x1, 0x0, 0x8E, 0x2F, 680, 0x0A4, 0, 0, 0 }, + { 6, 4500000, 5200000, 0x7, 0x1, 0x0, 0x04, 0x0D, 600, 0x04F, 0, 0, 0 }, + { 6, 4500000, 5200000, 0x7, 0x1, 0x0, 0x04, 0x0D, 680, 0x086, 0, 0, 0 }, + { 7, 5200000, 6000000, 0x7, 0x1, 0x0, 0x04, 0x0D, 600, 0x04D, 0, 0, 0 }, + { 7, 5200000, 6000000, 0x7, 0x1, 0x0, 0x04, 0x0D, 680, 0x04F, 0, 0, 0 } +}; + +static void hdmi_arc_config(struct cdns_mhdp_device *mhdp) +{ + u16 txpu_calib_code; + u16 txpd_calib_code; + u16 txpu_adj_calib_code; + u16 txpd_adj_calib_code; + u16 prev_calib_code; + u16 new_calib_code; + u16 rdata; + + /* Power ARC */ + cdns_phy_reg_write(mhdp, TXDA_CYA_AUXDA_CYA, 0x0001); + + prev_calib_code = cdns_phy_reg_read(mhdp, TX_DIG_CTRL_REG_2); + txpu_calib_code = cdns_phy_reg_read(mhdp, CMN_TXPUCAL_CTRL); + txpd_calib_code = cdns_phy_reg_read(mhdp, CMN_TXPDCAL_CTRL); + txpu_adj_calib_code = cdns_phy_reg_read(mhdp, CMN_TXPU_ADJ_CTRL); + txpd_adj_calib_code = cdns_phy_reg_read(mhdp, CMN_TXPD_ADJ_CTRL); + + new_calib_code = ((txpu_calib_code + txpd_calib_code) / 2) + + txpu_adj_calib_code + txpd_adj_calib_code; + + if (new_calib_code != prev_calib_code) { + rdata = cdns_phy_reg_read(mhdp, TX_ANA_CTRL_REG_1); + rdata &= 0xDFFF; + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, rdata); + cdns_phy_reg_write(mhdp, TX_DIG_CTRL_REG_2, new_calib_code); + mdelay(10); + rdata |= 0x2000; + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, rdata); + udelay(150); + } + + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x0100); + udelay(100); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x0300); + udelay(100); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_3, 0x0000); + udelay(100); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0x2008); + udelay(100); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0x2018); + udelay(100); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0x2098); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x030C); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_5, 0x0010); + udelay(100); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_4, 0x4001); + mdelay(5); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0x2198); + mdelay(5); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x030D); + udelay(100); + cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x030F); +} + +static void hdmi_phy_set_vswing(struct cdns_mhdp_device *mhdp) +{ + const u32 num_lanes = 4; + u32 k; + + for (k = 0; k < num_lanes; k++) { + cdns_phy_reg_write(mhdp, (TX_DIAG_TX_DRV | (k << 9)), 0x7c0); + cdns_phy_reg_write(mhdp, (TX_TXCC_CPOST_MULT_00_0 | (k << 9)), 0x0); + cdns_phy_reg_write(mhdp, (TX_TXCC_CAL_SCLR_MULT_0 | (k << 9)), 0x120); + } +} + +static int hdmi_feedback_factor(struct cdns_mhdp_device *mhdp) +{ + u32 feedback_factor; + + switch (mhdp->video_info.color_fmt) { + case YCBCR_4_2_2: + feedback_factor = 1000; + break; + case YCBCR_4_2_0: + switch (mhdp->video_info.color_depth) { + case 8: + feedback_factor = 500; + break; + case 10: + feedback_factor = 625; + break; + case 12: + feedback_factor = 750; + break; + case 16: + feedback_factor = 1000; + break; + default: + DRM_ERROR("Invalid ColorDepth\n"); + return 0; + } + break; + default: + /* Assume RGB/YUV444 */ + switch (mhdp->video_info.color_depth) { + case 10: + feedback_factor = 1250; + break; + case 12: + feedback_factor = 1500; + break; + case 16: + feedback_factor = 2000; + break; + default: + feedback_factor = 1000; + } + } + return feedback_factor; +} + +static int hdmi_phy_config(struct cdns_mhdp_device *mhdp, + const struct hdmi_ctrl *p_ctrl_table, + const struct hdmi_pll_tuning *p_pll_table, + char pclk_in) +{ + const u32 num_lanes = 4; + u32 val, i, k; + + /* enable PHY isolation mode only for CMN */ + cdns_phy_reg_write(mhdp, PHY_PMA_ISOLATION_CTRL, 0xD000); + + /* set cmn_pll0_clk_datart1_div/cmn_pll0_clk_datart0_div dividers */ + val = cdns_phy_reg_read(mhdp, PHY_PMA_ISO_PLL_CTRL1); + val &= 0xFF00; + val |= 0x0012; + cdns_phy_reg_write(mhdp, PHY_PMA_ISO_PLL_CTRL1, val); + + /* assert PHY reset from isolation register */ + cdns_phy_reg_write(mhdp, PHY_ISO_CMN_CTRL, 0x0000); + /* assert PMA CMN reset */ + cdns_phy_reg_write(mhdp, PHY_PMA_ISO_CMN_CTRL, 0x0000); + + /* register XCVR_DIAG_BIDI_CTRL */ + for (k = 0; k < num_lanes; k++) + cdns_phy_reg_write(mhdp, XCVR_DIAG_BIDI_CTRL | (k << 9), 0x00FF); + + /* Describing Task phy_cfg_hdp */ + + val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1); + val &= 0xFFF7; + val |= 0x0008; + cdns_phy_reg_write(mhdp, PHY_PMA_CMN_CTRL1, val); + + /* PHY Registers */ + val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1); + val &= 0xCFFF; + val |= p_ctrl_table->cmn_ref_clk_dig_div << 12; + cdns_phy_reg_write(mhdp, PHY_PMA_CMN_CTRL1, val); + + val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL); + val &= 0x00FF; + val |= 0x1200; + cdns_phy_reg_write(mhdp, PHY_HDP_CLK_CTL, val); + + /* Common control module control and diagnostic registers */ + val = cdns_phy_reg_read(mhdp, CMN_CDIAG_REFCLK_CTRL); + val &= 0x8FFF; + val |= p_ctrl_table->ref_clk_divider_scaler << 12; + val |= 0x00C0; + cdns_phy_reg_write(mhdp, CMN_CDIAG_REFCLK_CTRL, val); + + /* High speed clock used */ + val = cdns_phy_reg_read(mhdp, CMN_DIAG_HSCLK_SEL); + val &= 0xFF00; + val |= (p_ctrl_table->cmnda_hs_clk_0_sel >> 1) << 0; + val |= (p_ctrl_table->cmnda_hs_clk_1_sel >> 1) << 4; + cdns_phy_reg_write(mhdp, CMN_DIAG_HSCLK_SEL, val); + + for (k = 0; k < num_lanes; k++) { + val = cdns_phy_reg_read(mhdp, (XCVR_DIAG_HSCLK_SEL | (k << 9))); + val &= 0xCFFF; + val |= (p_ctrl_table->cmnda_hs_clk_0_sel >> 1) << 12; + cdns_phy_reg_write(mhdp, (XCVR_DIAG_HSCLK_SEL | (k << 9)), val); + } + + /* PLL 0 control state machine registers */ + val = p_ctrl_table->vco_ring_select << 12; + cdns_phy_reg_write(mhdp, CMN_PLLSM0_USER_DEF_CTRL, val); + + if (pclk_in == true) + val = 0x30A0; + else { + val = cdns_phy_reg_read(mhdp, CMN_PLL0_VCOCAL_START); + val &= 0xFE00; + val |= p_pll_table->vco_cal_code; + } + cdns_phy_reg_write(mhdp, CMN_PLL0_VCOCAL_START, val); + + cdns_phy_reg_write(mhdp, CMN_PLL0_VCOCAL_INIT_TMR, 0x0064); + cdns_phy_reg_write(mhdp, CMN_PLL0_VCOCAL_ITER_TMR, 0x000A); + + /* Common functions control and diagnostics registers */ + val = p_ctrl_table->cmnda_pll0_hs_sym_div_sel << 8; + val |= p_ctrl_table->cmnda_pll0_ip_div; + cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_INCLK_CTRL, val); + + cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_OVRD, 0x0000); + + val = p_ctrl_table->cmnda_pll0_fb_div_high; + val |= (1 << 15); + cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_FBH_OVRD, val); + + val = p_ctrl_table->cmnda_pll0_fb_div_low; + val |= (1 << 15); + cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_FBL_OVRD, val); + + if (pclk_in == false) { + val = p_ctrl_table->cmnda_pll0_pxdiv_low; + cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_PXL_DIVL, val); + + val = p_ctrl_table->cmnda_pll0_pxdiv_high; + val |= (1 << 15); + cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_PXL_DIVH, val); + } + + val = p_pll_table->volt_to_current_coarse; + val |= (p_pll_table->volt_to_current) << 4; + cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_V2I_TUNE, val); + + val = p_pll_table->charge_pump_gain; + cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_CP_TUNE, val); + + cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_LF_PROG, 0x0008); + + val = p_pll_table->pmos_ctrl; + val |= (p_pll_table->ndac_ctrl) << 8; + cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_PTATIS_TUNE1, val); + + val = p_pll_table->ptat_ndac_ctrl; + cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_PTATIS_TUNE2, val); + + if (pclk_in == true) + cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_TEST_MODE, 0x0022); + else + cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_TEST_MODE, 0x0020); + cdns_phy_reg_write(mhdp, CMN_PSM_CLK_CTRL, 0x0016); + + /* Transceiver control and diagnostic registers */ + for (k = 0; k < num_lanes; k++) { + val = cdns_phy_reg_read(mhdp, (XCVR_DIAG_PLLDRC_CTRL | (k << 9))); + val &= 0xBFFF; + cdns_phy_reg_write(mhdp, (XCVR_DIAG_PLLDRC_CTRL | (k << 9)), val); + } + + for (k = 0; k < num_lanes; k++) { + val = cdns_phy_reg_read(mhdp, (TX_DIAG_TX_CTRL | (k << 9))); + val &= 0xFF3F; + val |= (p_ctrl_table->hsclk_div_tx_sub_rate >> 1) << 6; + cdns_phy_reg_write(mhdp, (TX_DIAG_TX_CTRL | (k << 9)), val); + } + + /* + * for single ended reference clock val |= 0x0030; + * for differential clock val |= 0x0000; + */ + val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1); + val &= 0xFF8F; + if (pclk_in == true) + val |= 0x0030; + cdns_phy_reg_write(mhdp, PHY_PMA_CMN_CTRL1, val); + + /* for differential clock on the refclk_p and + * refclk_m off chip pins: CMN_DIAG_ACYA[8]=1'b1 */ + cdns_phy_reg_write(mhdp, CMN_DIAG_ACYA, 0x0100); + + /* Deassert PHY reset */ + cdns_phy_reg_write(mhdp, PHY_ISO_CMN_CTRL, 0x0001); + cdns_phy_reg_write(mhdp, PHY_PMA_ISO_CMN_CTRL, 0x0003); + + /* Power state machine registers */ + for (k = 0; k < num_lanes; k++) + cdns_phy_reg_write(mhdp, XCVR_PSM_RCTRL | (k << 9), 0xFEFC); + + /* Assert cmn_macro_pwr_en */ + cdns_phy_reg_write(mhdp, PHY_PMA_ISO_CMN_CTRL, 0x0013); + + /* wait for cmn_macro_pwr_en_ack */ + for (i = 0; i < 10; i++) { + val = cdns_phy_reg_read(mhdp, PHY_PMA_ISO_CMN_CTRL); + if (val & (1 << 5)) + break; + msleep(20); + } + if (i == 10) { + DRM_ERROR("PMA ouput macro power up failed\n"); + return false; + } + + /* wait for cmn_ready */ + for (i = 0; i < 10; i++) { + val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1); + if (val & (1 << 0)) + break; + msleep(20); + } + if (i == 10) { + DRM_ERROR("PMA output ready failed\n"); + return false; + } + + for (k = 0; k < num_lanes; k++) { + cdns_phy_reg_write(mhdp, TX_PSC_A0 | (k << 9), 0x6791); + cdns_phy_reg_write(mhdp, TX_PSC_A1 | (k << 9), 0x6790); + cdns_phy_reg_write(mhdp, TX_PSC_A2 | (k << 9), 0x0090); + cdns_phy_reg_write(mhdp, TX_PSC_A3 | (k << 9), 0x0090); + + val = cdns_phy_reg_read(mhdp, RX_PSC_CAL | (k << 9)); + val &= 0xFFBB; + cdns_phy_reg_write(mhdp, RX_PSC_CAL | (k << 9), val); + + val = cdns_phy_reg_read(mhdp, RX_PSC_A0 | (k << 9)); + val &= 0xFFBB; + cdns_phy_reg_write(mhdp, RX_PSC_A0 | (k << 9), val); + } + return true; +} + +static int hdmi_phy_cfg_t28hpc(struct cdns_mhdp_device *mhdp, + struct drm_display_mode *mode) +{ + const struct hdmi_ctrl *p_ctrl_table; + const struct hdmi_pll_tuning *p_pll_table; + const u32 refclk_freq_khz = 27000; + const u8 pclk_in = false; + u32 pixel_freq = mode->clock; + u32 vco_freq, char_freq; + u32 div_total, feedback_factor; + u32 i, ret; + + feedback_factor = hdmi_feedback_factor(mhdp); + + char_freq = pixel_freq * feedback_factor / 1000; + + DRM_INFO("Pixel clock: %d KHz, character clock: %d, bpc is %0d-bit, fmt %d\n", + pixel_freq, char_freq, mhdp->video_info.color_depth, mhdp->video_info.color_fmt); + + /* Get right row from the ctrl_table table. + * Check if 'pixel_freq_khz' value matches the PIXEL_CLK_FREQ column. + * Consider only the rows with FEEDBACK_FACTOR column matching feedback_factor. */ + for (i = 0; i < ARRAY_SIZE(imx8mq_ctrl_table); i++) { + if (feedback_factor == imx8mq_ctrl_table[i].feedback_factor && + pixel_freq == imx8mq_ctrl_table[i].pixel_clk_freq_min) { + p_ctrl_table = &imx8mq_ctrl_table[i]; + break; + } + } + if (i == ARRAY_SIZE(imx8mq_ctrl_table)) { + DRM_WARN("Pixel clk (%d KHz) not supported, color depth (%0d-bit)\n", + pixel_freq, mhdp->video_info.color_depth); + return 0; + } + + div_total = p_ctrl_table->pll_fb_div_total; + vco_freq = refclk_freq_khz * div_total / p_ctrl_table->cmnda_pll0_ip_div; + + /* Get right row from the imx8mq_pll_table table. + * Check if vco_freq_khz and feedback_div_total + * column matching with imx8mq_pll_table. */ + for (i = 0; i < ARRAY_SIZE(imx8mq_pll_table); i++) { + if (vco_freq == imx8mq_pll_table[i].vco_freq_min && + div_total == imx8mq_pll_table[i].feedback_div_total) { + p_pll_table = &imx8mq_pll_table[i]; + break; + } + } + if (i == ARRAY_SIZE(imx8mq_pll_table)) { + DRM_WARN("VCO (%d KHz) not supported\n", vco_freq); + return 0; + } + DRM_INFO("VCO frequency is %d KHz\n", vco_freq); + + ret = hdmi_phy_config(mhdp, p_ctrl_table, p_pll_table, pclk_in); + if (ret == false) + return 0; + + return char_freq; +} + +static int hdmi_phy_cfg_ss28fdsoi(struct cdns_mhdp_device *mhdp, + struct drm_display_mode *mode) +{ + const struct hdmi_ctrl *p_ctrl_table; + const struct hdmi_pll_tuning *p_pll_table; + const u8 pclk_in = true; + u32 pixel_freq = mode->clock; + u32 vco_freq, char_freq; + u32 div_total, feedback_factor; + u32 ret, i; + + feedback_factor = hdmi_feedback_factor(mhdp); + + char_freq = pixel_freq * feedback_factor / 1000; + + DRM_INFO("Pixel clock: %d KHz, character clock: %d, bpc is %0d-bit.\n", + pixel_freq, char_freq, mhdp->video_info.color_depth); + + /* Get right row from the ctrl_table table. + * Check if 'pixel_freq_khz' value matches the PIXEL_CLK_FREQ column. + * Consider only the rows with FEEDBACK_FACTOR column matching feedback_factor. */ + for (i = 0; i < ARRAY_SIZE(imx8qm_ctrl_table); i++) { + if (feedback_factor == imx8qm_ctrl_table[i].feedback_factor && + pixel_freq >= imx8qm_ctrl_table[i].pixel_clk_freq_min && + pixel_freq <= imx8qm_ctrl_table[i].pixel_clk_freq_max) { + p_ctrl_table = &imx8qm_ctrl_table[i]; + break; + } + } + if (i == ARRAY_SIZE(imx8qm_ctrl_table)) { + DRM_WARN("Pixel clk (%d KHz) not supported, color depth (%0d-bit)\n", + pixel_freq, mhdp->video_info.color_depth); + return 0; + } + + div_total = p_ctrl_table->pll_fb_div_total; + vco_freq = pixel_freq * div_total / p_ctrl_table->cmnda_pll0_ip_div; + + /* Get right row from the imx8mq_pll_table table. + * Check if vco_freq_khz and feedback_div_total + * column matching with imx8mq_pll_table. */ + for (i = 0; i < ARRAY_SIZE(imx8qm_pll_table); i++) { + if (vco_freq >= imx8qm_pll_table[i].vco_freq_min && + vco_freq < imx8qm_pll_table[i].vco_freq_max && + div_total == imx8qm_pll_table[i].feedback_div_total) { + p_pll_table = &imx8qm_pll_table[i]; + break; + } + } + if (i == ARRAY_SIZE(imx8qm_pll_table)) { + DRM_WARN("VCO (%d KHz) not supported\n", vco_freq); + return 0; + } + DRM_INFO("VCO frequency is %d KHz\n", vco_freq); + + ret = hdmi_phy_config(mhdp, p_ctrl_table, p_pll_table, pclk_in); + if (ret == false) + return 0; + + return char_freq; +} + +static int hdmi_arc_power_up(struct cdns_mhdp_device *mhdp) +{ + u32 val, i; + + /* set Power State to A2 */ + cdns_phy_reg_write(mhdp, PHY_HDP_MODE_CTRL, 0x0004); + + cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_0, 1); + cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_1, 1); + cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_2, 1); + cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_3, 1); + + /* Wait for Power State A2 Ack */ + for (i = 0; i < 10; i++) { + val = cdns_phy_reg_read(mhdp, PHY_HDP_MODE_CTRL); + if (val & (1 << 6)) + break; + msleep(20); + } + if (i == 10) { + dev_err(mhdp->dev, "Wait A2 Ack failed\n"); + return -1; + } + + /* Power up ARC */ + hdmi_arc_config(mhdp); + + return 0; +} + +bool cdns_hdmi_phy_video_valid_imx8mq(struct cdns_mhdp_device *mhdp) +{ + u32 rate = mhdp->valid_mode->clock; + int i; + + for (i = 0; i < ARRAY_SIZE(imx8mq_ctrl_table); i++) + if(rate == imx8mq_ctrl_table[i].pixel_clk_freq_min) + return true; + return false; +} + +int cdns_hdmi_phy_set_imx8mq(struct cdns_mhdp_device *mhdp) +{ + struct drm_display_mode *mode = &mhdp->mode; + int ret; + + /* Check HDMI FW alive before HDMI PHY init */ + ret = cdns_mhdp_check_alive(mhdp); + if (ret == false) { + DRM_ERROR("NO HDMI FW running\n"); + return -ENXIO; + } + + /* Configure PHY */ + mhdp->hdmi.char_rate = hdmi_phy_cfg_t28hpc(mhdp, mode); + if (mhdp->hdmi.char_rate == 0) { + DRM_ERROR("failed to set phy pclock\n"); + return -EINVAL; + } + + ret = hdmi_arc_power_up(mhdp); + if (ret < 0) + return ret; + + hdmi_phy_set_vswing(mhdp); + + return true; +} + +bool cdns_hdmi_phy_video_valid_imx8qm(struct cdns_mhdp_device *mhdp) +{ + u32 rate = mhdp->valid_mode->clock; + int i; + + for (i = 0; i < ARRAY_SIZE(imx8qm_ctrl_table); i++) + if(rate >= imx8qm_ctrl_table[i].pixel_clk_freq_min && + rate <= imx8qm_ctrl_table[i].pixel_clk_freq_max) + return true; + return false; +} + +int cdns_hdmi_phy_set_imx8qm(struct cdns_mhdp_device *mhdp) +{ + struct drm_display_mode *mode = &mhdp->mode; + int ret; + + /* Check HDMI FW alive before HDMI PHY init */ + ret = cdns_mhdp_check_alive(mhdp); + if (ret == false) { + DRM_ERROR("NO HDMI FW running\n"); + return -ENXIO; + } + imx8qm_phy_reset(0); + + /* Configure PHY */ + mhdp->hdmi.char_rate = hdmi_phy_cfg_ss28fdsoi(mhdp, mode); + if (mhdp->hdmi.char_rate == 0) { + DRM_ERROR("failed to set phy pclock\n"); + return -EINVAL; + } + imx8qm_phy_reset(1); + + ret = hdmi_arc_power_up(mhdp); + if (ret < 0) + return ret; + + hdmi_phy_set_vswing(mhdp); + + return true; +} + +int cdns_hdmi_phy_power_up(struct cdns_mhdp_device *mhdp) +{ + u32 val, i; + + /* Configure PHY in A0 mode (PHY must be in the A0 power + * state in order to transmit data) + */ + cdns_phy_reg_write(mhdp, PHY_HDP_MODE_CTRL, 0x0001); + + /* Wait for Power State A0 Ack */ + for (i = 0; i < 10; i++) { + val = cdns_phy_reg_read(mhdp, PHY_HDP_MODE_CTRL); + if (val & (1 << 4)) + break; + msleep(20); + } + if (i == 10) { + dev_err(mhdp->dev, "Wait A0 Ack failed\n"); + return -1; + } + return 0; +} + +int cdns_hdmi_phy_shutdown(struct cdns_mhdp_device *mhdp) +{ + int timeout; + u32 reg_val; + + reg_val = cdns_phy_reg_read(mhdp, PHY_HDP_MODE_CTRL); + reg_val &= 0xfff0; + /* PHY_DP_MODE_CTL set to A3 power state*/ + cdns_phy_reg_write(mhdp, PHY_HDP_MODE_CTRL, reg_val | 0x8); + + /* PHY_DP_MODE_CTL */ + timeout = 0; + do { + reg_val = cdns_phy_reg_read(mhdp, PHY_HDP_MODE_CTRL); + DRM_INFO("Reg val is 0x%04x\n", reg_val); + timeout++; + msleep(100); + } while (!(reg_val & (0x8 << 4)) && (timeout < 10)); /* Wait for A3 acknowledge */ + + DRM_INFO("hdmi phy shutdown complete\n"); + return 0; +} diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx.h b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx.h new file mode 100644 index 000000000000..e5ec3cc4073e --- /dev/null +++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx.h @@ -0,0 +1,76 @@ +/* + * Cadence High-Definition Multimedia Interface (HDMI) driver + * + * Copyright 2019-2021 NXP + * + * 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. + * + */ +#ifndef CDNS_MHDP_IMX_H_ +#define CDNS_MHDP_IMX_H_ + +#include <drm/bridge/cdns-mhdp.h> +#include <drm/drm_encoder_slave.h> + + +struct imx_mhdp_device; + +struct imx_hdp_clks { + struct clk *av_pll; + struct clk *dig_pll; + struct clk *clk_ipg; + struct clk *clk_core; + struct clk *clk_pxl; + struct clk *clk_pxl_mux; + struct clk *clk_pxl_link; + + struct clk *lpcg_hdp; + struct clk *lpcg_msi; + struct clk *lpcg_pxl; + struct clk *lpcg_vif; + struct clk *lpcg_lis; + struct clk *lpcg_apb; + struct clk *lpcg_apb_csr; + struct clk *lpcg_apb_ctrl; + + struct clk *lpcg_i2s; + struct clk *clk_i2s_bypass; +}; + +struct imx_mhdp_device { + struct cdns_mhdp_device mhdp; + struct drm_encoder encoder; + + struct mutex audio_mutex; + spinlock_t audio_lock; + bool connected; + bool active; + bool suspended; + struct imx_hdp_clks clks; + const struct firmware *fw; + const char *firmware_name; + + int bus_type; + + struct device *pd_mhdp_dev; + struct device *pd_pll0_dev; + struct device *pd_pll1_dev; + struct device_link *pd_mhdp_link; + struct device_link *pd_pll0_link; + struct device_link *pd_pll1_link; +}; + +void cdns_mhdp_plat_init_imx8qm(struct cdns_mhdp_device *mhdp); +void cdns_mhdp_plat_deinit_imx8qm(struct cdns_mhdp_device *mhdp); +void cdns_mhdp_pclk_rate_imx8qm(struct cdns_mhdp_device *mhdp); +int cdns_mhdp_firmware_init_imx8qm(struct cdns_mhdp_device *mhdp); +int cdns_mhdp_resume_imx8qm(struct cdns_mhdp_device *mhdp); +int cdns_mhdp_suspend_imx8qm(struct cdns_mhdp_device *mhdp); +int cdns_mhdp_power_on_imx8qm(struct cdns_mhdp_device *mhdp); +int cdns_mhdp_power_on_ls1028a(struct cdns_mhdp_device *mhdp); +void cdns_mhdp_pclk_rate_ls1028a(struct cdns_mhdp_device *mhdp); +void imx8qm_phy_reset(u8 reset); +#endif /* CDNS_MHDP_IMX_H_ */ diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx8qm.c b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx8qm.c new file mode 100644 index 000000000000..4fca888c3991 --- /dev/null +++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx8qm.c @@ -0,0 +1,642 @@ +/* + * Copyright 2019-2022 NXP + * + * this program is free software; you can redistribute it and/or modify + * it under the terms of the gnu general public license version 2 as + * published by the free software foundation. + */ +#include <dt-bindings/firmware/imx/rsrc.h> +#include <linux/firmware/imx/sci.h> +#include <linux/firmware.h> +#include <linux/pm_domain.h> +#include <linux/clk.h> +#include <drm/drm_vblank.h> +#include <drm/drm_print.h> + +#include "cdns-mhdp-imx.h" + +#define FW_IRAM_OFFSET 0x2000 +#define FW_IRAM_SIZE 0x10000 +#define FW_DRAM_SIZE 0x8000 + +#define PLL_800MHZ (800000000) + +#define HDP_DUAL_MODE_MIN_PCLK_RATE 300000 /* KHz */ +#define HDP_SINGLE_MODE_MAX_WIDTH 2560 + +#define CSR_PIXEL_LINK_MUX_CTL 0x00 +#define CSR_PIXEL_LINK_MUX_VCP_OFFSET 5 +#define CSR_PIXEL_LINK_MUX_HCP_OFFSET 4 + +static bool imx8qm_video_dual_mode(struct cdns_mhdp_device *mhdp) +{ + struct drm_display_mode *mode = &mhdp->mode; + return (mode->clock > HDP_DUAL_MODE_MIN_PCLK_RATE || + mode->hdisplay > HDP_SINGLE_MODE_MAX_WIDTH) ? true : false; +} + +static void imx8qm_pixel_link_mux(struct imx_mhdp_device *imx_mhdp) +{ + struct drm_display_mode *mode = &imx_mhdp->mhdp.mode; + bool dual_mode; + u32 val; + + dual_mode = imx8qm_video_dual_mode(&imx_mhdp->mhdp); + + val = 0x4; /* RGB */ + if (dual_mode) + val |= 0x2; /* pixel link 0 and 1 are active */ + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + val |= 1 << CSR_PIXEL_LINK_MUX_VCP_OFFSET; + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + val |= 1 << CSR_PIXEL_LINK_MUX_HCP_OFFSET; + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + val |= 0x2; + + writel(val, imx_mhdp->mhdp.regs_sec); +} + +static void imx8qm_pixel_link_valid(u32 dual_mode) +{ + struct imx_sc_ipc *handle; + + imx_scu_get_handle(&handle); + + imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_PXL_LINK_MST1_VLD, 1); + if (dual_mode) + imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_PXL_LINK_MST2_VLD, 1); +} + +static void imx8qm_pixel_link_invalid(u32 dual_mode) +{ + struct imx_sc_ipc *handle; + + imx_scu_get_handle(&handle); + + imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_PXL_LINK_MST1_VLD, 0); + if (dual_mode) + imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_PXL_LINK_MST2_VLD, 0); +} + +static void imx8qm_pixel_link_sync_enable(u32 dual_mode) +{ + struct imx_sc_ipc *handle; + + imx_scu_get_handle(&handle); + + if (dual_mode) + imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_SYNC_CTRL, 3); + else + imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_SYNC_CTRL0, 1); +} + +static void imx8qm_pixel_link_sync_disable(u32 dual_mode) +{ + struct imx_sc_ipc *handle; + + imx_scu_get_handle(&handle); + + if (dual_mode) + imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_SYNC_CTRL, 0); + else + imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_SYNC_CTRL0, 0); +} + +void imx8qm_phy_reset(u8 reset) +{ + struct imx_sc_ipc *handle; + + imx_scu_get_handle(&handle); + + /* set the pixel link mode and pixel type */ + imx_sc_misc_set_control(handle, IMX_SC_R_HDMI, IMX_SC_C_PHY_RESET, reset); +} + +static void imx8qm_clk_mux(u8 is_dp) +{ + struct imx_sc_ipc *handle; + + imx_scu_get_handle(&handle); + + if (is_dp) + /* Enable the 24MHz for HDP PHY */ + imx_sc_misc_set_control(handle, IMX_SC_R_HDMI, IMX_SC_C_MODE, 1); + else + imx_sc_misc_set_control(handle, IMX_SC_R_HDMI, IMX_SC_C_MODE, 0); +} + +int imx8qm_clocks_init(struct imx_mhdp_device *imx_mhdp) +{ + struct device *dev = imx_mhdp->mhdp.dev; + struct imx_hdp_clks *clks = &imx_mhdp->clks; + + clks->dig_pll = devm_clk_get(dev, "dig_pll"); + if (IS_ERR(clks->dig_pll)) { + dev_warn(dev, "failed to get dig pll clk\n"); + return PTR_ERR(clks->dig_pll); + } + + clks->av_pll = devm_clk_get(dev, "av_pll"); + if (IS_ERR(clks->av_pll)) { + dev_warn(dev, "failed to get av pll clk\n"); + return PTR_ERR(clks->av_pll); + } + + clks->clk_ipg = devm_clk_get(dev, "clk_ipg"); + if (IS_ERR(clks->clk_ipg)) { + dev_warn(dev, "failed to get dp ipg clk\n"); + return PTR_ERR(clks->clk_ipg); + } + + clks->clk_core = devm_clk_get(dev, "clk_core"); + if (IS_ERR(clks->clk_core)) { + dev_warn(dev, "failed to get hdp core clk\n"); + return PTR_ERR(clks->clk_core); + } + + clks->clk_pxl = devm_clk_get(dev, "clk_pxl"); + if (IS_ERR(clks->clk_pxl)) { + dev_warn(dev, "failed to get pxl clk\n"); + return PTR_ERR(clks->clk_pxl); + } + + clks->clk_pxl_mux = devm_clk_get(dev, "clk_pxl_mux"); + if (IS_ERR(clks->clk_pxl_mux)) { + dev_warn(dev, "failed to get pxl mux clk\n"); + return PTR_ERR(clks->clk_pxl_mux); + } + + clks->clk_pxl_link = devm_clk_get(dev, "clk_pxl_link"); + if (IS_ERR(clks->clk_pxl_link)) { + dev_warn(dev, "failed to get pxl link clk\n"); + return PTR_ERR(clks->clk_pxl_link); + } + + clks->lpcg_hdp = devm_clk_get(dev, "lpcg_hdp"); + if (IS_ERR(clks->lpcg_hdp)) { + dev_warn(dev, "failed to get lpcg hdp clk\n"); + return PTR_ERR(clks->lpcg_hdp); + } + + clks->lpcg_msi = devm_clk_get(dev, "lpcg_msi"); + if (IS_ERR(clks->lpcg_msi)) { + dev_warn(dev, "failed to get lpcg msi clk\n"); + return PTR_ERR(clks->lpcg_msi); + } + + clks->lpcg_pxl = devm_clk_get(dev, "lpcg_pxl"); + if (IS_ERR(clks->lpcg_pxl)) { + dev_warn(dev, "failed to get lpcg pxl clk\n"); + return PTR_ERR(clks->lpcg_pxl); + } + + clks->lpcg_vif = devm_clk_get(dev, "lpcg_vif"); + if (IS_ERR(clks->lpcg_vif)) { + dev_warn(dev, "failed to get lpcg vif clk\n"); + return PTR_ERR(clks->lpcg_vif); + } + + clks->lpcg_lis = devm_clk_get(dev, "lpcg_lis"); + if (IS_ERR(clks->lpcg_lis)) { + dev_warn(dev, "failed to get lpcg lis clk\n"); + return PTR_ERR(clks->lpcg_lis); + } + + clks->lpcg_apb = devm_clk_get(dev, "lpcg_apb"); + if (IS_ERR(clks->lpcg_apb)) { + dev_warn(dev, "failed to get lpcg apb clk\n"); + return PTR_ERR(clks->lpcg_apb); + } + + clks->lpcg_apb_csr = devm_clk_get(dev, "lpcg_apb_csr"); + if (IS_ERR(clks->lpcg_apb_csr)) { + dev_warn(dev, "failed to get apb csr clk\n"); + return PTR_ERR(clks->lpcg_apb_csr); + } + + clks->lpcg_apb_ctrl = devm_clk_get(dev, "lpcg_apb_ctrl"); + if (IS_ERR(clks->lpcg_apb_ctrl)) { + dev_warn(dev, "failed to get lpcg apb ctrl clk\n"); + return PTR_ERR(clks->lpcg_apb_ctrl); + } + + clks->clk_i2s_bypass = devm_clk_get(dev, "clk_i2s_bypass"); + if (IS_ERR(clks->clk_i2s_bypass)) { + dev_err(dev, "failed to get i2s bypass clk\n"); + return PTR_ERR(clks->clk_i2s_bypass); + } + + clks->lpcg_i2s = devm_clk_get(dev, "lpcg_i2s"); + if (IS_ERR(clks->lpcg_i2s)) { + dev_err(dev, "failed to get lpcg i2s clk\n"); + return PTR_ERR(clks->lpcg_i2s); + } + return true; +} + +static int imx8qm_pixel_clk_enable(struct imx_mhdp_device *imx_mhdp) +{ + struct imx_hdp_clks *clks = &imx_mhdp->clks; + struct device *dev = imx_mhdp->mhdp.dev; + int ret; + + ret = clk_prepare_enable(clks->av_pll); + if (ret < 0) { + dev_err(dev, "%s, pre av pll error\n", __func__); + return ret; + } + + ret = clk_prepare_enable(clks->clk_pxl); + if (ret < 0) { + dev_err(dev, "%s, pre clk pxl error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->clk_pxl_mux); + if (ret < 0) { + dev_err(dev, "%s, pre clk pxl mux error\n", __func__); + return ret; + } + + ret = clk_prepare_enable(clks->clk_pxl_link); + if (ret < 0) { + dev_err(dev, "%s, pre clk pxl link error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->lpcg_vif); + if (ret < 0) { + dev_err(dev, "%s, pre clk vif error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->lpcg_pxl); + if (ret < 0) { + dev_err(dev, "%s, pre lpcg pxl error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->lpcg_hdp); + if (ret < 0) { + dev_err(dev, "%s, pre lpcg hdp error\n", __func__); + return ret; + } + return ret; +} + +static void imx8qm_pixel_clk_disable(struct imx_mhdp_device *imx_mhdp) +{ + struct imx_hdp_clks *clks = &imx_mhdp->clks; + + clk_disable_unprepare(clks->lpcg_pxl); + clk_disable_unprepare(clks->lpcg_hdp); + clk_disable_unprepare(clks->lpcg_vif); + clk_disable_unprepare(clks->clk_pxl); + clk_disable_unprepare(clks->clk_pxl_link); + clk_disable_unprepare(clks->clk_pxl_mux); + clk_disable_unprepare(clks->av_pll); +} + +static void imx8qm_pixel_clk_set_rate(struct imx_mhdp_device *imx_mhdp, u32 pclock) +{ + bool dual_mode = imx8qm_video_dual_mode(&imx_mhdp->mhdp); + struct imx_hdp_clks *clks = &imx_mhdp->clks; + + /* pixel clock for HDMI */ + clk_set_rate(clks->av_pll, pclock); + + if (dual_mode == true) { + clk_set_rate(clks->clk_pxl, pclock/2); + clk_set_rate(clks->clk_pxl_link, pclock/2); + } else { + clk_set_rate(clks->clk_pxl_link, pclock); + clk_set_rate(clks->clk_pxl, pclock); + } + clk_set_rate(clks->clk_pxl_mux, pclock); +} + +static int imx8qm_ipg_clk_enable(struct imx_mhdp_device *imx_mhdp) +{ + int ret; + struct imx_hdp_clks *clks = &imx_mhdp->clks; + struct device *dev = imx_mhdp->mhdp.dev; + + ret = clk_prepare_enable(clks->dig_pll); + if (ret < 0) { + dev_err(dev, "%s, pre dig pll error\n", __func__); + return ret; + } + + ret = clk_prepare_enable(clks->clk_ipg); + if (ret < 0) { + dev_err(dev, "%s, pre clk_ipg error\n", __func__); + return ret; + } + + ret = clk_prepare_enable(clks->clk_core); + if (ret < 0) { + dev_err(dev, "%s, pre clk core error\n", __func__); + return ret; + } + + ret = clk_prepare_enable(clks->lpcg_apb); + if (ret < 0) { + dev_err(dev, "%s, pre clk apb error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->lpcg_lis); + if (ret < 0) { + dev_err(dev, "%s, pre clk lis error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->lpcg_msi); + if (ret < 0) { + dev_err(dev, "%s, pre clk msierror\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->lpcg_apb_csr); + if (ret < 0) { + dev_err(dev, "%s, pre clk apb csr error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->lpcg_apb_ctrl); + if (ret < 0) { + dev_err(dev, "%s, pre clk apb ctrl error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->lpcg_i2s); + if (ret < 0) { + dev_err(dev, "%s, pre clk i2s error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->clk_i2s_bypass); + if (ret < 0) { + dev_err(dev, "%s, pre clk i2s bypass error\n", __func__); + return ret; + } + return ret; +} + +static void imx8qm_ipg_clk_set_rate(struct imx_mhdp_device *imx_mhdp) +{ + struct imx_hdp_clks *clks = &imx_mhdp->clks; + + /* ipg/core clock */ + clk_set_rate(clks->dig_pll, PLL_800MHZ); + clk_set_rate(clks->clk_core, PLL_800MHZ/4); + clk_set_rate(clks->clk_ipg, PLL_800MHZ/8); +} + +static void imx8qm_detach_pm_domains(struct imx_mhdp_device *imx_mhdp) +{ + if (imx_mhdp->pd_pll1_link && !IS_ERR(imx_mhdp->pd_pll1_link)) + device_link_del(imx_mhdp->pd_pll1_link); + if (imx_mhdp->pd_pll1_dev && !IS_ERR(imx_mhdp->pd_pll1_dev)) + dev_pm_domain_detach(imx_mhdp->pd_pll1_dev, true); + + if (imx_mhdp->pd_pll0_link && !IS_ERR(imx_mhdp->pd_pll0_link)) + device_link_del(imx_mhdp->pd_pll0_link); + if (imx_mhdp->pd_pll0_dev && !IS_ERR(imx_mhdp->pd_pll0_dev)) + dev_pm_domain_detach(imx_mhdp->pd_pll0_dev, true); + + if (imx_mhdp->pd_mhdp_link && !IS_ERR(imx_mhdp->pd_mhdp_link)) + device_link_del(imx_mhdp->pd_mhdp_link); + if (imx_mhdp->pd_mhdp_dev && !IS_ERR(imx_mhdp->pd_mhdp_dev)) + dev_pm_domain_detach(imx_mhdp->pd_mhdp_dev, true); + + imx_mhdp->pd_mhdp_dev = NULL; + imx_mhdp->pd_mhdp_link = NULL; + imx_mhdp->pd_pll0_dev = NULL; + imx_mhdp->pd_pll0_link = NULL; + imx_mhdp->pd_pll1_dev = NULL; + imx_mhdp->pd_pll1_link = NULL; +} + +static int imx8qm_attach_pm_domains(struct imx_mhdp_device *imx_mhdp) +{ + struct device *dev = imx_mhdp->mhdp.dev; + u32 flags = DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE; + int ret = 0; + + imx_mhdp->pd_mhdp_dev = dev_pm_domain_attach_by_name(dev, "hdmi"); + if (IS_ERR(imx_mhdp->pd_mhdp_dev)) { + ret = PTR_ERR(imx_mhdp->pd_mhdp_dev); + dev_err(dev, "Failed to attach dc pd dev: %d\n", ret); + goto fail; + } + imx_mhdp->pd_mhdp_link = device_link_add(dev, imx_mhdp->pd_mhdp_dev, flags); + if (IS_ERR(imx_mhdp->pd_mhdp_link)) { + ret = PTR_ERR(imx_mhdp->pd_mhdp_link); + dev_err(dev, "Failed to add device link to dc pd dev: %d\n", + ret); + goto fail; + } + + imx_mhdp->pd_pll0_dev = dev_pm_domain_attach_by_name(dev, "pll0"); + if (IS_ERR(imx_mhdp->pd_pll0_dev)) { + ret = PTR_ERR(imx_mhdp->pd_pll0_dev); + dev_err(dev, "Failed to attach pll0 pd dev: %d\n", ret); + goto fail; + } + imx_mhdp->pd_pll0_link = device_link_add(dev, imx_mhdp->pd_pll0_dev, flags); + if (IS_ERR(imx_mhdp->pd_pll0_link)) { + ret = PTR_ERR(imx_mhdp->pd_pll0_link); + dev_err(dev, "Failed to add device link to pll0 pd dev: %d\n", + ret); + goto fail; + } + + imx_mhdp->pd_pll1_dev = dev_pm_domain_attach_by_name(dev, "pll1"); + if (IS_ERR(imx_mhdp->pd_pll1_dev)) { + ret = PTR_ERR(imx_mhdp->pd_pll1_dev); + dev_err(dev, "Failed to attach pll0 pd dev: %d\n", ret); + goto fail; + } + imx_mhdp->pd_pll1_link = device_link_add(dev, imx_mhdp->pd_pll1_dev, flags); + if (IS_ERR(imx_mhdp->pd_pll1_link)) { + ret = PTR_ERR(imx_mhdp->pd_pll1_link); + dev_err(dev, "Failed to add device link to pll1 pd dev: %d\n", + ret); + goto fail; + } +fail: + imx8qm_detach_pm_domains(imx_mhdp); + return ret; +} + +int cdns_mhdp_power_on_imx8qm(struct cdns_mhdp_device *mhdp) +{ + struct imx_mhdp_device *imx_mhdp = + container_of(mhdp, struct imx_mhdp_device, mhdp); + /* Power on PM Domains */ + + imx8qm_attach_pm_domains(imx_mhdp); + + /* clock init and rate set */ + imx8qm_clocks_init(imx_mhdp); + + imx8qm_ipg_clk_set_rate(imx_mhdp); + + /* Init pixel clock with 148.5MHz before FW init */ + imx8qm_pixel_clk_set_rate(imx_mhdp, 148500000); + + imx8qm_ipg_clk_enable(imx_mhdp); + + imx8qm_clk_mux(imx_mhdp->mhdp.plat_data->is_dp); + + imx8qm_pixel_clk_enable(imx_mhdp); + + imx8qm_phy_reset(1); + + return 0; +} + +void cdns_mhdp_plat_deinit_imx8qm(struct cdns_mhdp_device *mhdp) +{ + struct imx_mhdp_device *imx_mhdp = + container_of(mhdp, struct imx_mhdp_device, mhdp); + bool dual_mode = imx8qm_video_dual_mode(&imx_mhdp->mhdp); + + imx8qm_pixel_link_sync_disable(dual_mode); + imx8qm_pixel_link_invalid(dual_mode); +} + +void cdns_mhdp_plat_init_imx8qm(struct cdns_mhdp_device *mhdp) +{ + struct imx_mhdp_device *imx_mhdp = + container_of(mhdp, struct imx_mhdp_device, mhdp); + bool dual_mode = imx8qm_video_dual_mode(&imx_mhdp->mhdp); + + imx8qm_pixel_link_valid(dual_mode); + imx8qm_pixel_link_sync_enable(dual_mode); +} + +void cdns_mhdp_pclk_rate_imx8qm(struct cdns_mhdp_device *mhdp) +{ + struct imx_mhdp_device *imx_mhdp = + container_of(mhdp, struct imx_mhdp_device, mhdp); + + mutex_lock(&mhdp->iolock); + + /* set pixel clock before video mode setup */ + imx8qm_pixel_clk_disable(imx_mhdp); + + imx8qm_pixel_clk_set_rate(imx_mhdp, imx_mhdp->mhdp.mode.clock * 1000); + + imx8qm_pixel_clk_enable(imx_mhdp); + + mutex_unlock(&mhdp->iolock); + + /* Config pixel link mux */ + imx8qm_pixel_link_mux(imx_mhdp); +} + +int cdns_mhdp_firmware_write_section(struct imx_mhdp_device *imx_mhdp, + const u8 *data, int size, int addr) +{ + int i; + + for (i = 0; i < size; i += 4) { + u32 val = (unsigned int)data[i] << 0 | + (unsigned int)data[i + 1] << 8 | + (unsigned int)data[i + 2] << 16 | + (unsigned int)data[i + 3] << 24; + cdns_mhdp_bus_write(val, &imx_mhdp->mhdp, addr + i); + } + + return 0; +} + +static void cdns_mhdp_firmware_load_cont(const struct firmware *fw, void *context) +{ + struct imx_mhdp_device *imx_mhdp = context; + + imx_mhdp->fw = fw; +} + +static int cdns_mhdp_firmware_load(struct imx_mhdp_device *imx_mhdp) +{ + const u8 *iram; + const u8 *dram; + u32 rate; + int ret; + + /* configure HDMI/DP core clock */ + rate = clk_get_rate(imx_mhdp->clks.clk_core); + if (imx_mhdp->mhdp.is_ls1028a) + rate = rate / 4; + + cdns_mhdp_set_fw_clk(&imx_mhdp->mhdp, rate); + + /* skip fw loading if none is specified */ + if (!imx_mhdp->firmware_name) + goto out; + + if (!imx_mhdp->fw) { + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOUEVENT, + imx_mhdp->firmware_name, + imx_mhdp->mhdp.dev, GFP_KERNEL, + imx_mhdp, + cdns_mhdp_firmware_load_cont); + if (ret < 0) { + DRM_ERROR("failed to load firmware\n"); + return -ENOENT; + } + } else { + iram = imx_mhdp->fw->data + FW_IRAM_OFFSET; + dram = iram + FW_IRAM_SIZE; + + cdns_mhdp_firmware_write_section(imx_mhdp, iram, FW_IRAM_SIZE, ADDR_IMEM); + cdns_mhdp_firmware_write_section(imx_mhdp, dram, FW_DRAM_SIZE, ADDR_DMEM); + } + +out: + /* un-reset ucpu */ + cdns_mhdp_bus_write(0, &imx_mhdp->mhdp, APB_CTRL); + DRM_INFO("Started firmware!\n"); + + return 0; +} + +int cdns_mhdp_firmware_init_imx8qm(struct cdns_mhdp_device *mhdp) +{ + struct imx_mhdp_device *imx_mhdp = + container_of(mhdp, struct imx_mhdp_device, mhdp); + int ret; + + /* load firmware */ + ret = cdns_mhdp_firmware_load(imx_mhdp); + if (ret) + return ret; + + ret = cdns_mhdp_check_alive(&imx_mhdp->mhdp); + if (ret == false) { + DRM_ERROR("NO HDMI FW running\n"); + return -ENXIO; + } + + /* turn on IP activity */ + cdns_mhdp_set_firmware_active(&imx_mhdp->mhdp, 1); + + DRM_INFO("HDP FW Version - ver %d verlib %d\n", + cdns_mhdp_bus_read(mhdp, VER_L) + (cdns_mhdp_bus_read(mhdp, VER_H) << 8), + cdns_mhdp_bus_read(mhdp, VER_LIB_H_ADDR) + (cdns_mhdp_bus_read(mhdp, VER_LIB_H_ADDR) << 8)); + + return 0; +} + +int cdns_mhdp_suspend_imx8qm(struct cdns_mhdp_device *mhdp) +{ + struct imx_mhdp_device *imx_mhdp = + container_of(mhdp, struct imx_mhdp_device, mhdp); + + imx8qm_pixel_clk_disable(imx_mhdp); + + return 0; +} + +int cdns_mhdp_resume_imx8qm(struct cdns_mhdp_device *mhdp) +{ + struct imx_mhdp_device *imx_mhdp = + container_of(mhdp, struct imx_mhdp_device, mhdp); + + imx8qm_pixel_clk_enable(imx_mhdp); + + return cdns_mhdp_firmware_init_imx8qm(mhdp); +} diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imxdrv.c b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imxdrv.c new file mode 100644 index 000000000000..b3118fc7d629 --- /dev/null +++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imxdrv.c @@ -0,0 +1,276 @@ +/* + * Copyright 2019-2020 NXP + * + * this program is free software; you can redistribute it and/or modify + * it under the terms of the gnu general public license version 2 as + * published by the free software foundation. + */ +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/component.h> +#include <drm/drm_of.h> +#include <drm/drm_vblank.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder_slave.h> + +#include "cdns-mhdp-imx.h" +#include "cdns-mhdp-phy.h" +#include "../imx-drm.h" + +static void cdns_mhdp_imx_encoder_disable(struct drm_encoder *encoder) +{ + struct drm_bridge *bridge = drm_bridge_chain_get_first_bridge(encoder); + struct cdns_mhdp_device *mhdp = bridge->driver_private; + + if (mhdp->is_dp) + cdns_dp_phy_shutdown(mhdp); + else + cdns_hdmi_phy_shutdown(mhdp); + + cdns_mhdp_plat_call(mhdp, plat_init); +} + +static void cdns_mhdp_imx_encoder_enable(struct drm_encoder *encoder) +{ + struct drm_bridge *bridge = drm_bridge_chain_get_first_bridge(encoder); + struct cdns_mhdp_device *mhdp = bridge->driver_private; + + cdns_mhdp_plat_call(mhdp, plat_init); + if (mhdp->is_dp) + cdns_dp_phy_power_up(mhdp); + else + cdns_hdmi_phy_power_up(mhdp); +} + +static int cdns_mhdp_imx_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); + struct drm_bridge *bridge = drm_bridge_chain_get_first_bridge(encoder); + struct cdns_mhdp_device *mhdp = bridge->driver_private; + + if (mhdp->plat_data->video_format != 0) + imx_crtc_state->bus_format = mhdp->plat_data->video_format; + + if (mhdp->force_mode_set) { + crtc_state->mode_changed = true; + /* reset force mode set flag */ + mhdp->force_mode_set = false; + } + + return 0; +} + +static const struct drm_encoder_helper_funcs cdns_mhdp_imx_encoder_helper_funcs = { + .enable = cdns_mhdp_imx_encoder_enable, + .disable = cdns_mhdp_imx_encoder_disable, + .atomic_check = cdns_mhdp_imx_encoder_atomic_check, +}; + +static const struct drm_encoder_funcs cdns_mhdp_imx_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static struct cdns_plat_data imx8mq_hdmi_drv_data = { + .plat_name = "imx8mq-hdmi", + .bind = cdns_hdmi_bind, + .unbind = cdns_hdmi_unbind, + .phy_set = cdns_hdmi_phy_set_imx8mq, + .phy_video_valid = cdns_hdmi_phy_video_valid_imx8mq, + .bus_type = BUS_TYPE_NORMAL_APB, +}; + +static struct cdns_plat_data imx8mq_dp_drv_data = { + .plat_name = "imx8mq-dp", + .bind = cdns_dp_bind, + .unbind = cdns_dp_unbind, + .phy_set = cdns_dp_phy_set_imx8mq, + .bus_type = BUS_TYPE_NORMAL_APB, +}; + +static struct cdns_plat_data imx8qm_hdmi_drv_data = { + .plat_name = "imx8qm-hdmi", + .bind = cdns_hdmi_bind, + .unbind = cdns_hdmi_unbind, + .phy_set = cdns_hdmi_phy_set_imx8qm, + .phy_video_valid = cdns_hdmi_phy_video_valid_imx8qm, + .power_on = cdns_mhdp_power_on_imx8qm, + .firmware_init = cdns_mhdp_firmware_init_imx8qm, + .resume = cdns_mhdp_resume_imx8qm, + .suspend = cdns_mhdp_suspend_imx8qm, + .pclk_rate = cdns_mhdp_pclk_rate_imx8qm, + .plat_init = cdns_mhdp_plat_init_imx8qm, + .plat_deinit = cdns_mhdp_plat_deinit_imx8qm, + .bus_type = BUS_TYPE_LOW4K_APB, + .video_format = MEDIA_BUS_FMT_RGB101010_1X30, +}; + +static struct cdns_plat_data imx8qm_dp_drv_data = { + .plat_name = "imx8qm-dp", + .bind = cdns_dp_bind, + .unbind = cdns_dp_unbind, + .phy_set = cdns_dp_phy_set_imx8qm, + .power_on = cdns_mhdp_power_on_imx8qm, + .firmware_init = cdns_mhdp_firmware_init_imx8qm, + .resume = cdns_mhdp_resume_imx8qm, + .suspend = cdns_mhdp_suspend_imx8qm, + .pclk_rate = cdns_mhdp_pclk_rate_imx8qm, + .plat_init = cdns_mhdp_plat_init_imx8qm, + .plat_deinit = cdns_mhdp_plat_deinit_imx8qm, + .bus_type = BUS_TYPE_LOW4K_APB, + .video_format = MEDIA_BUS_FMT_RGB101010_1X30, + .is_dp = true, +}; + +static struct cdns_plat_data ls1028a_dp_drv_data = { + .bind = cdns_dp_bind, + .unbind = cdns_dp_unbind, + .phy_set = cdns_dp_phy_set_imx8mq, + .power_on = cdns_mhdp_power_on_ls1028a, + .firmware_init = cdns_mhdp_firmware_init_imx8qm, + .pclk_rate = cdns_mhdp_pclk_rate_ls1028a, + .bus_type = BUS_TYPE_NORMAL_APB, +}; + +static const struct of_device_id cdns_mhdp_imx_dt_ids[] = { + { .compatible = "cdn,imx8mq-hdmi", + .data = &imx8mq_hdmi_drv_data + }, + { .compatible = "cdn,imx8mq-dp", + .data = &imx8mq_dp_drv_data + }, + { .compatible = "cdn,imx8qm-hdmi", + .data = &imx8qm_hdmi_drv_data + }, + { .compatible = "cdn,imx8qm-dp", + .data = &imx8qm_dp_drv_data + }, + { .compatible = "cdn,ls1028a-dp", + .data = &ls1028a_dp_drv_data + }, + {}, +}; +MODULE_DEVICE_TABLE(of, cdns_mhdp_imx_dt_ids); + +static int cdns_mhdp_imx_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + const struct cdns_plat_data *plat_data; + const struct of_device_id *match; + struct drm_device *drm = data; + struct drm_encoder *encoder; + struct imx_mhdp_device *imx_mhdp; + int ret; + + if (!pdev->dev.of_node) + return -ENODEV; + + imx_mhdp = devm_kzalloc(&pdev->dev, sizeof(*imx_mhdp), GFP_KERNEL); + if (!imx_mhdp) + return -ENOMEM; + + match = of_match_node(cdns_mhdp_imx_dt_ids, pdev->dev.of_node); + if (!match) + return -EFAULT; + + plat_data = match->data; + encoder = &imx_mhdp->encoder; + + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + + ret = of_property_read_string(pdev->dev.of_node, "firmware-name", + &imx_mhdp->firmware_name); + /* + * If we failed to find the CRTC(s) which this encoder is + * supposed to be connected to, it's because the CRTC has + * not been registered yet. Defer probing, and hope that + * the required CRTC is added later. + */ + if (encoder->possible_crtcs == 0) + return -EPROBE_DEFER; + + drm_encoder_helper_add(encoder, &cdns_mhdp_imx_encoder_helper_funcs); + drm_encoder_init(drm, encoder, &cdns_mhdp_imx_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + + + imx_mhdp->mhdp.plat_data = plat_data; + imx_mhdp->mhdp.dev = dev; + imx_mhdp->mhdp.drm_dev = drm; + imx_mhdp->mhdp.bus_type = plat_data->bus_type; + ret = plat_data->bind(pdev, encoder, &imx_mhdp->mhdp); + /* + * If cdns_mhdp_bind() fails we'll never call cdns_mhdp_unbind(), + * which would have called the encoder cleanup. Do it manually. + */ + if (ret < 0) + drm_encoder_cleanup(encoder); + + return ret; +} + +static void cdns_mhdp_imx_unbind(struct device *dev, struct device *master, + void *data) +{ + struct imx_mhdp_device *imx_mhdp = dev_get_drvdata(dev); + + imx_mhdp->mhdp.plat_data->unbind(dev); +} + +static const struct component_ops cdns_mhdp_imx_ops = { + .bind = cdns_mhdp_imx_bind, + .unbind = cdns_mhdp_imx_unbind, +}; + +static int cdns_mhdp_imx_suspend(struct device *dev) +{ + struct imx_mhdp_device *imx_mhdp = dev_get_drvdata(dev); + + cdns_mhdp_plat_call(&imx_mhdp->mhdp, suspend); + + return 0; +} + +static int cdns_mhdp_imx_resume(struct device *dev) +{ + struct imx_mhdp_device *imx_mhdp = dev_get_drvdata(dev); + + cdns_mhdp_plat_call(&imx_mhdp->mhdp, resume); + cdns_mhdp_plat_call(&imx_mhdp->mhdp, phy_set); + + return 0; +} + +static int cdns_mhdp_imx_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &cdns_mhdp_imx_ops); +} + +static int cdns_mhdp_imx_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &cdns_mhdp_imx_ops); + + return 0; +} + +static const struct dev_pm_ops cdns_mhdp_imx_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(cdns_mhdp_imx_suspend, cdns_mhdp_imx_resume) +}; + +static struct platform_driver cdns_mhdp_imx_platform_driver = { + .probe = cdns_mhdp_imx_probe, + .remove = cdns_mhdp_imx_remove, + .driver = { + .name = "cdns-mhdp-imx", + .of_match_table = cdns_mhdp_imx_dt_ids, + .pm = &cdns_mhdp_imx_pm_ops, + }, +}; + +module_platform_driver(cdns_mhdp_imx_platform_driver); + +MODULE_AUTHOR("Sandor YU <sandor.yu@nxp.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cdnhdmi-imx"); diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-ls1028a.c b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-ls1028a.c new file mode 100644 index 000000000000..4cc71301f5fe --- /dev/null +++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-ls1028a.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 NXP + * + */ +#include <linux/clk.h> +#include <drm/drm_vblank.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> + +#include "cdns-mhdp-imx.h" + +static const struct of_device_id scfg_device_ids[] = { + { .compatible = "fsl,ls1028a-scfg", }, + {} +}; + +static void ls1028a_phy_reset(u8 reset) +{ + struct device_node *scfg_node; + void __iomem *scfg_base = NULL; + + scfg_node = of_find_matching_node(NULL, scfg_device_ids); + if (scfg_node) + scfg_base = of_iomap(scfg_node, 0); + + iowrite32(reset, scfg_base + 0x230); +} + +int ls1028a_clocks_init(struct imx_mhdp_device *imx_mhdp) +{ + struct device *dev = imx_mhdp->mhdp.dev; + struct imx_hdp_clks *clks = &imx_mhdp->clks; + + clks->clk_core = devm_clk_get(dev, "clk_core"); + if (IS_ERR(clks->clk_core)) { + dev_warn(dev, "failed to get hdp core clk\n"); + return PTR_ERR(clks->clk_core); + } + + clks->clk_pxl = devm_clk_get(dev, "clk_pxl"); + if (IS_ERR(clks->clk_pxl)) { + dev_warn(dev, "failed to get pxl clk\n"); + return PTR_ERR(clks->clk_pxl); + } + + return true; +} + +static int ls1028a_pixel_clk_enable(struct imx_mhdp_device *imx_mhdp) +{ + struct imx_hdp_clks *clks = &imx_mhdp->clks; + struct device *dev = imx_mhdp->mhdp.dev; + int ret; + + ret = clk_prepare_enable(clks->clk_pxl); + if (ret < 0) { + dev_err(dev, "%s, pre clk pxl error\n", __func__); + return ret; + } + + return ret; +} + +static void ls1028a_pixel_clk_disable(struct imx_mhdp_device *imx_mhdp) +{ + struct imx_hdp_clks *clks = &imx_mhdp->clks; + + clk_disable_unprepare(clks->clk_pxl); +} + +static void ls1028a_pixel_clk_set_rate(struct imx_mhdp_device *imx_mhdp, + u32 pclock) +{ + struct imx_hdp_clks *clks = &imx_mhdp->clks; + + clk_set_rate(clks->clk_pxl, pclock); +} + +int cdns_mhdp_power_on_ls1028a(struct cdns_mhdp_device *mhdp) +{ + struct imx_mhdp_device *imx_mhdp = container_of + (mhdp, struct imx_mhdp_device, mhdp); + + /* clock init and rate set */ + ls1028a_clocks_init(imx_mhdp); + + ls1028a_pixel_clk_enable(imx_mhdp); + + /* Init pixel clock with 148.5MHz before FW init */ + ls1028a_pixel_clk_set_rate(imx_mhdp, 148500000); + + ls1028a_phy_reset(1); + + return 0; +} + +void cdns_mhdp_pclk_rate_ls1028a(struct cdns_mhdp_device *mhdp) +{ + struct imx_mhdp_device *imx_mhdp = container_of + (mhdp, struct imx_mhdp_device, mhdp); + + /* set pixel clock before video mode setup */ + ls1028a_pixel_clk_disable(imx_mhdp); + + ls1028a_pixel_clk_set_rate(imx_mhdp, imx_mhdp->mhdp.mode.clock * 1000); + + ls1028a_pixel_clk_enable(imx_mhdp); +} diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-phy.h b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-phy.h new file mode 100644 index 000000000000..4f52dae4d0a1 --- /dev/null +++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-phy.h @@ -0,0 +1,159 @@ +/* + * Copyright 2019-2021 NXP + * + * 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. + */ + +#ifndef _CDN_DP_PHY_H +#define _CDN_DP_PHY_H + +#include <drm/bridge/cdns-mhdp.h> + +#define CMN_SSM_BIAS_TMR 0x0022 +#define CMN_PLLSM0_PLLEN_TMR 0x0029 +#define CMN_PLLSM0_PLLPRE_TMR 0x002A +#define CMN_PLLSM0_PLLVREF_TMR 0x002B +#define CMN_PLLSM0_PLLLOCK_TMR 0x002C +#define CMN_PLLSM0_USER_DEF_CTRL 0x002F +#define CMN_PSM_CLK_CTRL 0x0061 +#define CMN_CDIAG_REFCLK_CTRL 0x0062 +#define CMN_PLL0_VCOCAL_START 0x0081 +#define CMN_PLL0_VCOCAL_INIT_TMR 0x0084 +#define CMN_PLL0_VCOCAL_ITER_TMR 0x0085 +#define CMN_PLL0_INTDIV 0x0094 +#define CMN_PLL0_FRACDIV 0x0095 +#define CMN_PLL0_HIGH_THR 0x0096 +#define CMN_PLL0_DSM_DIAG 0x0097 +#define CMN_PLL0_SS_CTRL1 0x0098 +#define CMN_PLL0_SS_CTRL2 0x0099 +#define CMN_ICAL_INIT_TMR 0x00C4 +#define CMN_ICAL_ITER_TMR 0x00C5 +#define CMN_RXCAL_INIT_TMR 0x00D4 +#define CMN_RXCAL_ITER_TMR 0x00D5 +#define CMN_TXPUCAL_CTRL 0x00E0 +#define CMN_TXPUCAL_INIT_TMR 0x00E4 +#define CMN_TXPUCAL_ITER_TMR 0x00E5 +#define CMN_TXPDCAL_CTRL 0x00F0 +#define CMN_TXPDCAL_INIT_TMR 0x00F4 +#define CMN_TXPDCAL_ITER_TMR 0x00F5 +#define CMN_ICAL_ADJ_INIT_TMR 0x0102 +#define CMN_ICAL_ADJ_ITER_TMR 0x0103 +#define CMN_RX_ADJ_INIT_TMR 0x0106 +#define CMN_RX_ADJ_ITER_TMR 0x0107 +#define CMN_TXPU_ADJ_CTRL 0x0108 +#define CMN_TXPU_ADJ_INIT_TMR 0x010A +#define CMN_TXPU_ADJ_ITER_TMR 0x010B +#define CMN_TXPD_ADJ_CTRL 0x010c +#define CMN_TXPD_ADJ_INIT_TMR 0x010E +#define CMN_TXPD_ADJ_ITER_TMR 0x010F +#define CMN_DIAG_PLL0_FBH_OVRD 0x01C0 +#define CMN_DIAG_PLL0_FBL_OVRD 0x01C1 +#define CMN_DIAG_PLL0_OVRD 0x01C2 +#define CMN_DIAG_PLL0_TEST_MODE 0x01C4 +#define CMN_DIAG_PLL0_V2I_TUNE 0x01C5 +#define CMN_DIAG_PLL0_CP_TUNE 0x01C6 +#define CMN_DIAG_PLL0_LF_PROG 0x01C7 +#define CMN_DIAG_PLL0_PTATIS_TUNE1 0x01C8 +#define CMN_DIAG_PLL0_PTATIS_TUNE2 0x01C9 +#define CMN_DIAG_PLL0_INCLK_CTRL 0x01CA +#define CMN_DIAG_PLL0_PXL_DIVH 0x01CB +#define CMN_DIAG_PLL0_PXL_DIVL 0x01CC +#define CMN_DIAG_HSCLK_SEL 0x01E0 +#define CMN_DIAG_PER_CAL_ADJ 0x01EC +#define CMN_DIAG_CAL_CTRL 0x01ED +#define CMN_DIAG_ACYA 0x01FF +#define XCVR_PSM_RCTRL 0x4001 +#define XCVR_PSM_CAL_TMR 0x4002 +#define XCVR_PSM_A0IN_TMR 0x4003 +#define TX_TXCC_CAL_SCLR_MULT_0 0x4047 +#define TX_TXCC_CPOST_MULT_00_0 0x404C +#define TX_TXCC_MGNFS_MULT_000_0 0x4050 +#define XCVR_DIAG_PLLDRC_CTRL 0x40E0 +#define XCVR_DIAG_PLLDRC_CTRL 0x40E0 +#define XCVR_DIAG_HSCLK_SEL 0x40E1 +#define XCVR_DIAG_BIDI_CTRL 0x40E8 +#define XCVR_DIAG_LANE_FCM_EN_MGN_TMR 0x40F2 +#define XCVR_DIAG_LANE_FCM_EN_MGN 0x40F2 +#define TX_PSC_A0 0x4100 +#define TX_PSC_A1 0x4101 +#define TX_PSC_A2 0x4102 +#define TX_PSC_A3 0x4103 +#define TX_RCVDET_CTRL 0x4120 +#define TX_RCVDET_EN_TMR 0x4122 +#define TX_RCVDET_EN_TMR 0x4122 +#define TX_RCVDET_ST_TMR 0x4123 +#define TX_RCVDET_ST_TMR 0x4123 +#define TX_BIST_CTRL 0x4140 +#define TX_BIST_UDDWR 0x4141 +#define TX_DIAG_TX_CTRL 0x41E0 +#define TX_DIAG_TX_DRV 0x41E1 +#define TX_DIAG_BGREF_PREDRV_DELAY 0x41E7 +#define TX_DIAG_BGREF_PREDRV_DELAY 0x41E7 +#define XCVR_PSM_RCTRL_1 0x4201 +#define TX_TXCC_CAL_SCLR_MULT_1 0x4247 +#define TX_TXCC_CPOST_MULT_00_1 0x424C +#define TX_TXCC_MGNFS_MULT_000_1 0x4250 +#define XCVR_DIAG_PLLDRC_CTRL_1 0x42E0 +#define XCVR_DIAG_HSCLK_SEL_1 0x42E1 +#define XCVR_DIAG_LANE_FCM_EN_MGN_TMR_1 0x42F2 +#define TX_RCVDET_EN_TMR_1 0x4322 +#define TX_RCVDET_ST_TMR_1 0x4323 +#define TX_DIAG_ACYA_0 0x41FF +#define TX_DIAG_ACYA_1 0x43FF +#define TX_DIAG_ACYA_2 0x45FF +#define TX_DIAG_ACYA_3 0x47FF +#define TX_ANA_CTRL_REG_1 0x5020 +#define TX_ANA_CTRL_REG_2 0x5021 +#define TXDA_COEFF_CALC 0x5022 +#define TX_DIG_CTRL_REG_1 0x5023 +#define TX_DIG_CTRL_REG_2 0x5024 +#define TXDA_CYA_AUXDA_CYA 0x5025 +#define TX_ANA_CTRL_REG_3 0x5026 +#define TX_ANA_CTRL_REG_4 0x5027 +#define TX_ANA_CTRL_REG_5 0x5029 +#define RX_PSC_A0 0x8000 +#define RX_PSC_CAL 0x8006 +#define PMA_LANE_CFG 0xC000 +#define PIPE_CMN_CTRL1 0xC001 +#define PIPE_CMN_CTRL2 0xC002 +#define PIPE_COM_LOCK_CFG1 0xC003 +#define PIPE_COM_LOCK_CFG2 0xC004 +#define PIPE_RCV_DET_INH 0xC005 +#define PHY_HDP_MODE_CTRL 0xC008 +#define PHY_HDP_CLK_CTL 0xC009 +#define STS 0xC00F +#define PHY_ISO_CMN_CTRL 0xC010 +#define PHY_ISO_CMN_CTRL 0xC010 +#define PHY_HDP_TX_CTL_L0 0xC408 +#define PHY_DP_TX_CTL 0xC408 +#define PHY_HDP_TX_CTL_L1 0xC448 +#define PHY_HDP_TX_CTL_L2 0xC488 +#define PHY_HDP_TX_CTL_L3 0xC4C8 +#define PHY_PMA_CMN_CTRL1 0xC800 +#define PMA_CMN_CTRL1 0xC800 +#define PHY_PMA_ISO_CMN_CTRL 0xC810 +#define PHY_PMA_ISO_PLL_CTRL1 0xC812 +#define PHY_PMA_ISOLATION_CTRL 0xC81F +#define PHY_ISOLATION_CTRL 0xC81F +#define PHY_PMA_ISO_XCVR_CTRL 0xCC11 +#define PHY_PMA_ISO_LINK_MODE 0xCC12 +#define PHY_PMA_ISO_PWRST_CTRL 0xCC13 +#define PHY_PMA_ISO_TX_DATA_LO 0xCC14 +#define PHY_PMA_ISO_TX_DATA_HI 0xCC15 +#define PHY_PMA_ISO_RX_DATA_LO 0xCC16 +#define PHY_PMA_ISO_RX_DATA_HI 0xCC17 + +int cdns_dp_phy_set_imx8mq(struct cdns_mhdp_device *hdp); +int cdns_dp_phy_set_imx8qm(struct cdns_mhdp_device *hdp); +int cdns_dp_phy_shutdown(struct cdns_mhdp_device *mhdp); +int cdns_dp_phy_power_up(struct cdns_mhdp_device *mhdp); +bool cdns_hdmi_phy_video_valid_imx8mq(struct cdns_mhdp_device *hdp); +bool cdns_hdmi_phy_video_valid_imx8qm(struct cdns_mhdp_device *hdp); +int cdns_hdmi_phy_set_imx8mq(struct cdns_mhdp_device *hdp); +int cdns_hdmi_phy_set_imx8qm(struct cdns_mhdp_device *hdp); +int cdns_hdmi_phy_shutdown(struct cdns_mhdp_device *mhdp); +int cdns_hdmi_phy_power_up(struct cdns_mhdp_device *mhdp); +#endif /* _CDNS_MHDP_PHY_H */ diff --git a/drivers/gpu/drm/imx/sec_mipi_dphy_ln14lpp.h b/drivers/gpu/drm/imx/sec_mipi_dphy_ln14lpp.h new file mode 100644 index 000000000000..b302ed064e25 --- /dev/null +++ b/drivers/gpu/drm/imx/sec_mipi_dphy_ln14lpp.h @@ -0,0 +1,227 @@ +/* + * Copyright 2018 NXP + * + * 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. + */ + +#ifndef __SEC_DSIM_DPHY_LN14LPP_H__ +#define __SEC_DSIM_DPHY_LN14LPP_H__ + +#include <drm/bridge/sec_mipi_dsim.h> + +/* descending order based on 'bit_clk' value */ +static const struct sec_mipi_dsim_dphy_timing dphy_timing_ln14lpp_v1p2[] = { + { DSIM_DPHY_TIMING(2100, 19, 91, 22, 19, 20, 35, 22, 15, 26), }, + { DSIM_DPHY_TIMING(2090, 19, 91, 22, 19, 19, 35, 22, 15, 26), }, + { DSIM_DPHY_TIMING(2080, 19, 91, 21, 18, 19, 35, 22, 15, 26), }, + { DSIM_DPHY_TIMING(2070, 18, 90, 21, 18, 19, 35, 22, 15, 25), }, + { DSIM_DPHY_TIMING(2060, 18, 90, 21, 18, 19, 34, 22, 15, 25), }, + { DSIM_DPHY_TIMING(2050, 18, 89, 21, 18, 19, 34, 22, 15, 25), }, + { DSIM_DPHY_TIMING(2040, 18, 89, 21, 18, 19, 34, 21, 15, 25), }, + { DSIM_DPHY_TIMING(2030, 18, 88, 21, 18, 19, 34, 21, 15, 25), }, + { DSIM_DPHY_TIMING(2020, 18, 88, 21, 18, 19, 34, 21, 15, 25), }, + { DSIM_DPHY_TIMING(2010, 18, 87, 21, 18, 19, 34, 21, 15, 25), }, + { DSIM_DPHY_TIMING(2000, 18, 87, 21, 18, 19, 33, 21, 15, 25), }, + { DSIM_DPHY_TIMING(1990, 18, 87, 21, 18, 18, 33, 21, 14, 24), }, + { DSIM_DPHY_TIMING(1980, 18, 86, 21, 18, 18, 33, 21, 14, 24), }, + { DSIM_DPHY_TIMING(1970, 17, 86, 21, 17, 18, 33, 21, 14, 24), }, + { DSIM_DPHY_TIMING(1960, 17, 85, 21, 17, 18, 33, 21, 14, 24), }, + { DSIM_DPHY_TIMING(1950, 17, 85, 21, 17, 18, 32, 21, 14, 24), }, + { DSIM_DPHY_TIMING(1940, 17, 84, 20, 17, 18, 32, 21, 14, 24), }, + { DSIM_DPHY_TIMING(1930, 17, 84, 20, 17, 18, 32, 20, 14, 24), }, + { DSIM_DPHY_TIMING(1920, 17, 84, 20, 17, 18, 32, 20, 14, 24), }, + { DSIM_DPHY_TIMING(1910, 17, 83, 20, 17, 18, 32, 20, 14, 23), }, + { DSIM_DPHY_TIMING(1900, 17, 83, 20, 17, 18, 32, 20, 14, 23), }, + { DSIM_DPHY_TIMING(1890, 17, 82, 20, 17, 18, 31, 20, 14, 23), }, + { DSIM_DPHY_TIMING(1880, 17, 82, 20, 17, 17, 31, 20, 14, 23), }, + { DSIM_DPHY_TIMING(1870, 17, 81, 20, 17, 17, 31, 20, 14, 23), }, + { DSIM_DPHY_TIMING(1860, 16, 81, 20, 17, 17, 31, 20, 13, 23), }, + { DSIM_DPHY_TIMING(1850, 16, 80, 20, 16, 17, 31, 20, 13, 23), }, + { DSIM_DPHY_TIMING(1840, 16, 80, 20, 16, 17, 30, 20, 13, 23), }, + { DSIM_DPHY_TIMING(1830, 16, 80, 20, 16, 17, 30, 20, 13, 22), }, + { DSIM_DPHY_TIMING(1820, 16, 79, 20, 16, 17, 30, 19, 13, 22), }, + { DSIM_DPHY_TIMING(1810, 16, 79, 19, 16, 17, 30, 19, 13, 22), }, + { DSIM_DPHY_TIMING(1800, 16, 78, 19, 16, 17, 30, 19, 13, 22), }, + { DSIM_DPHY_TIMING(1790, 16, 78, 19, 16, 17, 30, 19, 13, 22), }, + { DSIM_DPHY_TIMING(1780, 16, 77, 19, 16, 16, 29, 19, 13, 22), }, + { DSIM_DPHY_TIMING(1770, 16, 77, 19, 16, 16, 29, 19, 13, 22), }, + { DSIM_DPHY_TIMING(1760, 16, 77, 19, 16, 16, 29, 19, 13, 22), }, + { DSIM_DPHY_TIMING(1750, 15, 76, 19, 16, 16, 29, 19, 13, 21), }, + { DSIM_DPHY_TIMING(1740, 15, 76, 19, 15, 16, 29, 19, 13, 21), }, + { DSIM_DPHY_TIMING(1730, 15, 75, 19, 15, 16, 28, 19, 12, 21), }, + { DSIM_DPHY_TIMING(1720, 15, 75, 19, 15, 16, 28, 19, 12, 21), }, + { DSIM_DPHY_TIMING(1710, 15, 74, 19, 15, 16, 28, 18, 12, 21), }, + { DSIM_DPHY_TIMING(1700, 15, 74, 19, 15, 16, 28, 18, 12, 21), }, + { DSIM_DPHY_TIMING(1690, 15, 73, 19, 15, 16, 28, 18, 12, 21), }, + { DSIM_DPHY_TIMING(1680, 15, 73, 18, 15, 16, 28, 18, 12, 21), }, + { DSIM_DPHY_TIMING(1670, 15, 73, 18, 15, 15, 27, 18, 12, 20), }, + { DSIM_DPHY_TIMING(1660, 15, 72, 18, 15, 15, 27, 18, 12, 20), }, + { DSIM_DPHY_TIMING(1650, 14, 72, 18, 15, 15, 27, 18, 12, 20), }, + { DSIM_DPHY_TIMING(1640, 14, 71, 18, 15, 15, 27, 18, 12, 20), }, + { DSIM_DPHY_TIMING(1630, 14, 71, 18, 15, 15, 27, 18, 12, 20), }, + { DSIM_DPHY_TIMING(1620, 14, 70, 18, 14, 15, 26, 18, 12, 20), }, + { DSIM_DPHY_TIMING(1610, 14, 70, 18, 14, 15, 26, 17, 12, 20), }, + { DSIM_DPHY_TIMING(1600, 14, 70, 18, 14, 15, 26, 17, 12, 20), }, + { DSIM_DPHY_TIMING(1590, 14, 69, 18, 14, 15, 26, 17, 11, 19), }, + { DSIM_DPHY_TIMING(1580, 14, 69, 18, 14, 15, 26, 17, 11, 19), }, + { DSIM_DPHY_TIMING(1570, 14, 68, 18, 14, 15, 26, 17, 11, 19), }, + { DSIM_DPHY_TIMING(1560, 14, 68, 18, 14, 14, 25, 17, 11, 19), }, + { DSIM_DPHY_TIMING(1550, 14, 67, 18, 14, 14, 25, 17, 11, 19), }, + { DSIM_DPHY_TIMING(1540, 13, 67, 17, 14, 14, 25, 17, 11, 19), }, + { DSIM_DPHY_TIMING(1530, 13, 66, 17, 14, 14, 25, 17, 11, 19), }, + { DSIM_DPHY_TIMING(1520, 13, 66, 17, 14, 14, 25, 17, 11, 19), }, + { DSIM_DPHY_TIMING(1510, 13, 66, 17, 13, 14, 24, 17, 11, 18), }, + { DSIM_DPHY_TIMING(1500, 13, 65, 17, 13, 14, 24, 16, 11, 18), }, + { DSIM_DPHY_TIMING(1490, 13, 65, 17, 13, 14, 24, 16, 11, 18), }, + { DSIM_DPHY_TIMING(1480, 13, 64, 17, 13, 14, 24, 16, 11, 18), }, + { DSIM_DPHY_TIMING(1470, 13, 64, 17, 13, 14, 24, 16, 11, 18), }, + { DSIM_DPHY_TIMING(1460, 13, 63, 17, 13, 13, 24, 16, 10, 18), }, + { DSIM_DPHY_TIMING(1450, 13, 63, 17, 13, 13, 23, 16, 10, 18), }, + { DSIM_DPHY_TIMING(1440, 13, 63, 17, 13, 13, 23, 16, 10, 18), }, + { DSIM_DPHY_TIMING(1430, 12, 62, 17, 13, 13, 23, 16, 10, 17), }, + { DSIM_DPHY_TIMING(1420, 12, 62, 17, 13, 13, 23, 16, 10, 17), }, + { DSIM_DPHY_TIMING(1410, 12, 61, 16, 13, 13, 23, 16, 10, 17), }, + { DSIM_DPHY_TIMING(1400, 12, 61, 16, 13, 13, 23, 16, 10, 17), }, + { DSIM_DPHY_TIMING(1390, 12, 60, 16, 12, 13, 22, 15, 10, 17), }, + { DSIM_DPHY_TIMING(1380, 12, 60, 16, 12, 13, 22, 15, 10, 17), }, + { DSIM_DPHY_TIMING(1370, 12, 59, 16, 12, 13, 22, 15, 10, 17), }, + { DSIM_DPHY_TIMING(1360, 12, 59, 16, 12, 13, 22, 15, 10, 17), }, + { DSIM_DPHY_TIMING(1350, 12, 59, 16, 12, 12, 22, 15, 10, 16), }, + { DSIM_DPHY_TIMING(1340, 12, 58, 16, 12, 12, 21, 15, 10, 16), }, + { DSIM_DPHY_TIMING(1330, 11, 58, 16, 12, 12, 21, 15, 9, 16), }, + { DSIM_DPHY_TIMING(1320, 11, 57, 16, 12, 12, 21, 15, 9, 16), }, + { DSIM_DPHY_TIMING(1310, 11, 57, 16, 12, 12, 21, 15, 9, 16), }, + { DSIM_DPHY_TIMING(1300, 11, 56, 16, 12, 12, 21, 15, 9, 16), }, + { DSIM_DPHY_TIMING(1290, 11, 56, 16, 12, 12, 21, 15, 9, 16), }, + { DSIM_DPHY_TIMING(1280, 11, 56, 15, 11, 12, 20, 14, 9, 16), }, + { DSIM_DPHY_TIMING(1270, 11, 55, 15, 11, 12, 20, 14, 9, 15), }, + { DSIM_DPHY_TIMING(1260, 11, 55, 15, 11, 12, 20, 14, 9, 15), }, + { DSIM_DPHY_TIMING(1250, 11, 54, 15, 11, 11, 20, 14, 9, 15), }, + { DSIM_DPHY_TIMING(1240, 11, 54, 15, 11, 11, 20, 14, 9, 15), }, + { DSIM_DPHY_TIMING(1230, 11, 53, 15, 11, 11, 19, 14, 9, 15), }, + { DSIM_DPHY_TIMING(1220, 10, 53, 15, 11, 11, 19, 14, 9, 15), }, + { DSIM_DPHY_TIMING(1210, 10, 52, 15, 11, 11, 19, 14, 9, 15), }, + { DSIM_DPHY_TIMING(1200, 10, 52, 15, 11, 11, 19, 14, 9, 15), }, + { DSIM_DPHY_TIMING(1190, 10, 52, 15, 11, 11, 19, 14, 8, 14), }, + { DSIM_DPHY_TIMING(1180, 10, 51, 15, 11, 11, 19, 13, 8, 14), }, + { DSIM_DPHY_TIMING(1170, 10, 51, 15, 10, 11, 18, 13, 8, 14), }, + { DSIM_DPHY_TIMING(1160, 10, 50, 15, 10, 11, 18, 13, 8, 14), }, + { DSIM_DPHY_TIMING(1150, 10, 50, 15, 10, 11, 18, 13, 8, 14), }, + { DSIM_DPHY_TIMING(1140, 10, 49, 14, 10, 10, 18, 13, 8, 14), }, + { DSIM_DPHY_TIMING(1130, 10, 49, 14, 10, 10, 18, 13, 8, 14), }, + { DSIM_DPHY_TIMING(1120, 10, 49, 14, 10, 10, 17, 13, 8, 14), }, + { DSIM_DPHY_TIMING(1110, 9, 48, 14, 10, 10, 17, 13, 8, 13), }, + { DSIM_DPHY_TIMING(1100, 9, 48, 14, 10, 10, 17, 13, 8, 13), }, + { DSIM_DPHY_TIMING(1090, 9, 47, 14, 10, 10, 17, 13, 8, 13), }, + { DSIM_DPHY_TIMING(1080, 9, 47, 14, 10, 10, 17, 13, 8, 13), }, + { DSIM_DPHY_TIMING(1070, 9, 46, 14, 10, 10, 17, 12, 8, 13), }, + { DSIM_DPHY_TIMING(1060, 9, 46, 14, 10, 10, 16, 12, 7, 13), }, + { DSIM_DPHY_TIMING(1050, 9, 45, 14, 9, 10, 16, 12, 7, 13), }, + { DSIM_DPHY_TIMING(1040, 9, 45, 14, 9, 10, 16, 12, 7, 13), }, + { DSIM_DPHY_TIMING(1030, 9, 45, 14, 9, 9, 16, 12, 7, 12), }, + { DSIM_DPHY_TIMING(1020, 9, 44, 14, 9, 9, 16, 12, 7, 12), }, + { DSIM_DPHY_TIMING(1010, 8, 44, 13, 9, 9, 15, 12, 7, 12), }, + { DSIM_DPHY_TIMING(1000, 8, 43, 13, 9, 9, 15, 12, 7, 12), }, + { DSIM_DPHY_TIMING( 990, 8, 43, 13, 9, 9, 15, 12, 7, 12), }, + { DSIM_DPHY_TIMING( 980, 8, 42, 13, 9, 9, 15, 12, 7, 12), }, + { DSIM_DPHY_TIMING( 970, 8, 42, 13, 9, 9, 15, 12, 7, 12), }, + { DSIM_DPHY_TIMING( 960, 8, 42, 13, 9, 9, 15, 11, 7, 12), }, + { DSIM_DPHY_TIMING( 950, 8, 41, 13, 9, 9, 14, 11, 7, 11), }, + { DSIM_DPHY_TIMING( 940, 8, 41, 13, 8, 9, 14, 11, 7, 11), }, + { DSIM_DPHY_TIMING( 930, 8, 40, 13, 8, 8, 14, 11, 6, 11), }, + { DSIM_DPHY_TIMING( 920, 8, 40, 13, 8, 8, 14, 11, 6, 11), }, + { DSIM_DPHY_TIMING( 910, 8, 39, 13, 8, 8, 14, 11, 6, 11), }, + { DSIM_DPHY_TIMING( 900, 7, 39, 13, 8, 8, 13, 11, 6, 11), }, + { DSIM_DPHY_TIMING( 890, 7, 38, 13, 8, 8, 13, 11, 6, 11), }, + { DSIM_DPHY_TIMING( 880, 7, 38, 12, 8, 8, 13, 11, 6, 11), }, + { DSIM_DPHY_TIMING( 870, 7, 38, 12, 8, 8, 13, 11, 6, 10), }, + { DSIM_DPHY_TIMING( 860, 7, 37, 12, 8, 8, 13, 11, 6, 10), }, + { DSIM_DPHY_TIMING( 850, 7, 37, 12, 8, 8, 13, 10, 6, 10), }, + { DSIM_DPHY_TIMING( 840, 7, 36, 12, 8, 8, 12, 10, 6, 10), }, + { DSIM_DPHY_TIMING( 830, 7, 36, 12, 8, 8, 12, 10, 6, 10), }, + { DSIM_DPHY_TIMING( 820, 7, 35, 12, 7, 7, 12, 10, 6, 10), }, + { DSIM_DPHY_TIMING( 810, 7, 35, 12, 7, 7, 12, 10, 6, 10), }, + { DSIM_DPHY_TIMING( 800, 7, 35, 12, 7, 7, 12, 10, 6, 10), }, + { DSIM_DPHY_TIMING( 790, 6, 34, 12, 7, 7, 11, 10, 5, 9), }, + { DSIM_DPHY_TIMING( 780, 6, 34, 12, 7, 7, 11, 10, 5, 9), }, + { DSIM_DPHY_TIMING( 770, 6, 33, 12, 7, 7, 11, 10, 5, 9), }, + { DSIM_DPHY_TIMING( 760, 6, 33, 12, 7, 7, 11, 10, 5, 9), }, + { DSIM_DPHY_TIMING( 750, 6, 32, 12, 7, 7, 11, 9, 5, 9), }, + { DSIM_DPHY_TIMING( 740, 6, 32, 11, 7, 7, 11, 9, 5, 9), }, + { DSIM_DPHY_TIMING( 730, 6, 31, 11, 7, 7, 10, 9, 5, 9), }, + { DSIM_DPHY_TIMING( 720, 6, 31, 11, 7, 6, 10, 9, 5, 9), }, + { DSIM_DPHY_TIMING( 710, 6, 31, 11, 6, 6, 10, 9, 5, 8), }, + { DSIM_DPHY_TIMING( 700, 6, 30, 11, 6, 6, 10, 9, 5, 8), }, + { DSIM_DPHY_TIMING( 690, 5, 30, 11, 6, 6, 10, 9, 5, 8), }, + { DSIM_DPHY_TIMING( 680, 5, 29, 11, 6, 6, 9, 9, 5, 8), }, + { DSIM_DPHY_TIMING( 670, 5, 29, 11, 6, 6, 9, 9, 5, 8), }, + { DSIM_DPHY_TIMING( 660, 5, 28, 11, 6, 6, 9, 9, 4, 8), }, + { DSIM_DPHY_TIMING( 650, 5, 28, 11, 6, 6, 9, 9, 4, 8), }, + { DSIM_DPHY_TIMING( 640, 5, 28, 11, 6, 6, 9, 8, 4, 8), }, + { DSIM_DPHY_TIMING( 630, 5, 27, 11, 6, 6, 9, 8, 4, 7), }, + { DSIM_DPHY_TIMING( 620, 5, 27, 11, 6, 6, 8, 8, 4, 7), }, + { DSIM_DPHY_TIMING( 610, 5, 26, 10, 6, 5, 8, 8, 4, 7), }, + { DSIM_DPHY_TIMING( 600, 5, 26, 10, 6, 5, 8, 8, 4, 7), }, + { DSIM_DPHY_TIMING( 590, 5, 25, 10, 5, 5, 8, 8, 4, 7), }, + { DSIM_DPHY_TIMING( 580, 4, 25, 10, 5, 5, 8, 8, 4, 7), }, + { DSIM_DPHY_TIMING( 570, 4, 24, 10, 5, 5, 7, 8, 4, 7), }, + { DSIM_DPHY_TIMING( 560, 4, 24, 10, 5, 5, 7, 8, 4, 7), }, + { DSIM_DPHY_TIMING( 550, 4, 24, 10, 5, 5, 7, 8, 4, 6), }, + { DSIM_DPHY_TIMING( 540, 4, 23, 10, 5, 5, 7, 8, 4, 6), }, + { DSIM_DPHY_TIMING( 530, 4, 23, 10, 5, 5, 7, 7, 3, 6), }, + { DSIM_DPHY_TIMING( 520, 4, 22, 10, 5, 5, 7, 7, 3, 6), }, + { DSIM_DPHY_TIMING( 510, 4, 22, 10, 5, 5, 6, 7, 3, 6), }, + { DSIM_DPHY_TIMING( 500, 4, 21, 10, 5, 4, 6, 7, 3, 6), }, + { DSIM_DPHY_TIMING( 490, 4, 21, 10, 5, 4, 6, 7, 3, 6), }, + { DSIM_DPHY_TIMING( 480, 4, 21, 9, 4, 4, 6, 7, 3, 6), }, + { DSIM_DPHY_TIMING( 470, 3, 20, 9, 4, 4, 6, 7, 3, 5), }, + { DSIM_DPHY_TIMING( 460, 3, 20, 9, 4, 4, 5, 7, 3, 5), }, + { DSIM_DPHY_TIMING( 450, 3, 19, 9, 4, 4, 5, 7, 3, 5), }, + { DSIM_DPHY_TIMING( 440, 3, 19, 9, 4, 4, 5, 7, 3, 5), }, + { DSIM_DPHY_TIMING( 430, 3, 18, 9, 4, 4, 5, 7, 3, 5), }, + { DSIM_DPHY_TIMING( 420, 3, 18, 9, 4, 4, 5, 6, 3, 5), }, + { DSIM_DPHY_TIMING( 410, 3, 17, 9, 4, 4, 5, 6, 3, 5), }, + { DSIM_DPHY_TIMING( 400, 3, 17, 9, 4, 3, 4, 6, 3, 5), }, + { DSIM_DPHY_TIMING( 390, 3, 17, 9, 4, 3, 4, 6, 2, 4), }, + { DSIM_DPHY_TIMING( 380, 3, 16, 9, 4, 3, 4, 6, 2, 4), }, + { DSIM_DPHY_TIMING( 370, 2, 16, 9, 3, 3, 4, 6, 2, 4), }, + { DSIM_DPHY_TIMING( 360, 2, 15, 9, 3, 3, 4, 6, 2, 4), }, + { DSIM_DPHY_TIMING( 350, 2, 15, 9, 3, 3, 3, 6, 2, 4), }, + { DSIM_DPHY_TIMING( 340, 2, 14, 8, 3, 3, 3, 6, 2, 4), }, + { DSIM_DPHY_TIMING( 330, 2, 14, 8, 3, 3, 3, 6, 2, 4), }, + { DSIM_DPHY_TIMING( 320, 2, 14, 8, 3, 3, 3, 5, 2, 4), }, + { DSIM_DPHY_TIMING( 310, 2, 13, 8, 3, 3, 3, 5, 2, 3), }, + { DSIM_DPHY_TIMING( 300, 2, 13, 8, 3, 3, 3, 5, 2, 3), }, + { DSIM_DPHY_TIMING( 290, 2, 12, 8, 3, 2, 2, 5, 2, 3), }, + { DSIM_DPHY_TIMING( 280, 2, 12, 8, 3, 2, 2, 5, 2, 3), }, + { DSIM_DPHY_TIMING( 270, 2, 11, 8, 3, 2, 2, 5, 2, 3), }, + { DSIM_DPHY_TIMING( 260, 1, 11, 8, 3, 2, 2, 5, 1, 3), }, + { DSIM_DPHY_TIMING( 250, 1, 10, 8, 2, 2, 2, 5, 1, 3), }, + { DSIM_DPHY_TIMING( 240, 1, 9, 8, 2, 2, 1, 5, 1, 3), }, + { DSIM_DPHY_TIMING( 230, 1, 8, 8, 2, 2, 1, 5, 1, 2), }, + { DSIM_DPHY_TIMING( 220, 1, 8, 8, 2, 2, 1, 5, 1, 2), }, + { DSIM_DPHY_TIMING( 210, 1, 7, 7, 2, 2, 1, 4, 1, 2), }, + { DSIM_DPHY_TIMING( 200, 1, 7, 7, 2, 2, 1, 4, 1, 2), }, + { DSIM_DPHY_TIMING( 190, 1, 7, 7, 2, 1, 1, 4, 1, 2), }, + { DSIM_DPHY_TIMING( 180, 1, 6, 7, 2, 1, 0, 4, 1, 2), }, + { DSIM_DPHY_TIMING( 170, 1, 6, 7, 2, 1, 0, 4, 1, 2), }, + { DSIM_DPHY_TIMING( 160, 1, 6, 7, 2, 1, 0, 4, 1, 2), }, + { DSIM_DPHY_TIMING( 150, 0, 5, 7, 2, 1, 0, 4, 1, 1), }, + { DSIM_DPHY_TIMING( 140, 0, 5, 7, 1, 1, 0, 4, 1, 1), }, + { DSIM_DPHY_TIMING( 130, 0, 4, 7, 1, 1, 0, 4, 0, 1), }, + { DSIM_DPHY_TIMING( 120, 0, 4, 7, 1, 1, 0, 4, 0, 1), }, + { DSIM_DPHY_TIMING( 110, 0, 3, 7, 1, 0, 0, 4, 0, 1), }, + { DSIM_DPHY_TIMING( 100, 0, 3, 7, 1, 0, 0, 3, 0, 1), }, + { DSIM_DPHY_TIMING( 90, 0, 2, 7, 1, 0, 0, 3, 0, 1), }, + { DSIM_DPHY_TIMING( 80, 0, 2, 6, 1, 0, 0, 3, 0, 1), }, +}; + +#endif diff --git a/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c b/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c new file mode 100644 index 000000000000..4542b1ddc219 --- /dev/null +++ b/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c @@ -0,0 +1,583 @@ +/* + * Samsung MIPI DSI Host Controller on IMX + * + * Copyright 2018-2022 NXP + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/busfreq-imx.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/mfd/syscon.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> +#include <drm/bridge/sec_mipi_dsim.h> +#include <drm/drm_bridge.h> +#include <drm/drm_encoder.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_simple_kms_helper.h> + +#include "imx-drm.h" +#include "sec_mipi_dphy_ln14lpp.h" +#include "sec_mipi_pll_1432x.h" + +#define DRIVER_NAME "imx_sec_dsim_drv" + +/* fixed phy ref clk rate */ +#define PHY_REF_CLK 12000 + +struct imx_sec_dsim_device { + struct device *dev; + void __iomem *base; + int irq; + struct clk *clk_cfg; + struct clk *clk_pllref; + struct drm_encoder encoder; + + struct reset_control *soft_resetn; + struct reset_control *clk_enable; + struct reset_control *mipi_reset; + + atomic_t rpm_suspended; + + bool enabled; +}; + +#define enc_to_dsim(enc) container_of(enc, struct imx_sec_dsim_device, encoder) + +static struct imx_sec_dsim_device *dsim_dev; + +#if CONFIG_PM +static int imx_sec_dsim_runtime_suspend(struct device *dev); +static int imx_sec_dsim_runtime_resume(struct device *dev); +#else +static int imx_sec_dsim_runtime_suspend(struct device *dev) +{ + return 0; +} +static int imx_sec_dsim_runtime_resume(struct device *dev) +{ + return 0; +} +#endif + +static int sec_dsim_rstc_reset(struct reset_control *rstc, bool assert) +{ + int ret; + + if (!rstc) + return 0; + + ret = assert ? reset_control_assert(rstc) : + reset_control_deassert(rstc); + + return ret; +} + +static struct drm_crtc * +imx_sec_dsim_encoder_get_new_crtc(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + struct drm_connector *connector; + struct drm_connector_state *conn_state; + + connector = drm_atomic_get_new_connector_for_encoder(state, encoder); + if (!connector) + return NULL; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (!conn_state) + return NULL; + + return conn_state->crtc; +} + +static void +imx_sec_dsim_encoder_atomic_enable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + int ret; + struct imx_sec_dsim_device *dsim_dev = enc_to_dsim(encoder); + struct drm_crtc *crtc; + struct drm_crtc_state *old_crtc_state; + + crtc = imx_sec_dsim_encoder_get_new_crtc(encoder, state); + if (!crtc) { + dev_err(dsim_dev->dev, "encoder is enabling without CRTC\n"); + return; + } + + old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc); + /* Coming back from self refresh, nothing to do. */ + if (old_crtc_state && old_crtc_state->self_refresh_active && + dsim_dev->enabled) + return; + + if (dsim_dev->enabled) + return; + + pm_runtime_get_sync(dsim_dev->dev); + + ret = sec_dsim_rstc_reset(dsim_dev->mipi_reset, false); + if (ret) + dev_err(dsim_dev->dev, "deassert mipi_reset failed\n"); + + dsim_dev->enabled = true; +} + +static void +imx_sec_dsim_encoder_atomic_disable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + int ret; + struct imx_sec_dsim_device *dsim_dev = enc_to_dsim(encoder); + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + + crtc = imx_sec_dsim_encoder_get_new_crtc(encoder, state); + /* No CRTC means we're doing a full shutdown. */ + if (!crtc) + goto disable; + + new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + /* Don't do disablement operation if we're entering PSR. */ + if (!new_crtc_state || new_crtc_state->self_refresh_active) + return; + +disable: + if (!dsim_dev->enabled) + return; + + ret = sec_dsim_rstc_reset(dsim_dev->mipi_reset, true); + if (ret) + dev_err(dsim_dev->dev, "deassert mipi_reset failed\n"); + + pm_runtime_put_sync(dsim_dev->dev); + + dsim_dev->enabled = false; +} + +static int imx_sec_dsim_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + int ret; + struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); + struct drm_bridge *bridge = drm_bridge_chain_get_first_bridge(encoder); + struct drm_bridge_state *bridge_state; + struct drm_bus_cfg *input_bus_cfg; + + /* check pll out */ + ret = sec_mipi_dsim_check_pll_out(bridge->driver_private, + adjusted_mode); + if (ret) + return ret; + + bridge_state = drm_atomic_get_new_bridge_state(crtc_state->state, + bridge); + + if (WARN_ON(!bridge_state)) + return -ENODEV; + + input_bus_cfg = &bridge_state->input_bus_cfg; + + imx_crtc_state->bus_format = input_bus_cfg->format; + imx_crtc_state->bus_flags = input_bus_cfg->flags; + + return 0; +} + +static const struct drm_encoder_helper_funcs imx_sec_dsim_encoder_helper_funcs = { + .atomic_enable = imx_sec_dsim_encoder_atomic_enable, + .atomic_disable = imx_sec_dsim_encoder_atomic_disable, + .atomic_check = imx_sec_dsim_encoder_atomic_check, +}; + +static int sec_dsim_determine_pll_ref_rate(u32 *rate, u32 min, u32 max) +{ + int ret; + struct device *dev = dsim_dev->dev; + u32 req_rate = PHY_REF_CLK; + unsigned long get_rate; + + ret = of_property_read_u32(dev->of_node, "pref-rate", &req_rate); + if (!ret) { + if (req_rate != clamp(req_rate, min, max)) { + dev_warn(dev, "invalid requested PLL ref clock rate : %u\n", req_rate); + req_rate = PHY_REF_CLK; + dev_warn(dev, "use default clock rate : %u\n", req_rate); + } + } + +set_rate: + ret = clk_set_rate(dsim_dev->clk_pllref, ((unsigned long)req_rate) * 1000); + if (ret) + return ret; + + get_rate = clk_get_rate(dsim_dev->clk_pllref); + if (!get_rate) + return -EINVAL; + + /* PLL ref clock rate should be set precisely */ + if (get_rate != req_rate * 1000) { + /* default clock rate should can be set precisely */ + if (WARN_ON(unlikely(req_rate == PHY_REF_CLK))) + return -EINVAL; + + dev_warn(dev, "request rate %u cannot be satisfied\n", req_rate); + req_rate = PHY_REF_CLK; + dev_warn(dev, "use default clock rate : %u\n", req_rate); + + goto set_rate; + } + + *rate = req_rate; + + return 0; +} + +static const struct sec_mipi_dsim_plat_data imx8mm_mipi_dsim_plat_data = { + .version = 0x1060200, + .max_data_lanes = 4, + .max_data_rate = 1500000000ULL, + .dphy_pll = &pll_1432x, + .dphy_timing = dphy_timing_ln14lpp_v1p2, + .num_dphy_timing = ARRAY_SIZE(dphy_timing_ln14lpp_v1p2), + .dphy_timing_cmp = dphy_timing_default_cmp, + .mode_valid = NULL, + .determine_pll_ref_rate = sec_dsim_determine_pll_ref_rate, +}; + +static const struct of_device_id imx_sec_dsim_dt_ids[] = { + { + .compatible = "fsl,imx8mm-mipi-dsim", + .data = &imx8mm_mipi_dsim_plat_data, + }, + { + .compatible = "fsl,imx8mn-mipi-dsim", + .data = &imx8mm_mipi_dsim_plat_data, + }, + { + .compatible = "fsl,imx8mp-mipi-dsim", + .data = &imx8mm_mipi_dsim_plat_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_sec_dsim_dt_ids); + +static int sec_dsim_of_parse_resets(struct imx_sec_dsim_device *dsim) +{ + int ret; + struct device *dev = dsim->dev; + struct device_node *np = dev->of_node; + struct device_node *parent, *child; + struct of_phandle_args args; + struct reset_control *rstc; + const char *compat; + uint32_t len, rstc_num = 0; + + /* TODO: bypass resets for imx8mp platform */ + compat = of_get_property(np, "compatible", NULL); + if (unlikely(!compat)) + return -ENODEV; + + len = strlen(compat); + if (!of_compat_cmp(compat, "fsl,imx8mp-mipi-dsim", len)) + return 0; + + ret = of_parse_phandle_with_args(np, "resets", "#reset-cells", + 0, &args); + if (ret) + return ret; + + parent = args.np; + for_each_child_of_node(parent, child) { + compat = of_get_property(child, "compatible", NULL); + if (!compat) + continue; + + rstc = of_reset_control_array_get(child, false, false, true); + if (IS_ERR(rstc)) + continue; + + len = strlen(compat); + if (!of_compat_cmp("dsi,soft-resetn", compat, len)) { + dsim->soft_resetn = rstc; + rstc_num++; + } else if (!of_compat_cmp("dsi,clk-enable", compat, len)) { + dsim->clk_enable = rstc; + rstc_num++; + } else if (!of_compat_cmp("dsi,mipi-reset", compat, len)) { + dsim->mipi_reset = rstc; + rstc_num++; + } else + dev_warn(dev, "invalid dsim reset node: %s\n", compat); + } + + if (!rstc_num) { + dev_err(dev, "no invalid reset control exists\n"); + return -EINVAL; + } + + return 0; +} + +static void sec_dsim_of_put_resets(struct imx_sec_dsim_device *dsim) +{ + if (dsim->soft_resetn) + reset_control_put(dsim->soft_resetn); + + if (dsim->clk_enable) + reset_control_put(dsim->clk_enable); + + if (dsim->mipi_reset) + reset_control_put(dsim->mipi_reset); +} + +static int imx_sec_dsim_bind(struct device *dev, struct device *master, + void *data) +{ + int ret; + struct drm_device *drm_dev = data; + struct device_node *np = dev->of_node; + const struct of_device_id *of_id = of_match_device(imx_sec_dsim_dt_ids, + dev); + const struct sec_mipi_dsim_plat_data *pdata; + struct drm_encoder *encoder; + + dev_dbg(dev, "%s: dsim bind begin\n", __func__); + + if (!of_id) + return -ENODEV; + pdata = of_id->data; + + encoder = &dsim_dev->encoder; + ret = imx_drm_encoder_parse_of(drm_dev, encoder, np); + if (ret) + return ret; + + drm_encoder_helper_add(encoder, &imx_sec_dsim_encoder_helper_funcs); + + ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_DSI); + if (ret) + return ret; + + /* bind sec dsim bridge */ + ret = sec_mipi_dsim_bind(dev, master, data, encoder, + dsim_dev->base, dsim_dev->irq, pdata); + if (ret) { + dev_err(dev, "failed to bind sec dsim bridge: %d\n", ret); + drm_encoder_cleanup(encoder); + + /* If no panel or bridge connected, just return 0 + * to make component core to believe it is bound + * successfully to allow other components can be + * bound continuously, since in component core, + * it follows 'one fails, all fail'. It is useful + * when there exists multiple heads display. + */ + if (ret == -ENODEV) + return 0; + + return ret; + } + + dev_dbg(dev, "%s: dsim bind end\n", __func__); + + return 0; +} + +static void imx_sec_dsim_unbind(struct device *dev, struct device *master, + void *data) +{ + if (!dsim_dev->encoder.dev) + return; + + drm_encoder_cleanup(&dsim_dev->encoder); + + sec_mipi_dsim_unbind(dev, master, data); +} + +static const struct component_ops imx_sec_dsim_ops = { + .bind = imx_sec_dsim_bind, + .unbind = imx_sec_dsim_unbind, +}; + +static int imx_sec_dsim_probe(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + + dev_dbg(dev, "%s: dsim probe begin\n", __func__); + + dsim_dev = devm_kzalloc(dev, sizeof(*dsim_dev), GFP_KERNEL); + if (!dsim_dev) { + dev_err(dev, "Unable to allocate 'dsim_dev'\n"); + return -ENOMEM; + } + dsim_dev->dev = dev; + + dsim_dev->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dsim_dev->base)) + return PTR_ERR(dsim_dev->base); + + dsim_dev->irq = platform_get_irq(pdev, 0); + if (dsim_dev->irq < 0) + return -ENODEV; + + dsim_dev->clk_cfg = devm_clk_get(dev, "cfg"); + if (IS_ERR(dsim_dev->clk_cfg)) + return PTR_ERR(dsim_dev->clk_cfg); + + dsim_dev->clk_pllref = devm_clk_get(dev, "pll-ref"); + if (IS_ERR(dsim_dev->clk_pllref)) + return PTR_ERR(dsim_dev->clk_pllref); + + ret = sec_dsim_of_parse_resets(dsim_dev); + if (ret) + return ret; + + atomic_set(&dsim_dev->rpm_suspended, 1); + + pm_runtime_enable(dev); + + ret = component_add(dev, &imx_sec_dsim_ops); + if (ret) { + pm_runtime_disable(dev); + sec_dsim_of_put_resets(dsim_dev); + dev_err_probe(dev, ret, "Failed to add component\n"); + } + + return ret; +} + +static int imx_sec_dsim_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &imx_sec_dsim_ops); + pm_runtime_disable(&pdev->dev); + sec_dsim_of_put_resets(dsim_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int imx_sec_dsim_suspend(struct device *dev) +{ + return imx_sec_dsim_runtime_suspend(dev); +} + +static int imx_sec_dsim_resume(struct device *dev) +{ + return imx_sec_dsim_runtime_resume(dev); +} +#endif + +#ifdef CONFIG_PM +static int imx_sec_dsim_runtime_suspend(struct device *dev) +{ + /* check sec dsim is bound or not */ + if (unlikely(!dsim_dev->encoder.dev)) + return 0; + + if (atomic_inc_return(&dsim_dev->rpm_suspended) > 1) + return 0; + + sec_mipi_dsim_suspend(dev); + + clk_disable_unprepare(dsim_dev->clk_cfg); + clk_disable_unprepare(dsim_dev->clk_pllref); + + release_bus_freq(BUS_FREQ_HIGH); + + return 0; +} + +static int imx_sec_dsim_runtime_resume(struct device *dev) +{ + int ret; + + /* check sec dsim is bound or not */ + if (unlikely(!dsim_dev->encoder.dev)) + return 0; + + if (unlikely(!atomic_read(&dsim_dev->rpm_suspended))) { + dev_warn(dsim_dev->dev, + "Unbalanced %s!\n", __func__); + return 0; + } + + if (!atomic_dec_and_test(&dsim_dev->rpm_suspended)) + return 0; + + request_bus_freq(BUS_FREQ_HIGH); + + ret = clk_prepare_enable(dsim_dev->clk_pllref); + if (WARN_ON(unlikely(ret))) + return ret; + + ret = clk_prepare_enable(dsim_dev->clk_cfg); + if (WARN_ON(unlikely(ret))) + return ret; + + ret = sec_dsim_rstc_reset(dsim_dev->soft_resetn, false); + if (ret) { + dev_err(dev, "deassert soft_resetn failed\n"); + return ret; + } + + ret = sec_dsim_rstc_reset(dsim_dev->clk_enable, true); + if (ret) { + dev_err(dev, "assert clk_enable failed\n"); + return ret; + } + + ret = sec_dsim_rstc_reset(dsim_dev->mipi_reset, false); + if (ret) { + dev_err(dev, "deassert mipi_reset failed\n"); + return ret; + } + + sec_mipi_dsim_resume(dev); + + return 0; +} +#endif + +static const struct dev_pm_ops imx_sec_dsim_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(imx_sec_dsim_suspend, + imx_sec_dsim_resume) + SET_RUNTIME_PM_OPS(imx_sec_dsim_runtime_suspend, + imx_sec_dsim_runtime_resume, + NULL) +}; + +struct platform_driver imx_sec_dsim_driver = { + .probe = imx_sec_dsim_probe, + .remove = imx_sec_dsim_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = imx_sec_dsim_dt_ids, + .pm = &imx_sec_dsim_pm_ops, + }, +}; + +module_platform_driver(imx_sec_dsim_driver); + +MODULE_DESCRIPTION("NXP i.MX MIPI DSI Host Controller driver"); +MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/imx/sec_mipi_pll_1432x.h b/drivers/gpu/drm/imx/sec_mipi_pll_1432x.h new file mode 100644 index 000000000000..c387b2facd59 --- /dev/null +++ b/drivers/gpu/drm/imx/sec_mipi_pll_1432x.h @@ -0,0 +1,49 @@ +/* + * Samsung MIPI DSIM PLL_1432X + * + * Copyright 2019 NXP + * + * 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. + */ + +#ifndef __SEC_DSIM_PLL_1432X_H__ +#define __SEC_DSIM_PLL_1432X_H__ + +#include <drm/bridge/sec_mipi_dsim.h> +/* + * DSIM PLL_1432X setting guide from spec: + * + * Fout(bitclk) = ((m + k / 65536) * Fin) / (p * 2^s), and + * p = P[5:0], m = M[9:0], s = S[2:0], k = K[15:0]; + * + * Fpref = Fin / p + * Fin: [6MHz ~ 300MHz], Fpref: [2MHz ~ 30MHz] + * + * Fvco = ((m + k / 65536) * Fin) / p + * Fvco: [1050MHz ~ 2100MHz] + * + * 1 <= P[5:0] <= 63, 64 <= M[9:0] <= 1023, + * 0 <= S[2:0] <= 5, -32768 <= K[15:0] <= 32767 + * + */ + +const struct sec_mipi_dsim_pll pll_1432x = { + .p = { .min = 1, .max = 63, }, + .m = { .min = 64, .max = 1023, }, + .s = { .min = 0, .max = 5, }, + .k = { .min = 0, .max = 32768, }, /* abs(k) */ + .fin = { .min = 6000, .max = 300000, }, /* in KHz */ + .fpref = { .min = 2000, .max = 30000, }, /* in KHz */ + .fvco = { .min = 1050000, .max = 2100000, }, /* in KHz */ +}; + +#endif + diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.c b/drivers/gpu/drm/mxsfb/mxsfb_drv.c index 86d78634a979..ced16169035b 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c +++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c @@ -263,6 +263,9 @@ static int mxsfb_load(struct drm_device *drm, goto err_vblank; } + of_property_read_u32(drm->dev->of_node, "max-memory-bandwidth", + &mxsfb->max_bw); + drm->mode_config.min_width = MXSFB_MIN_XRES; drm->mode_config.min_height = MXSFB_MIN_YRES; drm->mode_config.max_width = MXSFB_MAX_XRES; diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.h b/drivers/gpu/drm/mxsfb/mxsfb_drv.h index ddb5b0417a82..effeb1c7c764 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_drv.h +++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.h @@ -44,6 +44,7 @@ struct mxsfb_drm_private { struct drm_encoder encoder; struct drm_connector *connector; struct drm_bridge *bridge; + u32 max_bw; }; static inline struct mxsfb_drm_private * diff --git a/drivers/gpu/drm/mxsfb/mxsfb_kms.c b/drivers/gpu/drm/mxsfb/mxsfb_kms.c index 988bc4fbd78d..328857c4b556 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_kms.c +++ b/drivers/gpu/drm/mxsfb/mxsfb_kms.c @@ -8,6 +8,7 @@ * Copyright (C) 2008 Embedded Alley Solutions, Inc All Rights Reserved. */ +#include <linux/busfreq-imx.h> #include <linux/clk.h> #include <linux/io.h> #include <linux/iopoll.h> @@ -53,9 +54,19 @@ static void mxsfb_set_formats(struct mxsfb_drm_private *mxsfb, struct drm_device *drm = mxsfb->drm; const u32 format = mxsfb->crtc.primary->state->fb->format->format; u32 ctrl, ctrl1; - - DRM_DEV_DEBUG_DRIVER(drm->dev, "Using bus_format: 0x%08X\n", - bus_format); + bool bgr_format = true; + + /* Do some clean-up that we might have from a previous mode */ + ctrl = CTRL_SHIFT_DIR(1); + ctrl |= CTRL_SHIFT_NUM(0xff); + ctrl |= CTRL_SET_WORD_LENGTH(0xff); + ctrl |= CTRL_DF16; + ctrl |= CTRL_SET_BUS_WIDTH(0xff); + writel(ctrl, mxsfb->base + LCDC_CTRL + REG_CLR); + if (mxsfb->devdata->has_ctrl2) + writel(CTRL2_ODD_LINE_PATTERN(CTRL2_LINE_PATTERN_CLR) | + CTRL2_EVEN_LINE_PATTERN(CTRL2_LINE_PATTERN_CLR), + mxsfb->base + LCDC_V4_CTRL2 + REG_CLR); ctrl = CTRL_BYPASS_COUNT | CTRL_MASTER; @@ -63,20 +74,70 @@ static void mxsfb_set_formats(struct mxsfb_drm_private *mxsfb, ctrl1 = readl(mxsfb->base + LCDC_CTRL1); ctrl1 &= CTRL1_CUR_FRAME_DONE_IRQ_EN | CTRL1_CUR_FRAME_DONE_IRQ; + DRM_DEV_DEBUG_DRIVER(drm->dev, "Setting up %p4cc mode\n", &format); + switch (format) { - case DRM_FORMAT_RGB565: - dev_dbg(drm->dev, "Setting up RGB565 mode\n"); - ctrl |= CTRL_WORD_LENGTH_16; + case DRM_FORMAT_BGR565: /* BG16 */ + if (!mxsfb->devdata->has_ctrl2) + goto err; + writel(CTRL2_ODD_LINE_PATTERN(CTRL2_LINE_PATTERN_BGR) | + CTRL2_EVEN_LINE_PATTERN(CTRL2_LINE_PATTERN_BGR), + mxsfb->base + LCDC_V4_CTRL2 + REG_SET); + fallthrough; + case DRM_FORMAT_RGB565: /* RG16 */ + ctrl |= CTRL_SET_WORD_LENGTH(0); + ctrl &= ~CTRL_DF16; ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0xf); break; - case DRM_FORMAT_XRGB8888: - dev_dbg(drm->dev, "Setting up XRGB8888 mode\n"); - ctrl |= CTRL_WORD_LENGTH_24; + case DRM_FORMAT_XBGR1555: /* XB15 */ + fallthrough; + case DRM_FORMAT_ABGR1555: /* AB15 */ + if (!mxsfb->devdata->has_ctrl2) + goto err; + writel(CTRL2_ODD_LINE_PATTERN(CTRL2_LINE_PATTERN_BGR) | + CTRL2_EVEN_LINE_PATTERN(CTRL2_LINE_PATTERN_BGR), + mxsfb->base + LCDC_V4_CTRL2 + REG_SET); + fallthrough; + case DRM_FORMAT_XRGB1555: /* XR15 */ + fallthrough; + case DRM_FORMAT_ARGB1555: /* AR15 */ + ctrl |= CTRL_SET_WORD_LENGTH(0); + ctrl |= CTRL_DF16; + ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0xf); + break; + case DRM_FORMAT_RGBX8888: /* RX24 */ + fallthrough; + case DRM_FORMAT_RGBA8888: /* RA24 */ + /* RGBX - > 0RGB */ + ctrl |= CTRL_SHIFT_DIR(1); + ctrl |= CTRL_SHIFT_NUM(8); + bgr_format = false; + fallthrough; + case DRM_FORMAT_XBGR8888: /* XB24 */ + fallthrough; + case DRM_FORMAT_ABGR8888: /* AB24 */ + if (bgr_format) { + if (!mxsfb->devdata->has_ctrl2) + goto err; + writel(CTRL2_ODD_LINE_PATTERN(CTRL2_LINE_PATTERN_BGR) | + CTRL2_EVEN_LINE_PATTERN(CTRL2_LINE_PATTERN_BGR), + mxsfb->base + LCDC_V4_CTRL2 + REG_SET); + } + fallthrough; + case DRM_FORMAT_XRGB8888: /* XR24 */ + fallthrough; + case DRM_FORMAT_ARGB8888: /* AR24 */ + ctrl |= CTRL_SET_WORD_LENGTH(3); /* Do not use packed pixels = one pixel per word instead. */ ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0x7); break; + default: + goto err; } + DRM_DEV_DEBUG_DRIVER(drm->dev, "Using bus_format: 0x%08X\n", + bus_format); + switch (bus_format) { case MEDIA_BUS_FMT_RGB565_1X16: ctrl |= CTRL_BUS_WIDTH_16; @@ -93,7 +154,11 @@ static void mxsfb_set_formats(struct mxsfb_drm_private *mxsfb, } writel(ctrl1, mxsfb->base + LCDC_CTRL1); - writel(ctrl, mxsfb->base + LCDC_CTRL); + writel(ctrl, mxsfb->base + LCDC_CTRL + REG_SET); + + return; +err: + dev_err(drm->dev, "Unhandled pixel format: %p4cc\n", &format); } static void mxsfb_enable_controller(struct mxsfb_drm_private *mxsfb) @@ -112,6 +177,9 @@ static void mxsfb_enable_controller(struct mxsfb_drm_private *mxsfb) writel(reg, mxsfb->base + LCDC_V4_CTRL2); } + /* De-assert LCD Reset bit */ + writel(CTRL_LCD_RESET, mxsfb->base + LCDC_CTRL1 + REG_SET); + /* If it was disabled, re-enable the mode again */ writel(CTRL_DOTCLK_MODE, mxsfb->base + LCDC_CTRL + REG_SET); @@ -156,6 +224,11 @@ static void mxsfb_disable_controller(struct mxsfb_drm_private *mxsfb) { u32 reg; + writel(CTRL_RUN, mxsfb->base + LCDC_CTRL + REG_CLR); + + /* Assert LCD Reset bit */ + writel(CTRL_LCD_RESET, mxsfb->base + LCDC_CTRL1 + REG_CLR); + /* * Even if we disable the controller here, it will still continue * until its FIFOs are running out of data @@ -307,6 +380,28 @@ static void mxsfb_crtc_mode_set_nofb(struct mxsfb_drm_private *mxsfb, mxsfb->base + LCDC_VDCTRL4); } +static enum drm_mode_status mxsfb_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct mxsfb_drm_private *mxsfb = to_mxsfb_drm_private(crtc->dev); + u32 bpp; + u64 bw; + + if (!crtc->primary->state->fb) + bpp = 32; + else + bpp = crtc->primary->state->fb->format->cpp[0] * 8; + + bw = (u64)mode->clock * 1000; + bw = bw * mode->hdisplay * mode->vdisplay * (bpp / 8); + bw = div_u64(bw, mode->htotal * mode->vtotal); + + if (mxsfb->max_bw && bw > mxsfb->max_bw) + return MODE_BAD; + + return MODE_OK; +} + static int mxsfb_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) { @@ -351,6 +446,7 @@ static void mxsfb_crtc_atomic_enable(struct drm_crtc *crtc, u32 bus_format = 0; dma_addr_t paddr; + request_bus_freq(BUS_FREQ_HIGH); pm_runtime_get_sync(drm->dev); mxsfb_enable_axi_clk(mxsfb); @@ -415,6 +511,7 @@ static void mxsfb_crtc_atomic_disable(struct drm_crtc *crtc, mxsfb_disable_axi_clk(mxsfb); pm_runtime_put_sync(drm->dev); + release_bus_freq(BUS_FREQ_HIGH); } static int mxsfb_crtc_enable_vblank(struct drm_crtc *crtc) @@ -438,6 +535,7 @@ static void mxsfb_crtc_disable_vblank(struct drm_crtc *crtc) } static const struct drm_crtc_helper_funcs mxsfb_crtc_helper_funcs = { + .mode_valid = mxsfb_crtc_mode_valid, .atomic_check = mxsfb_crtc_atomic_check, .atomic_flush = mxsfb_crtc_atomic_flush, .atomic_enable = mxsfb_crtc_atomic_enable, @@ -474,25 +572,120 @@ static int mxsfb_plane_atomic_check(struct drm_plane *plane, plane); struct mxsfb_drm_private *mxsfb = to_mxsfb_drm_private(plane->dev); struct drm_crtc_state *crtc_state; + struct drm_framebuffer *fb = plane_state->fb; + struct drm_plane_state *old_state = plane->state; + struct drm_framebuffer *old_fb = old_state->fb; crtc_state = drm_atomic_get_new_crtc_state(state, &mxsfb->crtc); + if (plane->type == DRM_PLANE_TYPE_PRIMARY && old_fb && fb && + old_fb->pitches[0] != fb->pitches[0]) + crtc_state->mode_changed = true; + return drm_atomic_helper_check_plane_state(plane_state, crtc_state, DRM_PLANE_HELPER_NO_SCALING, DRM_PLANE_HELPER_NO_SCALING, false, true); } +static void mxsfb_set_fb_hcrop(struct mxsfb_drm_private *mxsfb, u32 src_w, u32 fb_w) +{ + u32 mask_cnt; + u32 vdctrl3, vdctrl4, transfer_count; + + if (src_w == fb_w) { + writel(0x0, mxsfb->base + HW_EPDC_PIGEON_12_0); + writel(0x0, mxsfb->base + HW_EPDC_PIGEON_12_1); + + return; + } + + transfer_count = readl(mxsfb->base + LCDC_V4_TRANSFER_COUNT); + transfer_count &= ~TRANSFER_COUNT_SET_HCOUNT(0xffff); + transfer_count |= TRANSFER_COUNT_SET_HCOUNT(fb_w); + writel(transfer_count, mxsfb->base + LCDC_V4_TRANSFER_COUNT); + + vdctrl4 = readl(mxsfb->base + LCDC_VDCTRL4); + vdctrl4 &= ~SET_DOTCLK_H_VALID_DATA_CNT(0x3ffff); + vdctrl4 |= SET_DOTCLK_H_VALID_DATA_CNT(fb_w); + writel(vdctrl4, mxsfb->base + LCDC_VDCTRL4); + + /* configure related pigeon registers */ + vdctrl3 = readl(mxsfb->base + LCDC_VDCTRL3); + mask_cnt = GET_HOR_WAIT_CNT(vdctrl3) - 5; + + writel(PIGEON_12_0_SET_STATE_MASK(0x24) | + PIGEON_12_0_SET_MASK_CNT(mask_cnt) | + PIGEON_12_0_SET_MASK_CNT_SEL(0x6) | + PIGEON_12_0_POL_ACTIVE_LOW | + PIGEON_12_0_EN, + mxsfb->base + HW_EPDC_PIGEON_12_0); + + writel(PIGEON_12_1_SET_CLR_CNT(src_w) | + PIGEON_12_1_SET_SET_CNT(0x0), + mxsfb->base + HW_EPDC_PIGEON_12_1); + + writel(0, mxsfb->base + HW_EPDC_PIGEON_12_2); +} + static void mxsfb_plane_primary_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state) { struct mxsfb_drm_private *mxsfb = to_mxsfb_drm_private(plane->dev); + struct drm_plane_state *new_pstate = drm_atomic_get_new_plane_state(state, + plane); + struct drm_plane_state *old_pstate = drm_atomic_get_old_plane_state(state, + plane); + struct drm_framebuffer *fb = new_pstate->fb; + struct drm_framebuffer *old_fb = old_pstate->fb; dma_addr_t paddr; + u32 src_off, src_w, stride, cpp = 0; paddr = mxsfb_get_fb_paddr(plane); - if (paddr) - writel(paddr, mxsfb->base + mxsfb->devdata->next_buf); + if (!paddr) + return; + + /* We will use the 'has_ctrl2' to assume IP version larger than 4 */ + if (mxsfb->devdata->has_ctrl2) { + cpp = fb->format->cpp[0]; + src_off = (new_pstate->src_y >> 16) * fb->pitches[0] + + (new_pstate->src_x >> 16) * cpp; + paddr += fb->offsets[0] + src_off; + } + + writel(paddr, mxsfb->base + mxsfb->devdata->next_buf); + + if (mxsfb->devdata->has_ctrl2 && + unlikely(drm_atomic_crtc_needs_modeset(new_pstate->crtc->state))) { + stride = DIV_ROUND_UP(fb->pitches[0], cpp); + src_w = new_pstate->src_w >> 16; + mxsfb_set_fb_hcrop(mxsfb, src_w, stride); + } + + /* Update format if changed */ + if (old_fb && old_fb->format->format != fb->format->format) { + u32 bus_format = 0; + struct drm_bridge_state *bridge_state; + + /* If there is a bridge attached to the LCDIF, use its bus format */ + if (mxsfb->bridge) { + bridge_state = + drm_atomic_get_new_bridge_state(state, + mxsfb->bridge); + bus_format = bridge_state->input_bus_cfg.format; + } + + /* If there is no bridge, use bus format from connector */ + if (!bus_format && mxsfb->connector->display_info.num_bus_formats) + bus_format = mxsfb->connector->display_info.bus_formats[0]; + + /* If all else fails, default to RGB888_1X24 */ + if (!bus_format) + bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + mxsfb_set_formats(mxsfb, bus_format); + } } static void mxsfb_plane_overlay_atomic_update(struct drm_plane *plane, @@ -585,11 +778,26 @@ static const struct drm_plane_funcs mxsfb_plane_funcs = { .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, }; -static const uint32_t mxsfb_primary_plane_formats[] = { +static const uint32_t mxsfb_primary_plane_formats_v3[] = { DRM_FORMAT_RGB565, DRM_FORMAT_XRGB8888, }; +static const uint32_t mxsfb_primary_plane_formats_v4[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_XBGR1555, +}; + static const uint32_t mxsfb_overlay_plane_formats[] = { DRM_FORMAT_XRGB4444, DRM_FORMAT_ARGB4444, @@ -613,14 +821,23 @@ int mxsfb_kms_init(struct mxsfb_drm_private *mxsfb) { struct drm_encoder *encoder = &mxsfb->encoder; struct drm_crtc *crtc = &mxsfb->crtc; + uint32_t const *plane_formats; + unsigned int format_count; int ret; + if (!mxsfb->devdata->has_ctrl2) { + plane_formats = &mxsfb_primary_plane_formats_v3[0]; + format_count = ARRAY_SIZE(mxsfb_primary_plane_formats_v3); + } else { + plane_formats = &mxsfb_primary_plane_formats_v4[0]; + format_count = ARRAY_SIZE(mxsfb_primary_plane_formats_v4); + } + drm_plane_helper_add(&mxsfb->planes.primary, &mxsfb_plane_primary_helper_funcs); ret = drm_universal_plane_init(mxsfb->drm, &mxsfb->planes.primary, 1, &mxsfb_plane_funcs, - mxsfb_primary_plane_formats, - ARRAY_SIZE(mxsfb_primary_plane_formats), + plane_formats, format_count, mxsfb_modifiers, DRM_PLANE_TYPE_PRIMARY, NULL); if (ret) diff --git a/drivers/gpu/drm/mxsfb/mxsfb_regs.h b/drivers/gpu/drm/mxsfb/mxsfb_regs.h index 694fea13e893..cc762fc3dcab 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_regs.h +++ b/drivers/gpu/drm/mxsfb/mxsfb_regs.h @@ -21,11 +21,19 @@ #define LCDC_V4_NEXT_BUF 0x50 #define LCDC_V3_CUR_BUF 0x30 #define LCDC_V3_NEXT_BUF 0x40 +#define LCDC_TIMING 0x60 #define LCDC_VDCTRL0 0x70 #define LCDC_VDCTRL1 0x80 #define LCDC_VDCTRL2 0x90 #define LCDC_VDCTRL3 0xa0 #define LCDC_VDCTRL4 0xb0 +#define LCDC_DVICTRL0 0xc0 +#define LCDC_DVICTRL1 0xd0 +#define LCDC_DVICTRL2 0xe0 +#define LCDC_DVICTRL3 0xf0 +#define LCDC_DVICTRL4 0x100 +#define LCDC_V4_DATA 0x180 +#define LCDC_V3_DATA 0x1b0 #define LCDC_V4_DEBUG0 0x1d0 #define LCDC_V3_DEBUG0 0x1f0 #define LCDC_AS_CTRL 0x210 @@ -34,31 +42,55 @@ #define LCDC_AS_CLRKEYLOW 0x240 #define LCDC_AS_CLRKEYHIGH 0x250 +#define REG_PUT(x, h, l) (((x) << (l)) & GENMASK(h, l)) +#define REG_GET(x, h, l) (((x) & GENMASK(h, l)) >> (l)) + #define CTRL_SFTRST BIT(31) #define CTRL_CLKGATE BIT(30) +#define CTRL_SHIFT_DIR(x) REG_PUT((x), 26, 26) +#define CTRL_SHIFT_NUM(x) REG_PUT((x), 25, 21) #define CTRL_BYPASS_COUNT BIT(19) #define CTRL_VSYNC_MODE BIT(18) #define CTRL_DOTCLK_MODE BIT(17) #define CTRL_DATA_SELECT BIT(16) -#define CTRL_BUS_WIDTH_16 (0 << 10) -#define CTRL_BUS_WIDTH_8 (1 << 10) -#define CTRL_BUS_WIDTH_18 (2 << 10) -#define CTRL_BUS_WIDTH_24 (3 << 10) -#define CTRL_BUS_WIDTH_MASK (0x3 << 10) -#define CTRL_WORD_LENGTH_16 (0 << 8) -#define CTRL_WORD_LENGTH_8 (1 << 8) -#define CTRL_WORD_LENGTH_18 (2 << 8) -#define CTRL_WORD_LENGTH_24 (3 << 8) +#define CTRL_INPUT_SWIZZLE(x) REG_PUT((x), 15, 14) +#define CTRL_CSC_SWIZZLE(x) REG_PUT((x), 13, 12) +#define CTRL_SET_BUS_WIDTH(x) REG_PUT((x), 11, 10) +#define CTRL_BUS_WIDTH_MASK REG_PUT((0x3), 11, 10) +#define CTRL_SET_WORD_LENGTH(x) REG_PUT((x), 9, 8) #define CTRL_MASTER BIT(5) #define CTRL_DF16 BIT(3) #define CTRL_DF18 BIT(2) #define CTRL_DF24 BIT(1) #define CTRL_RUN BIT(0) +#define CTRL_BUS_WIDTH_8 CTRL_SET_BUS_WIDTH(1) +#define CTRL_BUS_WIDTH_16 CTRL_SET_BUS_WIDTH(0) +#define CTRL_BUS_WIDTH_18 CTRL_SET_BUS_WIDTH(2) +#define CTRL_BUS_WIDTH_24 CTRL_SET_BUS_WIDTH(3) +#define CTRL_WORD_LENGTH_8 CTRL_SET_WORD_LENGTH(1) +#define CTRL_WORD_LENGTH_16 CTRL_SET_WORD_LENGTH(0) +#define CTRL_WORD_LENGTH_18 CTRL_SET_WORD_LENGTH(2) +#define CTRL_WORD_LENGTH_24 CTRL_SET_WORD_LENGTH(3) + #define CTRL1_RECOVER_ON_UNDERFLOW BIT(24) #define CTRL1_FIFO_CLEAR BIT(21) -#define CTRL1_SET_BYTE_PACKAGING(x) (((x) & 0xf) << 16) -#define CTRL1_GET_BYTE_PACKAGING(x) (((x) >> 16) & 0xf) + +/* + * BYTE_PACKAGING + * + * This bitfield is used to show which data bytes in a 32-bit word area valid. + * Default value 0xf indicates that all bytes are valid. For 8-bit transfers, + * any combination in this bitfield will mean valid data is present in the + * corresponding bytes. In the 16-bit mode, a 16-bit half-word is valid only if + * adjacent bits [1:0] or [3:2] or both are 1. A value of 0x0 will mean that + * none of the bytes are valid and should not be used. For example, set the bit + * field value to 0x7 if the display data is arranged in the 24-bit unpacked + * format (A-R-G-B where A value does not have be transmitted). + */ +#define CTRL1_SET_BYTE_PACKAGING(x) REG_PUT((x), 19, 16) +#define CTRL1_GET_BYTE_PACKAGING(x) REG_GET((x), 19, 16) + #define CTRL1_CUR_FRAME_DONE_IRQ_EN BIT(13) #define CTRL1_CUR_FRAME_DONE_IRQ BIT(9) @@ -69,10 +101,27 @@ #define CTRL2_SET_OUTSTANDING_REQS_16 (0x4 << 21) #define CTRL2_SET_OUTSTANDING_REQS_MASK (0x7 << 21) -#define TRANSFER_COUNT_SET_VCOUNT(x) (((x) & 0xffff) << 16) -#define TRANSFER_COUNT_GET_VCOUNT(x) (((x) >> 16) & 0xffff) -#define TRANSFER_COUNT_SET_HCOUNT(x) ((x) & 0xffff) -#define TRANSFER_COUNT_GET_HCOUNT(x) ((x) & 0xffff) +#define SWIZZLE_LE 0 /* Little-Endian or No swap */ +#define SWIZZLE_BE 1 /* Big-Endian or swap all */ +#define SWIZZLE_HWD 2 /* Swap half-words */ +#define SWIZZLE_HWD_BYTE 3 /* Swap bytes within each half-word */ + +#define CTRL2_ODD_LINE_PATTERN(x) REG_PUT((x), 18, 16) +#define CTRL2_EVEN_LINE_PATTERN(x) REG_PUT((x), 14, 12) +#define CTRL2_LINE_PATTERN_RGB 0 +#define CTRL2_LINE_PATTERN_RBG 1 +#define CTRL2_LINE_PATTERN_GBR 2 +#define CTRL2_LINE_PATTERN_GRB 3 +#define CTRL2_LINE_PATTERN_BRG 4 +#define CTRL2_LINE_PATTERN_BGR 5 +#define CTRL2_LINE_PATTERN_CLR 7 + +#define CTRL_LCD_RESET BIT(0) + +#define TRANSFER_COUNT_SET_VCOUNT(x) REG_PUT((x), 31, 16) +#define TRANSFER_COUNT_GET_VCOUNT(x) REG_GET((x), 31, 16) +#define TRANSFER_COUNT_SET_HCOUNT(x) REG_PUT((x), 15, 0) +#define TRANSFER_COUNT_GET_HCOUNT(x) REG_GET((x), 15, 0) #define VDCTRL0_ENABLE_PRESENT BIT(28) #define VDCTRL0_VSYNC_ACT_HIGH BIT(27) @@ -83,23 +132,23 @@ #define VDCTRL0_VSYNC_PULSE_WIDTH_UNIT BIT(20) #define VDCTRL0_HALF_LINE BIT(19) #define VDCTRL0_HALF_LINE_MODE BIT(18) -#define VDCTRL0_SET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff) -#define VDCTRL0_GET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff) +#define VDCTRL0_SET_VSYNC_PULSE_WIDTH(x) REG_PUT((x), 17, 0) +#define VDCTRL0_GET_VSYNC_PULSE_WIDTH(x) REG_GET((x), 17, 0) -#define VDCTRL2_SET_HSYNC_PERIOD(x) ((x) & 0x3ffff) -#define VDCTRL2_GET_HSYNC_PERIOD(x) ((x) & 0x3ffff) +#define VDCTRL2_SET_HSYNC_PERIOD(x) REG_PUT((x), 15, 0) +#define VDCTRL2_GET_HSYNC_PERIOD(x) REG_GET((x), 15, 0) #define VDCTRL3_MUX_SYNC_SIGNALS BIT(29) #define VDCTRL3_VSYNC_ONLY BIT(28) -#define SET_HOR_WAIT_CNT(x) (((x) & 0xfff) << 16) -#define GET_HOR_WAIT_CNT(x) (((x) >> 16) & 0xfff) -#define SET_VERT_WAIT_CNT(x) ((x) & 0xffff) -#define GET_VERT_WAIT_CNT(x) ((x) & 0xffff) +#define SET_HOR_WAIT_CNT(x) REG_PUT((x), 27, 16) +#define GET_HOR_WAIT_CNT(x) REG_GET((x), 27, 16) +#define SET_VERT_WAIT_CNT(x) REG_PUT((x), 15, 0) +#define GET_VERT_WAIT_CNT(x) REG_GET((x), 15, 0) -#define VDCTRL4_SET_DOTCLK_DLY(x) (((x) & 0x7) << 29) /* v4 only */ -#define VDCTRL4_GET_DOTCLK_DLY(x) (((x) >> 29) & 0x7) /* v4 only */ +#define VDCTRL4_SET_DOTCLK_DLY(x) REG_PUT((x), 31, 29) /* v4 only */ +#define VDCTRL4_GET_DOTCLK_DLY(x) REG_GET((x), 31, 29) /* v4 only */ #define VDCTRL4_SYNC_SIGNALS_ON BIT(18) -#define SET_DOTCLK_H_VALID_DATA_CNT(x) ((x) & 0x3ffff) +#define SET_DOTCLK_H_VALID_DATA_CNT(x) REG_PUT((x), 17, 0) #define DEBUG0_HSYNC BIT(26) #define DEBUG0_VSYNC BIT(25) @@ -121,6 +170,22 @@ #define AS_CTRL_ALPHA_CTRL_EMBEDDED (0 << 1) #define AS_CTRL_AS_ENABLE BIT(0) +/* pigeon registers for crop */ +#define HW_EPDC_PIGEON_12_0 0xb00 +#define HW_EPDC_PIGEON_12_1 0xb10 +#define HW_EPDC_PIGEON_12_2 0xb20 + +#define PIGEON_12_0_SET_STATE_MASK(x) REG_PUT((x), 31, 24) +#define PIGEON_12_0_SET_MASK_CNT(x) REG_PUT((x), 23, 12) +#define PIGEON_12_0_SET_MASK_CNT_SEL(x) REG_PUT((x), 11, 8) +#define PIGEON_12_0_SET_OFFSET(x) REG_PUT((x), 7, 4) +#define PIGEON_12_0_SET_INC_SEL(x) REG_PUT((x), 3, 2) +#define PIGEON_12_0_POL_ACTIVE_LOW BIT(1) +#define PIGEON_12_0_EN BIT(0) + +#define PIGEON_12_1_SET_CLR_CNT(x) REG_PUT((x), 31, 16) +#define PIGEON_12_1_SET_SET_CNT(x) REG_PUT((x), 15, 0) + #define MXSFB_MIN_XRES 120 #define MXSFB_MIN_YRES 120 #define MXSFB_MAX_XRES 0xffff diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 479ffdb64486..bb2eb6808a48 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -305,6 +305,15 @@ config DRM_PANEL_OLIMEX_LCD_OLINUXINO Say Y here if you want to enable support for Olimex Ltd. LCD-OLinuXino panel. +config DRM_PANEL_ONTAT_KD50G21_40NT_A1 + tristate "ON Tat Industrial Company KD50G21-40NT-A1 panel" + depends on OF + depends on BACKLIGHT_CLASS_DEVICE + select VIDEOMODE_HELPERS + help + Say Y here if you want to enable support for the ON Tat Industrial + Company KD50G21-40NT-A1 for 800x480 LCD panel + config DRM_PANEL_ORISETECH_OTM8009A tristate "Orise Technology otm8009a 480x800 dsi 2dl panel" depends on OF @@ -359,6 +368,17 @@ config DRM_PANEL_RAYDIUM_RM68200 Say Y here if you want to enable support for Raydium RM68200 720x1280 DSI video mode panel. +config DRM_PANEL_ROCKTECK_HIMAX8394F + tristate "Rocktech Himax8394f 720x1280 DSI video mode panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for Rocktech Himax8394f + TFT-LCD modules. The panel has a 720x1280 resolution and uses + 24 bit RGB per pixel. It provides a MIPI DSI interface to the host + and has a built-in LED backlight. + config DRM_PANEL_RONBO_RB070D30 tristate "Ronbo Electronics RB070D30 panel" depends on OF @@ -621,4 +641,13 @@ config DRM_PANEL_XINPENG_XPP055C272 Say Y here if you want to enable support for the Xinpeng XPP055C272 controller for 720x1280 LCD panels with MIPI/RGB/SPI system interfaces. +config DRM_PANEL_WKS_101WX001 + tristate "WKS 101WX001 parallel LCD" + depends on OF + depends on BACKLIGHT_CLASS_DEVICE + select VIDEOMODE_HELPERS + help + Say Y here if you want to enable support for the WKS + 101WX001 controller for 1280x800 LCD panel + endmenu diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index c8132050bcec..d15a67e92319 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -28,12 +28,14 @@ obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672A) += panel-novatek-nt36672a.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT39016) += panel-novatek-nt39016.o obj-$(CONFIG_DRM_PANEL_MANTIX_MLAF057WE51) += panel-mantix-mlaf057we51.o obj-$(CONFIG_DRM_PANEL_OLIMEX_LCD_OLINUXINO) += panel-olimex-lcd-olinuxino.o +obj-$(CONFIG_DRM_PANEL_ONTAT_KD50G21_40NT_A1) += panel-ontat-kd50g21-40nt-a1.o obj-$(CONFIG_DRM_PANEL_ORISETECH_OTM8009A) += panel-orisetech-otm8009a.o obj-$(CONFIG_DRM_PANEL_OSD_OSD101T2587_53TS) += panel-osd-osd101t2587-53ts.o obj-$(CONFIG_DRM_PANEL_PANASONIC_VVX10F034N00) += panel-panasonic-vvx10f034n00.o obj-$(CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN) += panel-raspberrypi-touchscreen.o obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM67191) += panel-raydium-rm67191.o obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM68200) += panel-raydium-rm68200.o +obj-$(CONFIG_DRM_PANEL_ROCKTECK_HIMAX8394F) += panel-rocktech-hx8394f.o obj-$(CONFIG_DRM_PANEL_RONBO_RB070D30) += panel-ronbo-rb070d30.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_ATNA33XC20) += panel-samsung-atna33xc20.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_DB7430) += panel-samsung-db7430.o @@ -64,3 +66,4 @@ obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o +obj-$(CONFIG_DRM_PANEL_WKS_101WX001) += panel-wks-101wx001.o diff --git a/drivers/gpu/drm/panel/panel-ontat-kd50g21-40nt-a1.c b/drivers/gpu/drm/panel/panel-ontat-kd50g21-40nt-a1.c new file mode 100644 index 000000000000..3af4ca5dd4a2 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-ontat-kd50g21-40nt-a1.c @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2022 NXP + * + * Based on drivers/gpu/drm/panel/panel-seiko-43wvf1g.c + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include <video/display_timing.h> +#include <video/videomode.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_panel.h> + +struct ontat_kd50g21_40nt_a1_panel_desc { + const struct drm_display_mode *modes; + unsigned int num_modes; + const struct display_timing *timings; + unsigned int num_timings; + + unsigned int bpc; + + /** + * @width: width (in millimeters) of the panel's active display area + * @height: height (in millimeters) of the panel's active display area + */ + struct { + unsigned int width; + unsigned int height; + } size; + + u32 bus_format; + u32 bus_flags; +}; + +struct ontat_kd50g21_40nt_a1_panel { + struct drm_panel base; + bool enabled; + bool prepared; + ktime_t prepared_time; + ktime_t unprepared_time; + const struct ontat_kd50g21_40nt_a1_panel_desc *desc; + struct regulator *supply; + struct gpio_desc *enable_gpio; +}; + +static inline struct ontat_kd50g21_40nt_a1_panel * +to_ontat_kd50g21_40nt_a1_panel(struct drm_panel *panel) +{ + return container_of(panel, struct ontat_kd50g21_40nt_a1_panel, base); +} + +static int +ontat_kd50g21_40nt_a1_panel_get_fixed_modes(struct ontat_kd50g21_40nt_a1_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + unsigned int i, num = 0; + + if (!panel->desc) + return 0; + + for (i = 0; i < panel->desc->num_timings; i++) { + const struct display_timing *dt = &panel->desc->timings[i]; + struct videomode vm; + + videomode_from_timing(dt, &vm); + mode = drm_mode_create(connector->dev); + if (!mode) { + dev_err(panel->base.dev, "failed to add mode %ux%u\n", + dt->hactive.typ, dt->vactive.typ); + continue; + } + + drm_display_mode_from_videomode(&vm, mode); + + mode->type |= DRM_MODE_TYPE_DRIVER; + + if (panel->desc->num_timings == 1) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_probed_add(connector, mode); + num++; + } + + for (i = 0; i < panel->desc->num_modes; i++) { + const struct drm_display_mode *m = &panel->desc->modes[i]; + + mode = drm_mode_duplicate(connector->dev, m); + if (!mode) { + dev_err(panel->base.dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, + drm_mode_vrefresh(m)); + continue; + } + + mode->type |= DRM_MODE_TYPE_DRIVER; + + if (panel->desc->num_modes == 1) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + + drm_mode_probed_add(connector, mode); + num++; + } + + connector->display_info.bpc = panel->desc->bpc; + connector->display_info.width_mm = panel->desc->size.width; + connector->display_info.height_mm = panel->desc->size.height; + if (panel->desc->bus_format) + drm_display_info_set_bus_formats(&connector->display_info, + &panel->desc->bus_format, 1); + connector->display_info.bus_flags = panel->desc->bus_flags; + + return num; +} + +static void ontat_kd50g21_40nt_a1_panel_wait(ktime_t start_ktime, + unsigned int min_ms) +{ + ktime_t now_ktime, min_ktime; + + if (!min_ms) + return; + + min_ktime = ktime_add(start_ktime, ms_to_ktime(min_ms)); + now_ktime = ktime_get(); + + if (ktime_before(now_ktime, min_ktime)) + msleep(ktime_to_ms(ktime_sub(min_ktime, now_ktime)) + 1); +} + +static int ontat_kd50g21_40nt_a1_panel_disable(struct drm_panel *panel) +{ + struct ontat_kd50g21_40nt_a1_panel *p = to_ontat_kd50g21_40nt_a1_panel(panel); + + if (!p->enabled) + return 0; + + msleep(114); + + p->enabled = false; + + return 0; +} + +static int ontat_kd50g21_40nt_a1_panel_unprepare(struct drm_panel *panel) +{ + struct ontat_kd50g21_40nt_a1_panel *p = to_ontat_kd50g21_40nt_a1_panel(panel); + + if (!p->prepared) + return 0; + + gpiod_set_value_cansleep(p->enable_gpio, 0); + regulator_disable(p->supply); + p->unprepared_time = ktime_get(); + + p->prepared = false; + + return 0; +} + +static int ontat_kd50g21_40nt_a1_panel_prepare(struct drm_panel *panel) +{ + struct ontat_kd50g21_40nt_a1_panel *p = to_ontat_kd50g21_40nt_a1_panel(panel); + int err; + + if (p->prepared) + return 0; + + ontat_kd50g21_40nt_a1_panel_wait(p->unprepared_time, 17); + + err = regulator_enable(p->supply); + if (err < 0) { + dev_err(panel->dev, "failed to enable power supply: %d\n", err); + return err; + } + + gpiod_set_value_cansleep(p->enable_gpio, 1); + + msleep(36); + + p->prepared_time = ktime_get(); + + p->prepared = true; + + return 0; +} + +static int ontat_kd50g21_40nt_a1_panel_enable(struct drm_panel *panel) +{ + struct ontat_kd50g21_40nt_a1_panel *p = to_ontat_kd50g21_40nt_a1_panel(panel); + + if (p->enabled) + return 0; + + msleep(163); + + ontat_kd50g21_40nt_a1_panel_wait(p->prepared_time, 0); + + p->enabled = true; + + return 0; +} + +static int +ontat_kd50g21_40nt_a1_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct ontat_kd50g21_40nt_a1_panel *p = to_ontat_kd50g21_40nt_a1_panel(panel); + + /* add hard-coded panel modes */ + return ontat_kd50g21_40nt_a1_panel_get_fixed_modes(p, connector); +} + +static int +ontat_kd50g21_40nt_a1_panel_get_timings(struct drm_panel *panel, + unsigned int num_timings, + struct display_timing *timings) +{ + struct ontat_kd50g21_40nt_a1_panel *p = to_ontat_kd50g21_40nt_a1_panel(panel); + unsigned int i; + + if (p->desc->num_timings < num_timings) + num_timings = p->desc->num_timings; + + if (timings) + for (i = 0; i < num_timings; i++) + timings[i] = p->desc->timings[i]; + + return p->desc->num_timings; +} + +static const struct drm_panel_funcs ontat_kd50g21_40nt_a1_panel_funcs = { + .disable = ontat_kd50g21_40nt_a1_panel_disable, + .unprepare = ontat_kd50g21_40nt_a1_panel_unprepare, + .prepare = ontat_kd50g21_40nt_a1_panel_prepare, + .enable = ontat_kd50g21_40nt_a1_panel_enable, + .get_modes = ontat_kd50g21_40nt_a1_panel_get_modes, + .get_timings = ontat_kd50g21_40nt_a1_panel_get_timings, +}; + +static int +ontat_kd50g21_40nt_a1_panel_probe(struct device *dev, + const struct ontat_kd50g21_40nt_a1_panel_desc *desc) +{ + struct ontat_kd50g21_40nt_a1_panel *panel; + int err; + + panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL); + if (!panel) + return -ENOMEM; + + panel->enabled = false; + panel->prepared = false; + panel->desc = desc; + + panel->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(panel->supply)) + return PTR_ERR(panel->supply); + + panel->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(panel->enable_gpio)) { + err = PTR_ERR(panel->enable_gpio); + if (err != -EPROBE_DEFER) + dev_err(dev, "failed to request GPIO: %d\n", err); + return err; + } + + dev_set_drvdata(dev, panel); + + drm_panel_init(&panel->base, dev, &ontat_kd50g21_40nt_a1_panel_funcs, + DRM_MODE_CONNECTOR_DPI); + + err = drm_panel_of_backlight(&panel->base); + if (err) + return err; + + drm_panel_add(&panel->base); + + return 0; +} + +static int ontat_kd50g21_40nt_a1_panel_remove(struct platform_device *pdev) +{ + struct ontat_kd50g21_40nt_a1_panel *panel = platform_get_drvdata(pdev); + + drm_panel_remove(&panel->base); + drm_panel_disable(&panel->base); + drm_panel_unprepare(&panel->base); + + return 0; +} + +static void ontat_kd50g21_40nt_a1_panel_shutdown(struct platform_device *pdev) +{ + struct ontat_kd50g21_40nt_a1_panel *panel = platform_get_drvdata(pdev); + + drm_panel_disable(&panel->base); + drm_panel_unprepare(&panel->base); +} + +static const struct display_timing ontat_kd50g21_40nt_a1_timing = { + .pixelclock = { 30000000, 30000000, 30000000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 40, 40, 40 }, + .hback_porch = { 40, 40, 40 }, + .hsync_len = { 48, 48, 48 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 13, 13, 13 }, + .vback_porch = { 29, 29, 29 }, + .vsync_len = { 3, 3, 3 }, + .flags = DISPLAY_FLAGS_VSYNC_LOW | DISPLAY_FLAGS_HSYNC_LOW, +}; + +static const struct ontat_kd50g21_40nt_a1_panel_desc ontat_kd50g21_40nt_a1 = { + .timings = &ontat_kd50g21_40nt_a1_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 108, + .height = 65, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE, +}; + +static const struct of_device_id platform_of_match[] = { + { + .compatible = "ontat,kd50g21-40nt-a1", + .data = &ontat_kd50g21_40nt_a1, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, platform_of_match); + +static int ontat_kd50g21_40nt_a1_panel_platform_probe(struct platform_device *pdev) +{ + const struct of_device_id *id; + + id = of_match_node(platform_of_match, pdev->dev.of_node); + if (!id) + return -ENODEV; + + return ontat_kd50g21_40nt_a1_panel_probe(&pdev->dev, id->data); +} + +static struct platform_driver ontat_kd50g21_40nt_a1_panel_platform_driver = { + .driver = { + .name = "ontat_kd50g21_40nt_a1_panel", + .of_match_table = platform_of_match, + }, + .probe = ontat_kd50g21_40nt_a1_panel_platform_probe, + .remove = ontat_kd50g21_40nt_a1_panel_remove, + .shutdown = ontat_kd50g21_40nt_a1_panel_shutdown, +}; +module_platform_driver(ontat_kd50g21_40nt_a1_panel_platform_driver); + +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_DESCRIPTION("ON Tat Industrial Company KD50G21-40NT-A1 panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-raydium-rm67191.c b/drivers/gpu/drm/panel/panel-raydium-rm67191.c index 572547d1aa83..871b5f1ad12b 100644 --- a/drivers/gpu/drm/panel/panel-raydium-rm67191.c +++ b/drivers/gpu/drm/panel/panel-raydium-rm67191.c @@ -10,6 +10,7 @@ #include <linux/gpio/consumer.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_platform.h> #include <linux/regulator/consumer.h> #include <video/mipi_display.h> @@ -38,150 +39,78 @@ struct cmd_set_entry { * There is no description in the Reference Manual about these commands. * We received them from vendor, so just use them as is. */ -static const struct cmd_set_entry manufacturer_cmd_set[] = { - {0xFE, 0x0B}, - {0x28, 0x40}, - {0x29, 0x4F}, - {0xFE, 0x0E}, - {0x4B, 0x00}, - {0x4C, 0x0F}, - {0x4D, 0x20}, - {0x4E, 0x40}, - {0x4F, 0x60}, - {0x50, 0xA0}, - {0x51, 0xC0}, - {0x52, 0xE0}, - {0x53, 0xFF}, - {0xFE, 0x0D}, - {0x18, 0x08}, - {0x42, 0x00}, - {0x08, 0x41}, - {0x46, 0x02}, - {0x72, 0x09}, - {0xFE, 0x0A}, - {0x24, 0x17}, - {0x04, 0x07}, - {0x1A, 0x0C}, - {0x0F, 0x44}, - {0xFE, 0x04}, - {0x00, 0x0C}, - {0x05, 0x08}, - {0x06, 0x08}, - {0x08, 0x08}, - {0x09, 0x08}, - {0x0A, 0xE6}, - {0x0B, 0x8C}, - {0x1A, 0x12}, - {0x1E, 0xE0}, - {0x29, 0x93}, - {0x2A, 0x93}, - {0x2F, 0x02}, - {0x31, 0x02}, - {0x33, 0x05}, - {0x37, 0x2D}, - {0x38, 0x2D}, - {0x3A, 0x1E}, - {0x3B, 0x1E}, - {0x3D, 0x27}, - {0x3F, 0x80}, - {0x40, 0x40}, - {0x41, 0xE0}, - {0x4F, 0x2F}, - {0x50, 0x1E}, - {0xFE, 0x06}, - {0x00, 0xCC}, - {0x05, 0x05}, - {0x07, 0xA2}, - {0x08, 0xCC}, - {0x0D, 0x03}, - {0x0F, 0xA2}, - {0x32, 0xCC}, - {0x37, 0x05}, - {0x39, 0x83}, - {0x3A, 0xCC}, - {0x41, 0x04}, - {0x43, 0x83}, - {0x44, 0xCC}, - {0x49, 0x05}, - {0x4B, 0xA2}, - {0x4C, 0xCC}, - {0x51, 0x03}, - {0x53, 0xA2}, - {0x75, 0xCC}, - {0x7A, 0x03}, - {0x7C, 0x83}, - {0x7D, 0xCC}, - {0x82, 0x02}, - {0x84, 0x83}, - {0x85, 0xEC}, - {0x86, 0x0F}, - {0x87, 0xFF}, - {0x88, 0x00}, - {0x8A, 0x02}, - {0x8C, 0xA2}, - {0x8D, 0xEA}, - {0x8E, 0x01}, - {0x8F, 0xE8}, - {0xFE, 0x06}, - {0x90, 0x0A}, - {0x92, 0x06}, - {0x93, 0xA0}, - {0x94, 0xA8}, - {0x95, 0xEC}, - {0x96, 0x0F}, - {0x97, 0xFF}, - {0x98, 0x00}, - {0x9A, 0x02}, - {0x9C, 0xA2}, - {0xAC, 0x04}, - {0xFE, 0x06}, - {0xB1, 0x12}, - {0xB2, 0x17}, - {0xB3, 0x17}, - {0xB4, 0x17}, - {0xB5, 0x17}, - {0xB6, 0x11}, - {0xB7, 0x08}, - {0xB8, 0x09}, - {0xB9, 0x06}, - {0xBA, 0x07}, - {0xBB, 0x17}, - {0xBC, 0x17}, - {0xBD, 0x17}, - {0xBE, 0x17}, - {0xBF, 0x17}, - {0xC0, 0x17}, - {0xC1, 0x17}, - {0xC2, 0x17}, - {0xC3, 0x17}, - {0xC4, 0x0F}, - {0xC5, 0x0E}, - {0xC6, 0x00}, - {0xC7, 0x01}, - {0xC8, 0x10}, - {0xFE, 0x06}, - {0x95, 0xEC}, - {0x8D, 0xEE}, - {0x44, 0xEC}, - {0x4C, 0xEC}, - {0x32, 0xEC}, - {0x3A, 0xEC}, - {0x7D, 0xEC}, - {0x75, 0xEC}, - {0x00, 0xEC}, - {0x08, 0xEC}, - {0x85, 0xEC}, - {0xA6, 0x21}, - {0xA7, 0x05}, - {0xA9, 0x06}, - {0x82, 0x06}, - {0x41, 0x06}, - {0x7A, 0x07}, - {0x37, 0x07}, - {0x05, 0x06}, - {0x49, 0x06}, - {0x0D, 0x04}, - {0x51, 0x04}, +static const struct cmd_set_entry mcs_rm67191[] = { + {0xFE, 0x0B}, {0x28, 0x40}, {0x29, 0x4F}, {0xFE, 0x0E}, + {0x4B, 0x00}, {0x4C, 0x0F}, {0x4D, 0x20}, {0x4E, 0x40}, + {0x4F, 0x60}, {0x50, 0xA0}, {0x51, 0xC0}, {0x52, 0xE0}, + {0x53, 0xFF}, {0xFE, 0x0D}, {0x18, 0x08}, {0x42, 0x00}, + {0x08, 0x41}, {0x46, 0x02}, {0x72, 0x09}, {0xFE, 0x0A}, + {0x24, 0x17}, {0x04, 0x07}, {0x1A, 0x0C}, {0x0F, 0x44}, + {0xFE, 0x04}, {0x00, 0x0C}, {0x05, 0x08}, {0x06, 0x08}, + {0x08, 0x08}, {0x09, 0x08}, {0x0A, 0xE6}, {0x0B, 0x8C}, + {0x1A, 0x12}, {0x1E, 0xE0}, {0x29, 0x93}, {0x2A, 0x93}, + {0x2F, 0x02}, {0x31, 0x02}, {0x33, 0x05}, {0x37, 0x2D}, + {0x38, 0x2D}, {0x3A, 0x1E}, {0x3B, 0x1E}, {0x3D, 0x27}, + {0x3F, 0x80}, {0x40, 0x40}, {0x41, 0xE0}, {0x4F, 0x2F}, + {0x50, 0x1E}, {0xFE, 0x06}, {0x00, 0xCC}, {0x05, 0x05}, + {0x07, 0xA2}, {0x08, 0xCC}, {0x0D, 0x03}, {0x0F, 0xA2}, + {0x32, 0xCC}, {0x37, 0x05}, {0x39, 0x83}, {0x3A, 0xCC}, + {0x41, 0x04}, {0x43, 0x83}, {0x44, 0xCC}, {0x49, 0x05}, + {0x4B, 0xA2}, {0x4C, 0xCC}, {0x51, 0x03}, {0x53, 0xA2}, + {0x75, 0xCC}, {0x7A, 0x03}, {0x7C, 0x83}, {0x7D, 0xCC}, + {0x82, 0x02}, {0x84, 0x83}, {0x85, 0xEC}, {0x86, 0x0F}, + {0x87, 0xFF}, {0x88, 0x00}, {0x8A, 0x02}, {0x8C, 0xA2}, + {0x8D, 0xEA}, {0x8E, 0x01}, {0x8F, 0xE8}, {0xFE, 0x06}, + {0x90, 0x0A}, {0x92, 0x06}, {0x93, 0xA0}, {0x94, 0xA8}, + {0x95, 0xEC}, {0x96, 0x0F}, {0x97, 0xFF}, {0x98, 0x00}, + {0x9A, 0x02}, {0x9C, 0xA2}, {0xAC, 0x04}, {0xFE, 0x06}, + {0xB1, 0x12}, {0xB2, 0x17}, {0xB3, 0x17}, {0xB4, 0x17}, + {0xB5, 0x17}, {0xB6, 0x11}, {0xB7, 0x08}, {0xB8, 0x09}, + {0xB9, 0x06}, {0xBA, 0x07}, {0xBB, 0x17}, {0xBC, 0x17}, + {0xBD, 0x17}, {0xBE, 0x17}, {0xBF, 0x17}, {0xC0, 0x17}, + {0xC1, 0x17}, {0xC2, 0x17}, {0xC3, 0x17}, {0xC4, 0x0F}, + {0xC5, 0x0E}, {0xC6, 0x00}, {0xC7, 0x01}, {0xC8, 0x10}, + {0xFE, 0x06}, {0x95, 0xEC}, {0x8D, 0xEE}, {0x44, 0xEC}, + {0x4C, 0xEC}, {0x32, 0xEC}, {0x3A, 0xEC}, {0x7D, 0xEC}, + {0x75, 0xEC}, {0x00, 0xEC}, {0x08, 0xEC}, {0x85, 0xEC}, + {0xA6, 0x21}, {0xA7, 0x05}, {0xA9, 0x06}, {0x82, 0x06}, + {0x41, 0x06}, {0x7A, 0x07}, {0x37, 0x07}, {0x05, 0x06}, + {0x49, 0x06}, {0x0D, 0x04}, {0x51, 0x04}, +}; + +static const struct cmd_set_entry mcs_rm67199[] = { + {0xFE, 0xA0}, {0x2B, 0x18}, {0xFE, 0x70}, {0x7D, 0x05}, + {0x5D, 0x0A}, {0x5A, 0x79}, {0x5C, 0x00}, {0x52, 0x00}, + {0xFE, 0xD0}, {0x40, 0x02}, {0x13, 0x40}, {0xFE, 0x40}, + {0x05, 0x08}, {0x06, 0x08}, {0x08, 0x08}, {0x09, 0x08}, + {0x0A, 0xCA}, {0x0B, 0x88}, {0x20, 0x93}, {0x21, 0x93}, + {0x24, 0x02}, {0x26, 0x02}, {0x28, 0x05}, {0x2A, 0x05}, + {0x74, 0x2F}, {0x75, 0x1E}, {0xAD, 0x00}, {0xFE, 0x60}, + {0x00, 0xCC}, {0x01, 0x00}, {0x02, 0x04}, {0x03, 0x00}, + {0x04, 0x00}, {0x05, 0x07}, {0x06, 0x00}, {0x07, 0x88}, + {0x08, 0x00}, {0x09, 0xCC}, {0x0A, 0x00}, {0x0B, 0x04}, + {0x0C, 0x00}, {0x0D, 0x00}, {0x0E, 0x05}, {0x0F, 0x00}, + {0x10, 0x88}, {0x11, 0x00}, {0x12, 0xCC}, {0x13, 0x0F}, + {0x14, 0xFF}, {0x15, 0x04}, {0x16, 0x00}, {0x17, 0x06}, + {0x18, 0x00}, {0x19, 0x96}, {0x1A, 0x00}, {0x24, 0xCC}, + {0x25, 0x00}, {0x26, 0x02}, {0x27, 0x00}, {0x28, 0x00}, + {0x29, 0x06}, {0x2A, 0x06}, {0x2B, 0x82}, {0x2D, 0x00}, + {0x2F, 0xCC}, {0x30, 0x00}, {0x31, 0x02}, {0x32, 0x00}, + {0x33, 0x00}, {0x34, 0x07}, {0x35, 0x06}, {0x36, 0x82}, + {0x37, 0x00}, {0x38, 0xCC}, {0x39, 0x00}, {0x3A, 0x02}, + {0x3B, 0x00}, {0x3D, 0x00}, {0x3F, 0x07}, {0x40, 0x00}, + {0x41, 0x88}, {0x42, 0x00}, {0x43, 0xCC}, {0x44, 0x00}, + {0x45, 0x02}, {0x46, 0x00}, {0x47, 0x00}, {0x48, 0x06}, + {0x49, 0x02}, {0x4A, 0x8A}, {0x4B, 0x00}, {0x5F, 0xCA}, + {0x60, 0x01}, {0x61, 0xE8}, {0x62, 0x09}, {0x63, 0x00}, + {0x64, 0x07}, {0x65, 0x00}, {0x66, 0x30}, {0x67, 0x80}, + {0x9B, 0x03}, {0xA9, 0x07}, {0xAA, 0x06}, {0xAB, 0x02}, + {0xAC, 0x10}, {0xAD, 0x11}, {0xAE, 0x05}, {0xAF, 0x04}, + {0xB0, 0x10}, {0xB1, 0x10}, {0xB2, 0x10}, {0xB3, 0x10}, + {0xB4, 0x10}, {0xB5, 0x10}, {0xB6, 0x10}, {0xB7, 0x10}, + {0xB8, 0x10}, {0xB9, 0x10}, {0xBA, 0x04}, {0xBB, 0x05}, + {0xBC, 0x00}, {0xBD, 0x01}, {0xBE, 0x0A}, {0xBF, 0x10}, + {0xC0, 0x11}, {0xFE, 0xA0}, {0x22, 0x00}, }; static const u32 rad_bus_formats[] = { @@ -205,10 +134,16 @@ struct rad_panel { bool prepared; bool enabled; + + const struct rad_platform_data *pdata; +}; + +struct rad_platform_data { + int (*enable)(struct rad_panel *panel); }; static const struct drm_display_mode default_mode = { - .clock = 132000, + .clock = 121000, .hdisplay = 1080, .hsync_start = 1080 + 20, .hsync_end = 1080 + 20 + 2, @@ -228,14 +163,15 @@ static inline struct rad_panel *to_rad_panel(struct drm_panel *panel) return container_of(panel, struct rad_panel, panel); } -static int rad_panel_push_cmd_list(struct mipi_dsi_device *dsi) +static int rad_panel_push_cmd_list(struct mipi_dsi_device *dsi, + struct cmd_set_entry const *cmd_set, + size_t count) { size_t i; - size_t count = ARRAY_SIZE(manufacturer_cmd_set); int ret = 0; for (i = 0; i < count; i++) { - const struct cmd_set_entry *entry = &manufacturer_cmd_set[i]; + const struct cmd_set_entry *entry = cmd_set++; u8 buffer[2] = { entry->cmd, entry->param }; ret = mipi_dsi_generic_write(dsi, &buffer, sizeof(buffer)); @@ -273,11 +209,16 @@ static int rad_panel_prepare(struct drm_panel *panel) if (ret) return ret; + /* At lest 10ms needed between power-on and reset-out as RM specifies */ + usleep_range(10000, 12000); + if (rad->reset) { - gpiod_set_value_cansleep(rad->reset, 1); - usleep_range(3000, 5000); gpiod_set_value_cansleep(rad->reset, 0); - usleep_range(18000, 20000); + /* + * 50ms delay after reset-out, as per manufacturer initalization + * sequence. + */ + msleep(50); } rad->prepared = true; @@ -313,20 +254,22 @@ static int rad_panel_unprepare(struct drm_panel *panel) return 0; } -static int rad_panel_enable(struct drm_panel *panel) +static int rm67191_enable(struct rad_panel *panel) { - struct rad_panel *rad = to_rad_panel(panel); - struct mipi_dsi_device *dsi = rad->dsi; + struct mipi_dsi_device *dsi = panel->dsi; struct device *dev = &dsi->dev; + u8 dsi_mode; int color_format = color_format_from_dsi_format(dsi->format); int ret; - if (rad->enabled) + if (panel->enabled) return 0; dsi->mode_flags |= MIPI_DSI_MODE_LPM; - ret = rad_panel_push_cmd_list(dsi); + ret = rad_panel_push_cmd_list(dsi, + &mcs_rm67191[0], + ARRAY_SIZE(mcs_rm67191)); if (ret < 0) { dev_err(dev, "Failed to send MCS (%d)\n", ret); goto fail; @@ -347,7 +290,8 @@ static int rad_panel_enable(struct drm_panel *panel) usleep_range(15000, 17000); /* Set DSI mode */ - ret = mipi_dsi_generic_write(dsi, (u8[]){ 0xC2, 0x0B }, 2); + dsi_mode = (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) ? 0x0B : 0x00; + ret = mipi_dsi_generic_write(dsi, (u8[]){ 0xC2, dsi_mode }, 2); if (ret < 0) { dev_err(dev, "Failed to set DSI mode (%d)\n", ret); goto fail; @@ -386,18 +330,116 @@ static int rad_panel_enable(struct drm_panel *panel) goto fail; } - backlight_enable(rad->backlight); + backlight_enable(panel->backlight); + + panel->enabled = true; + + return 0; + +fail: + gpiod_set_value_cansleep(panel->reset, 1); + + return ret; +} + +static int rm67199_enable(struct rad_panel *panel) +{ + struct mipi_dsi_device *dsi = panel->dsi; + struct device *dev = &dsi->dev; + u8 dsi_mode; + int color_format = color_format_from_dsi_format(dsi->format); + int ret; + + if (panel->enabled) + return 0; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + ret = rad_panel_push_cmd_list(dsi, + &mcs_rm67199[0], + ARRAY_SIZE(mcs_rm67199)); + if (ret < 0) { + dev_err(dev, "Failed to send MCS (%d)\n", ret); + goto fail; + } + + /* Select User Command Set table (CMD1) */ + ret = mipi_dsi_generic_write(dsi, (u8[]){ WRMAUCCTR, 0x00 }, 2); + if (ret < 0) + goto fail; + + /* Set DSI mode */ + dsi_mode = (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) ? 0x0B : 0x00; + ret = mipi_dsi_generic_write(dsi, (u8[]){ 0xC2, dsi_mode }, 2); + if (ret < 0) { + dev_err(dev, "Failed to set DSI mode (%d)\n", ret); + goto fail; + } + /* Set tear ON */ + ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + if (ret < 0) { + dev_err(dev, "Failed to set tear ON (%d)\n", ret); + goto fail; + } + /* Set tear scanline */ + ret = mipi_dsi_dcs_set_tear_scanline(dsi, 0x00); + if (ret < 0) { + dev_err(dev, "Failed to set tear scanline (%d)\n", ret); + goto fail; + } + /* Set pixel format */ + ret = mipi_dsi_dcs_set_pixel_format(dsi, color_format); + dev_dbg(dev, "Interface color format set to 0x%x\n", + color_format); + if (ret < 0) { + dev_err(dev, "Failed to set pixel format (%d)\n", ret); + goto fail; + } + /* Exit sleep mode */ + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret < 0) { + dev_err(dev, "Failed to exit sleep mode (%d)\n", ret); + goto fail; + } + + /* + * Although, 120ms seems a lot, this is the amount of delay that the + * manufacturer suggests it should be used between the sleep-out and + * display-on commands + */ + msleep(120); + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret < 0) { + dev_err(dev, "Failed to set display ON (%d)\n", ret); + goto fail; + } + + /* + * Also, 100ms delay between display-on and backlight enable as per + * manufacturer initialization sequence. + */ + msleep(100); + + backlight_enable(panel->backlight); - rad->enabled = true; + panel->enabled = true; return 0; fail: - gpiod_set_value_cansleep(rad->reset, 1); + gpiod_set_value_cansleep(panel->reset, 1); return ret; } +static int rad_panel_enable(struct drm_panel *panel) +{ + struct rad_panel *rad = to_rad_panel(panel); + + return rad->pdata->enable(rad); +} + static int rad_panel_disable(struct drm_panel *panel) { struct rad_panel *rad = to_rad_panel(panel); @@ -534,15 +576,34 @@ static int rad_init_regulators(struct rad_panel *rad) return devm_regulator_bulk_get(dev, rad->num_supplies, rad->supplies); }; +static const struct rad_platform_data rad_rm67191 = { + .enable = &rm67191_enable, +}; + +static const struct rad_platform_data rad_rm67199 = { + .enable = &rm67199_enable, +}; + +static const struct of_device_id rad_of_match[] = { + { .compatible = "raydium,rm67191", .data = &rad_rm67191 }, + { .compatible = "raydium,rm67199", .data = &rad_rm67199 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rad_of_match); + static int rad_panel_probe(struct mipi_dsi_device *dsi) { struct device *dev = &dsi->dev; + const struct of_device_id *of_id = of_match_device(rad_of_match, dev); struct device_node *np = dev->of_node; struct rad_panel *panel; struct backlight_properties bl_props; int ret; u32 video_mode; + if (!of_id || !of_id->data) + return -ENODEV; + panel = devm_kzalloc(&dsi->dev, sizeof(*panel), GFP_KERNEL); if (!panel) return -ENOMEM; @@ -550,23 +611,32 @@ static int rad_panel_probe(struct mipi_dsi_device *dsi) mipi_dsi_set_drvdata(dsi, panel); panel->dsi = dsi; + panel->pdata = of_id->data; dsi->format = MIPI_DSI_FMT_RGB888; - dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_NO_EOT_PACKET; ret = of_property_read_u32(np, "video-mode", &video_mode); if (!ret) { switch (video_mode) { case 0: /* burst mode */ - dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_BURST; + dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_VIDEO; break; case 1: /* non-burst mode with sync event */ + dsi->mode_flags |= MIPI_DSI_MODE_VIDEO; break; case 2: /* non-burst mode with sync pulse */ - dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_VIDEO; + break; + case 3: + /* command mode */ + dsi->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS | + MIPI_DSI_MODE_VSYNC_FLUSH; break; default: dev_warn(dev, "invalid video mode %d\n", video_mode); @@ -580,9 +650,15 @@ static int rad_panel_probe(struct mipi_dsi_device *dsi) return ret; } - panel->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); - if (IS_ERR(panel->reset)) - return PTR_ERR(panel->reset); + panel->reset = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW | + GPIOD_FLAGS_BIT_NONEXCLUSIVE); + if (IS_ERR(panel->reset)) { + ret = PTR_ERR(panel->reset); + dev_err(dev, "Failed to get reset gpio (%d)\n", ret); + return ret; + } + gpiod_set_value_cansleep(panel->reset, 1); memset(&bl_props, 0, sizeof(bl_props)); bl_props.type = BACKLIGHT_RAW; @@ -638,12 +714,6 @@ static void rad_panel_shutdown(struct mipi_dsi_device *dsi) rad_panel_unprepare(&rad->panel); } -static const struct of_device_id rad_of_match[] = { - { .compatible = "raydium,rm67191", }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, rad_of_match); - static struct mipi_dsi_driver rad_panel_driver = { .driver = { .name = "panel-raydium-rm67191", diff --git a/drivers/gpu/drm/panel/panel-raydium-rm68200.c b/drivers/gpu/drm/panel/panel-raydium-rm68200.c index 412c0dbcb2b6..1288389600f3 100644 --- a/drivers/gpu/drm/panel/panel-raydium-rm68200.c +++ b/drivers/gpu/drm/panel/panel-raydium-rm68200.c @@ -75,6 +75,7 @@ struct rm68200 { struct device *dev; struct drm_panel panel; + struct gpio_desc *enable_gpio; struct gpio_desc *reset_gpio; struct regulator *supply; bool prepared; @@ -234,10 +235,22 @@ static void rm68200_init_sequence(struct rm68200 *ctx) static int rm68200_disable(struct drm_panel *panel) { struct rm68200 *ctx = panel_to_rm68200(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; if (!ctx->enabled) return 0; + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret) + dev_warn(panel->dev, "failed to set display off: %d\n", ret); + + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret) + dev_warn(panel->dev, "failed to enter sleep mode: %d\n", ret); + + msleep(120); + ctx->enabled = false; return 0; @@ -246,27 +259,17 @@ static int rm68200_disable(struct drm_panel *panel) static int rm68200_unprepare(struct drm_panel *panel) { struct rm68200 *ctx = panel_to_rm68200(panel); - struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); - int ret; if (!ctx->prepared) return 0; - ret = mipi_dsi_dcs_set_display_off(dsi); - if (ret) - dev_warn(panel->dev, "failed to set display off: %d\n", ret); - - ret = mipi_dsi_dcs_enter_sleep_mode(dsi); - if (ret) - dev_warn(panel->dev, "failed to enter sleep mode: %d\n", ret); - - msleep(120); - if (ctx->reset_gpio) { gpiod_set_value_cansleep(ctx->reset_gpio, 1); msleep(20); } + gpiod_set_value_cansleep(ctx->enable_gpio, 0); + regulator_disable(ctx->supply); ctx->prepared = false; @@ -277,7 +280,6 @@ static int rm68200_unprepare(struct drm_panel *panel) static int rm68200_prepare(struct drm_panel *panel) { struct rm68200 *ctx = panel_to_rm68200(panel); - struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); int ret; if (ctx->prepared) @@ -289,6 +291,8 @@ static int rm68200_prepare(struct drm_panel *panel) return ret; } + gpiod_set_value_cansleep(ctx->enable_gpio, 1); + if (ctx->reset_gpio) { gpiod_set_value_cansleep(ctx->reset_gpio, 1); msleep(20); @@ -296,6 +300,20 @@ static int rm68200_prepare(struct drm_panel *panel) msleep(100); } + ctx->prepared = true; + + return 0; +} + +static int rm68200_enable(struct drm_panel *panel) +{ + struct rm68200 *ctx = panel_to_rm68200(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; + + if (ctx->enabled) + return 0; + rm68200_init_sequence(ctx); ret = mipi_dsi_dcs_exit_sleep_mode(dsi); @@ -310,18 +328,6 @@ static int rm68200_prepare(struct drm_panel *panel) msleep(20); - ctx->prepared = true; - - return 0; -} - -static int rm68200_enable(struct drm_panel *panel) -{ - struct rm68200 *ctx = panel_to_rm68200(panel); - - if (ctx->enabled) - return 0; - ctx->enabled = true; return 0; @@ -369,6 +375,13 @@ static int rm68200_probe(struct mipi_dsi_device *dsi) if (!ctx) return -ENOMEM; + ctx->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(ctx->enable_gpio)) { + ret = PTR_ERR(ctx->enable_gpio); + dev_err(dev, "cannot get enable GPIO: %d\n", ret); + return ret; + } + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(ctx->reset_gpio)) { ret = PTR_ERR(ctx->reset_gpio); @@ -390,8 +403,8 @@ static int rm68200_probe(struct mipi_dsi_device *dsi) dsi->lanes = 2; dsi->format = MIPI_DSI_FMT_RGB888; - dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | - MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM; drm_panel_init(&ctx->panel, dev, &rm68200_drm_funcs, DRM_MODE_CONNECTOR_DSI); diff --git a/drivers/gpu/drm/panel/panel-rocktech-hx8394f.c b/drivers/gpu/drm/panel/panel-rocktech-hx8394f.c new file mode 100644 index 000000000000..ed768cba88a4 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-rocktech-hx8394f.c @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Copyright 2021,2022 NXP + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +/* User Define command set */ +#define UD_SETADDRESSMODE 0x36 /* Set address mode */ +#define UD_SETSEQUENCE 0xB0 /* Set sequence */ +#define UD_SETPOWER 0xB1 /* Set power */ +#define UD_SETDISP 0xB2 /* Set display related register */ +#define UD_SETCYC 0xB4 /* Set display waveform cycles */ +#define UD_SETVCOM 0xB6 /* Set VCOM voltage */ +#define UD_SETTE 0xB7 /* Set internal TE function */ +#define UD_SETSENSOR 0xB8 /* Set temperature sensor */ +#define UD_SETEXTC 0xB9 /* Set extension command */ +#define UD_SETMIPI 0xBA /* Set MIPI control */ +#define UD_SETOTP 0xBB /* Set OTP */ +#define UD_SETREGBANK 0xBD /* Set register bank */ +#define UD_SETDGCLUT 0xC1 /* Set DGC LUT */ +#define UD_SETID 0xC3 /* Set ID */ +#define UD_SETDDB 0xC4 /* Set DDB */ +#define UD_SETCABC 0xC9 /* Set CABC control */ +#define UD_SETCABCGAIN 0xCA +#define UD_SETPANEL 0xCC +#define UD_SETOFFSET 0xD2 +#define UD_SETGIP0 0xD3 /* Set GIP Option0 */ +#define UD_SETGIP1 0xD5 /* Set GIP Option1 */ +#define UD_SETGIP2 0xD6 /* Set GIP Option2 */ +#define UD_SETGPO 0xD9 +#define UD_SETSCALING 0xDD +#define UD_SETIDLE 0xDF +#define UD_SETGAMMA 0xE0 /* Set gamma curve related setting */ +#define UD_SETCHEMODE_DYN 0xE4 +#define UD_SETCHE 0xE5 +#define UD_SETCESEL 0xE6 /* Enable color enhance */ +#define UD_SET_SP_CMD 0xE9 +#define UD_SETREADINDEX 0xFE /* Set SPI Read Index */ +#define UD_GETSPIREAD 0xFF /* SPI Read Command Data */ + +struct hx8394f { + struct device *dev; + struct drm_panel panel; + struct gpio_desc *enable_gpio; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[2]; + bool prepared; + bool enabled; +}; + +static const struct drm_display_mode default_mode = { + .clock = 66000, + .hdisplay = 720, + .hsync_start = 720 + 52, + .hsync_end = 720 + 52 + 10, + .htotal = 720 + 52 + 10 + 52, + .vdisplay = 1280, + .vsync_start = 1280 + 16, + .vsync_end = 1280 + 16 + 7, + .vtotal = 1280 + 16 + 7 + 16, + .flags = 0, + .width_mm = 68, + .height_mm = 122, +}; + +static inline struct hx8394f *panel_to_hx8394f(struct drm_panel *panel) +{ + return container_of(panel, struct hx8394f, panel); +} + +static void hx8394f_dcs_write_buf(struct hx8394f *ctx, const void *data, + size_t len) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int err; + + err = mipi_dsi_dcs_write_buffer(dsi, data, len); + if (err < 0) + dev_err_ratelimited(ctx->dev, "MIPI DSI DCS write buffer failed: %d\n", err); +} + +#define dcs_write_seq(ctx, seq...) \ +({ \ + static const u8 d[] = { seq }; \ + \ + hx8394f_dcs_write_buf(ctx, d, ARRAY_SIZE(d)); \ +}) + +static void hx8394f_init_sequence(struct hx8394f *ctx) +{ + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + u8 mipi_data[] = {UD_SETMIPI, 0x60, 0x03, 0x68, 0x6B, 0xB2, 0xC0}; + + dcs_write_seq(ctx, UD_SETADDRESSMODE, 0x02); + + dcs_write_seq(ctx, UD_SETEXTC, 0xFF, 0x83, 0x94); + + /* SETMIPI */ + mipi_data[1] = 0x60 | (dsi->lanes - 1); + hx8394f_dcs_write_buf(ctx, mipi_data, ARRAY_SIZE(mipi_data)); + + dcs_write_seq(ctx, UD_SETPOWER, 0x48, 0x12, 0x72, 0x09, 0x32, 0x54, + 0x71, 0x71, 0x57, 0x47); + + dcs_write_seq(ctx, UD_SETDISP, 0x00, 0x80, 0x64, 0x15, 0x0E, 0x11); + + dcs_write_seq(ctx, UD_SETCYC, 0x73, 0x74, 0x73, 0x74, 0x73, 0x74, 0x01, + 0x0C, 0x86, 0x75, 0x00, 0x3F, 0x73, 0x74, 0x73, 0x74, + 0x73, 0x74, 0x01, 0x0C, 0x86); + + dcs_write_seq(ctx, UD_SETGIP0, 0x00, 0x00, 0x07, 0x07, 0x40, 0x07, 0x0C, + 0x00, 0x08, 0x10, 0x08, 0x00, 0x08, 0x54, 0x15, 0x0A, + 0x05, 0x0A, 0x02, 0x15, 0x06, 0x05, 0x06, 0x47, 0x44, + 0x0A, 0x0A, 0x4B, 0x10, 0x07, 0x07, 0x0C, 0x40); + + dcs_write_seq(ctx, UD_SETGIP1, 0x1C, 0x1C, 0x1D, 0x1D, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x24, 0x25, 0x18, 0x18, 0x26, 0x27, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x20, 0x21, 0x18, 0x18, 0x18, + 0x18); + + dcs_write_seq(ctx, UD_SETGIP2, 0x1C, 0x1C, 0x1D, 0x1D, 0x07, 0x06, 0x05, + 0x04, 0x03, 0x02, 0x01, 0x00, 0x0B, 0x0A, 0x09, 0x08, + 0x21, 0x20, 0x18, 0x18, 0x27, 0x26, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x25, 0x24, 0x18, 0x18, 0x18, + 0x18); + + dcs_write_seq(ctx, UD_SETVCOM, 0x92, 0x92); + + dcs_write_seq(ctx, UD_SETGAMMA, 0x00, 0x0A, 0x15, 0x1B, 0x1E, 0x21, + 0x24, 0x22, 0x47, 0x56, 0x65, 0x66, 0x6E, 0x82, 0x88, + 0x8B, 0x9A, 0x9D, 0x98, 0xA8, 0xB9, 0x5D, 0x5C, 0x61, + 0x66, 0x6A, 0x6F, 0x7F, 0x7F, 0x00, 0x0A, 0x15, 0x1B, + 0x1E, 0x21, 0x24, 0x22, 0x47, 0x56, 0x65, 0x65, 0x6E, + 0x81, 0x87, 0x8B, 0x98, 0x9D, 0x99, 0xA8, 0xBA, 0x5D, + 0x5D, 0x62, 0x67, 0x6B, 0x72, 0x7F, 0x7F); + dcs_write_seq(ctx, 0xC0, 0x1F, 0x31); + dcs_write_seq(ctx, UD_SETPANEL, 0x03); + dcs_write_seq(ctx, 0xD4, 0x02); + dcs_write_seq(ctx, UD_SETREGBANK, 0x02); + dcs_write_seq(ctx, 0xD8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF); + dcs_write_seq(ctx, UD_SETREGBANK, 0x00); + dcs_write_seq(ctx, UD_SETREGBANK, 0x01); + dcs_write_seq(ctx, UD_SETPOWER, 0x00); + dcs_write_seq(ctx, UD_SETREGBANK, 0x00); + dcs_write_seq(ctx, 0xBF, 0x40, 0x81, 0x50, 0x00, 0x1A, 0xFC, 0x01); + dcs_write_seq(ctx, 0xC6, 0xED); +} + +static int hx8394f_disable(struct drm_panel *panel) +{ + struct hx8394f *ctx = panel_to_hx8394f(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; + + if (!ctx->enabled) + return 0; + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret) + dev_warn(panel->dev, "failed to set display off: %d\n", ret); + + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret) + dev_warn(panel->dev, "failed to enter sleep mode: %d\n", ret); + + msleep(120); + + ctx->enabled = false; + + return 0; +} + +static int hx8394f_unprepare(struct drm_panel *panel) +{ + struct hx8394f *ctx = panel_to_hx8394f(panel); + int ret; + + if (!ctx->prepared) + return 0; + + if (ctx->reset_gpio) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + msleep(20); + } + + gpiod_set_value_cansleep(ctx->enable_gpio, 0); + + ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) { + dev_err(ctx->dev, "failed to disable supplies: %d\n", ret); + return ret; + } + + ctx->prepared = false; + + return 0; +} + +static int hx8394f_prepare(struct drm_panel *panel) +{ + struct hx8394f *ctx = panel_to_hx8394f(panel); + int ret; + + if (ctx->prepared) + return 0; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret) { + dev_err(ctx->dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + gpiod_set_value_cansleep(ctx->enable_gpio, 1); + + if (ctx->reset_gpio) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + msleep(20); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + msleep(55); + } + + ctx->prepared = true; + + return 0; +} + +static int hx8394f_enable(struct drm_panel *panel) +{ + struct hx8394f *ctx = panel_to_hx8394f(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; + + if (ctx->enabled) + return 0; + + hx8394f_init_sequence(ctx); + + /* Set tear ON */ + ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + if (ret < 0) { + dev_err(ctx->dev, "failed to set tear ON (%d)\n", ret); + return ret; + } + + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret) + return ret; + + msleep(120); + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret) + return ret; + + msleep(50); + + ctx->enabled = true; + + return 0; +} + +static int hx8394f_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &default_mode); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + default_mode.hdisplay, default_mode.vdisplay, + drm_mode_vrefresh(&default_mode)); + return -ENOMEM; + } + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + + return 1; +} + +static const struct drm_panel_funcs hx8394f_drm_funcs = { + .disable = hx8394f_disable, + .unprepare = hx8394f_unprepare, + .prepare = hx8394f_prepare, + .enable = hx8394f_enable, + .get_modes = hx8394f_get_modes, +}; + +static int hx8394f_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct hx8394f *ctx; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(ctx->enable_gpio)) { + ret = PTR_ERR(ctx->enable_gpio); + dev_err(dev, "failed to get enable GPIO: %d\n", ret); + return ret; + } + + ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) { + ret = PTR_ERR(ctx->reset_gpio); + dev_err(dev, "failed to get reset GPIO: %d\n", ret); + return ret; + } + + ctx->supplies[0].supply = "vcc"; + ctx->supplies[1].supply = "iovcc"; + ret = devm_regulator_bulk_get(dev, 2, ctx->supplies); + if (ret < 0) + return ret; + + mipi_dsi_set_drvdata(dsi, ctx); + + ctx->dev = dev; + + ret = of_property_read_u32(dev->of_node, "himax,dsi-lanes", + &dsi->lanes); + if (ret) { + dev_err(dev, "failed to get himax,dsi-lanes property: %d\n", + ret); + return ret; + } + + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM; + + drm_panel_init(&ctx->panel, dev, &hx8394f_drm_funcs, + DRM_MODE_CONNECTOR_DSI); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return ret; + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "mipi_dsi_attach() failed: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static int hx8394f_remove(struct mipi_dsi_device *dsi) +{ + struct hx8394f *ctx = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&ctx->panel); + + return 0; +} + +static const struct of_device_id hx8394f_of_match[] = { + { .compatible = "rocktech,hx8394f" }, + { } +}; +MODULE_DEVICE_TABLE(of, hx8394f_of_match); + +static struct mipi_dsi_driver hx8394f_driver = { + .probe = hx8394f_probe, + .remove = hx8394f_remove, + .driver = { + .name = "panel-rocktech-hx8394f", + .of_match_table = hx8394f_of_match, + }, +}; +module_mipi_dsi_driver(hx8394f_driver); + +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_DESCRIPTION("DRM Driver for Rocktech Himax8394f MIPI DSI panel"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 7cf0af78b7bc..8fdd04e7ef59 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -1500,6 +1500,37 @@ static const struct panel_desc bananapi_s070wv20_ct16 = { }, }; +static const struct drm_display_mode boe_ev121wxm_n10_1850_mode = { + .clock = 71143, + .hdisplay = 1280, + .hsync_start = 1280 + 48, + .hsync_end = 1280 + 48 + 32, + .htotal = 1280 + 48 + 32 + 80, + .vdisplay = 800, + .vsync_start = 800 + 3, + .vsync_end = 800 + 3 + 6, + .vtotal = 800 + 3 + 6 + 14, +}; + +static const struct panel_desc boe_ev121wxm_n10_1850 = { + .modes = &boe_ev121wxm_n10_1850_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 261, + .height = 163, + }, + .delay = { + .prepare = 8, + .enable = 300, + .unprepare = 300, + .disable = 60, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + static const struct drm_display_mode boe_hv070wsa_mode = { .clock = 42105, .hdisplay = 1024, @@ -2834,6 +2865,43 @@ static const struct panel_desc kingdisplay_kd116n21_30nv_a010 = { .connector_type = DRM_MODE_CONNECTOR_eDP, }; +static const struct display_timing jdi_tx26d202vm0bwa_timing = { + .pixelclock = { 151820000, 156720000, 159780000 }, + .hactive = { 1920, 1920, 1920 }, + .hfront_porch = { 76, 100, 112 }, + .hback_porch = { 74, 100, 112 }, + .hsync_len = { 30, 30, 30 }, + .vactive = { 1200, 1200, 1200}, + .vfront_porch = { 3, 5, 10 }, + .vback_porch = { 2, 5, 10 }, + .vsync_len = { 5, 5, 5 }, + .flags = DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc jdi_tx26d202vm0bwa = { + .timings = &jdi_tx26d202vm0bwa_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 217, + .height = 136, + }, + .delay = { + /* + * The panel spec recommends one second delay + * to the below items. However, it's a bit too + * long in pratice. Based on tests, it turns + * out 100 milliseconds is fine. + */ + .prepare = 100, + .enable = 100, + .unprepare = 100, + .disable = 100, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .connector_type = DRM_MODE_CONNECTOR_LVDS, +}; + static const struct display_timing koe_tx14d24vm1bpa_timing = { .pixelclock = { 5580000, 5850000, 6200000 }, .hactive = { 320, 320, 320 }, @@ -4550,6 +4618,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "bananapi,s070wv20-ct16", .data = &bananapi_s070wv20_ct16, }, { + .compatible = "boe,ev121wxm-n10-1850", + .data = &boe_ev121wxm_n10_1850, + }, { .compatible = "boe,hv070wsa-100", .data = &boe_hv070wsa }, { @@ -4703,6 +4774,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "kingdisplay,kd116n21-30nv-a010", .data = &kingdisplay_kd116n21_30nv_a010, }, { + .compatible = "jdi,tx26d202vm0bwa", + .data = &jdi_tx26d202vm0bwa, + }, { .compatible = "koe,tx14d24vm1bpa", .data = &koe_tx14d24vm1bpa, }, { diff --git a/drivers/gpu/drm/panel/panel-wks-101wx001.c b/drivers/gpu/drm/panel/panel-wks-101wx001.c new file mode 100644 index 000000000000..ef924ab3a477 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-wks-101wx001.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * WKS 101WX001-WCT parallel LCD panel driver + * + * Copyright 2020 NXP + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include <video/display_timing.h> +#include <video/videomode.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> + +struct wks_panel { + struct drm_panel panel; + struct gpio_desc *bl_ctr; + struct regulator *vcc; + + bool prepared; + bool enabled; +}; + +static const struct drm_display_mode default_mode = { + .clock = 71100, + .hdisplay = 1280, + .hsync_start = 1280 + 70, + .hsync_end = 1280 + 70 + 10, + .htotal = 1280 + 70 + 10 + 80, + .vdisplay = 800, + .vsync_start = 800 + 10, + .vsync_end = 800 + 10 + 3, + .vtotal = 800 + 10 + 3 + 10, + .width_mm = 217, + .height_mm = 135, + .flags = DRM_MODE_FLAG_NHSYNC | + DRM_MODE_FLAG_NVSYNC, +}; + +static const u32 wks_bus_formats[] = { + MEDIA_BUS_FMT_RGB666_1X18, +}; + +static const u32 wks_bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; + +static inline struct wks_panel *to_wks_panel(struct drm_panel *panel) +{ + return container_of(panel, struct wks_panel, panel); +} + +static int wks_panel_prepare(struct drm_panel *panel) +{ + struct wks_panel *p = to_wks_panel(panel); + int err; + + if (p->prepared) + return 0; + + err = regulator_enable(p->vcc); + if (err < 0) { + dev_err(panel->dev, "failed to enable vcc: %d\n", err); + return err; + } + + p->prepared = true; + + return 0; +} + +static int wks_panel_enable(struct drm_panel *panel) +{ + struct wks_panel *p = to_wks_panel(panel); + + if (p->enabled) + return 0; + + gpiod_set_value_cansleep(p->bl_ctr, 1); + + p->enabled = true; + + return 0; +} + + +static int wks_panel_disable(struct drm_panel *panel) +{ + struct wks_panel *p = to_wks_panel(panel); + + if (!p->enabled) + return 0; + + gpiod_set_value_cansleep(p->bl_ctr, 0); + + p->enabled = false; + + return 0; +} + +static int wks_panel_unprepare(struct drm_panel *panel) +{ + struct wks_panel *p = to_wks_panel(panel); + + if (!p->prepared) + return 0; + + regulator_disable(p->vcc); + + p->prepared = false; + + return 0; +} + +static int wks_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &default_mode); + if (!mode) { + DRM_DEV_ERROR(panel->dev, "failed to add mode %ux%ux@%d\n", + default_mode.hdisplay, default_mode.vdisplay, + drm_mode_vrefresh(&default_mode)); + return -ENOMEM; + } + + drm_mode_set_name(mode); + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + DRM_DEV_DEBUG_DRIVER(panel->dev, "Mode flags: 0x%08X", mode->flags); + + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + connector->display_info.bus_flags = wks_bus_flags; + + drm_display_info_set_bus_formats(&connector->display_info, + wks_bus_formats, + ARRAY_SIZE(wks_bus_formats)); + return 1; + +} + +static const struct drm_panel_funcs wks_panel_funcs = { + .prepare = wks_panel_prepare, + .enable = wks_panel_enable, + .disable = wks_panel_disable, + .unprepare = wks_panel_unprepare, + .get_modes = wks_panel_get_modes, +}; + +static int wks_panel_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct wks_panel *panel; + int err; + + panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL); + if (!panel) + return -ENOMEM; + + panel->vcc = devm_regulator_get(dev, "vcc"); + if (IS_ERR(panel->vcc)) + return PTR_ERR(panel->vcc); + + panel->bl_ctr = devm_gpiod_get(dev, "blctr", GPIOD_OUT_LOW); + if (IS_ERR(panel->bl_ctr)) { + err = PTR_ERR(panel->bl_ctr); + dev_err(dev, "Failed to get blctr gpio (%d)\n", err); + return err; + } + + drm_panel_init(&panel->panel, + dev, + &wks_panel_funcs, + DRM_MODE_CONNECTOR_DPI); + + drm_panel_add(&panel->panel); + dev_set_drvdata(dev, panel); + + return 0; +} + +static int wks_panel_remove(struct platform_device *pdev) +{ + struct wks_panel *p = dev_get_drvdata(&pdev->dev); + + drm_panel_remove(&p->panel); + + wks_panel_disable(&p->panel); + + return 0; +} + +static void wks_panel_shutdown(struct platform_device *pdev) +{ + struct wks_panel *p = dev_get_drvdata(&pdev->dev); + + wks_panel_disable(&p->panel); +} + +static const struct of_device_id wks_of_match[] = { + { .compatible = "wks,101wx001", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, wks_of_match); + +static struct platform_driver wks_panel_platform_driver = { + .driver = { + .name = "panel-wks-101wx001", + .of_match_table = wks_of_match, + }, + .probe = wks_panel_probe, + .remove = wks_panel_remove, + .shutdown = wks_panel_shutdown, +}; +module_platform_driver(wks_panel_platform_driver); + +MODULE_AUTHOR("Marco Franchi <marco.franchi@nxp.com>"); +MODULE_DESCRIPTION("Seiko 43WVF1G panel driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/imx/Kconfig b/drivers/gpu/imx/Kconfig new file mode 100644 index 000000000000..0c9f14f02a87 --- /dev/null +++ b/drivers/gpu/imx/Kconfig @@ -0,0 +1,19 @@ +source "drivers/gpu/imx/ipu-v3/Kconfig" +source "drivers/gpu/imx/dpu/Kconfig" +source "drivers/gpu/imx/dpu-blit/Kconfig" +source "drivers/gpu/imx/lcdif/Kconfig" +source "drivers/gpu/imx/lcdifv3/Kconfig" +config IMX8_PC + tristate + default y if IMX_DPU_CORE=y + default m if IMX_DPU_CORE=m + +config IMX8_PRG + tristate + default y if IMX_DPU_CORE=y + default m if IMX_DPU_CORE=m + +config IMX8_DPRC + tristate + default y if IMX_DPU_CORE=y + default m if IMX_DPU_CORE=m diff --git a/drivers/gpu/imx/Makefile b/drivers/gpu/imx/Makefile new file mode 100644 index 000000000000..3c08def348d6 --- /dev/null +++ b/drivers/gpu/imx/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/ +obj-$(CONFIG_IMX_DPU_CORE) += dpu/ +obj-$(CONFIG_IMX_DPU_BLIT) += dpu-blit/ +obj-$(CONFIG_IMX_LCDIF_CORE) += lcdif/ +obj-$(CONFIG_IMX_LCDIFV3_CORE) += lcdifv3/ +obj-$(CONFIG_IMX8_PC) += imx8_pc.o +obj-$(CONFIG_IMX8_PRG) += imx8_prg.o +obj-$(CONFIG_IMX8_DPRC) += imx8_dprc.o diff --git a/drivers/gpu/imx/dpu-blit/Kconfig b/drivers/gpu/imx/dpu-blit/Kconfig new file mode 100644 index 000000000000..d71d9a7f9515 --- /dev/null +++ b/drivers/gpu/imx/dpu-blit/Kconfig @@ -0,0 +1,5 @@ +config IMX_DPU_BLIT + tristate + depends on IMX_DPU_CORE + default y if IMX_DPU_CORE=y + default m if IMX_DPU_CORE=m diff --git a/drivers/gpu/imx/dpu-blit/Makefile b/drivers/gpu/imx/dpu-blit/Makefile new file mode 100644 index 000000000000..6d06b88b9d5e --- /dev/null +++ b/drivers/gpu/imx/dpu-blit/Makefile @@ -0,0 +1,5 @@ +ccflags-y += -I $(srctree)/$(src)/../dpu/ + +imx-dpu-blit-objs := dpu-blit.o + +obj-$(CONFIG_IMX_DPU_BLIT) += imx-dpu-blit.o diff --git a/drivers/gpu/imx/dpu-blit/dpu-blit-registers.h b/drivers/gpu/imx/dpu-blit/dpu-blit-registers.h new file mode 100644 index 000000000000..14563746c7eb --- /dev/null +++ b/drivers/gpu/imx/dpu-blit/dpu-blit-registers.h @@ -0,0 +1,284 @@ +/* + * Copyright 2017,2022 NXP + * + * 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. + */ + +#ifndef __DPU_BLIT_REGISTERS_H__ +#define __DPU_BLIT_REGISTERS_H__ + +/* Registers Defination */ +#define COMCTRL_IPIDENTIFIER ((uint32_t)(0)) + +#define PIXENGCFG_STORE9_TRIGGER ((uint32_t)(0x954)) + +#define COMCTRL_USERINTERRUPTMASK0 ((uint32_t)(0x48)) +#define COMCTRL_USERINTERRUPTMASK0_USERINTERRUPTMASK0_MASK 0xFFFFFFFFU +#define COMCTRL_USERINTERRUPTENABLE0 ((uint32_t)(0x80)) + +#define COMCTRL_INTERRUPTENABLE0 ((uint32_t)(0x50)) + +#define COMCTRL_INTERRUPTSTATUS0 ((uint32_t)(0x68)) +#define COMCTRL_USERINTERRUPTSTATUS0 ((uint32_t)(0x98)) + +#define COMCTRL_USERINTERRUPTCLEAR0 ((uint32_t)(0x90)) +#define COMCTRL_USERINTERRUPTCLEAR0_USERINTERRUPTCLEAR0_MASK 0xFFFFFFFFU + +#define COMCTRL_INTERRUPTCLEAR0 ((uint32_t)(0x60)) +#define COMCTRL_INTERRUPTCLEAR0_INTERRUPTCLEAR0_MASK 0xFFFFFFFFU + +#define COMCTRL_USERINTERRUPTPRESET1 ((uint32_t)(0x8C)) + +#define PIXENGCFG_FETCHDECODE9_DYNAMIC ((uint32_t)(0x828)) +#define PIXENGCFG_FETCHDECODE9_DYNAMIC_RESET_VALUE 0U + +#define PIXENGCFG_FETCHWARP9_DYNAMIC ((uint32_t)(0x848)) +#define PIXENGCFG_FETCHWARP9_DYNAMIC_RESET_VALUE 0U + +#define PIXENGCFG_ROP9_DYNAMIC ((uint32_t)(0x868)) +#define PIXENGCFG_ROP9_DYNAMIC_RESET_VALUE 0x1000000U + +#define PIXENGCFG_MATRIX9_DYNAMIC ((uint32_t)(0x8A8)) +#define PIXENGCFG_MATRIX9_DYNAMIC_RESET_VALUE 0x1000000U + +#define PIXENGCFG_HSCALER9_DYNAMIC ((uint32_t)(0x8C8)) +#define PIXENGCFG_HSCALER9_DYNAMIC_RESET_VALUE 0x1000000U + +#define PIXENGCFG_VSCALER9_DYNAMIC ((uint32_t)(0x8E8)) +#define PIXENGCFG_VSCALER9_DYNAMIC_RESET_VALUE 0x1000000U + +#define PIXENGCFG_BLITBLEND9_DYNAMIC ((uint32_t)(0x928)) +#define PIXENGCFG_BLITBLEND9_DYNAMIC_RESET_VALUE 0x1000000U + +#define PIXENGCFG_STORE9_STATIC ((uint32_t)(0x948)) +#define PIXENGCFG_STORE9_STATIC_RESET_VALUE 0x800010U +#define PIXENGCFG_STORE9_STATIC_RESET_MASK 0xFFFFFFFFU +#define PIXENGCFG_STORE9_STATIC_STORE9_SHDEN_MASK 0x1U +#define PIXENGCFG_STORE9_STATIC_STORE9_SHDEN_SHIFT 0U +#define PIXENGCFG_STORE9_STATIC_STORE9_POWERDOWN_MASK 0x10U +#define PIXENGCFG_STORE9_STATIC_STORE9_POWERDOWN_SHIFT 4U +#define PIXENGCFG_STORE9_STATIC_STORE9_SYNC_MODE_MASK 0x100U +#define PIXENGCFG_STORE9_STATIC_STORE9_SYNC_MODE_SHIFT 8U +#define PIXENGCFG_STORE9_STATIC_STORE9_SYNC_MODE__SINGLE 0U +#define PIXENGCFG_STORE9_STATIC_STORE9_SYNC_MODE__AUTO 0x1U +#define PIXENGCFG_STORE9_STATIC_STORE9_SW_RESET_MASK 0x800U +#define PIXENGCFG_STORE9_STATIC_STORE9_SW_RESET_SHIFT 11U +/* Field Value: STORE9_SW_RESET__OPERATION, Normal Operation */ +#define PIXENGCFG_STORE9_STATIC_STORE9_SW_RESET__OPERATION 0U +/* Field Value: STORE9_SW_RESET__SWRESET, Software Reset */ +#define PIXENGCFG_STORE9_STATIC_STORE9_SW_RESET__SWRESET 0x1U +#define PIXENGCFG_STORE9_STATIC_STORE9_DIV_MASK 0xFF0000U +#define PIXENGCFG_STORE9_STATIC_STORE9_DIV_SHIFT 16U + +#define PIXENGCFG_STORE9_DYNAMIC ((uint32_t)(0x94C)) + +#define FETCHDECODE9_STATICCONTROL ((uint32_t)(0x1008)) +#define FETCHDECODE9_STATICCONTROL_OFFSET ((uint32_t)(0x8)) +#define FETCHDECODE9_STATICCONTROL_RESET_VALUE 0U +#define FETCHDECODE9_STATICCONTROL_SHDEN_MASK 0x1U +#define FETCHDECODE9_STATICCONTROL_SHDEN_SHIFT 0U +#define FETCHDECODE9_STATICCONTROL_BASEADDRESSAUTOUPDATE_MASK 0xFF0000U +#define FETCHDECODE9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT 16U + +#define FETCHDECODE9_BURSTBUFFERMANAGEMENT ((uint32_t)(0x100C)) +#define FETCHDECODE9_BASEADDRESS0 ((uint32_t)(0x101C)) +#define FETCHDECODE9_SOURCEBUFFERATTRIBUTES0 ((uint32_t)(0x1020)) +#define FETCHDECODE9_SOURCEBUFFERDIMENSION0 ((uint32_t)(0x1024)) +#define FETCHDECODE9_COLORCOMPONENTBITS0 ((uint32_t)(0x1028)) +#define FETCHDECODE9_COLORCOMPONENTSHIFT0 ((uint32_t)(0x102C)) +#define FETCHDECODE9_LAYEROFFSET0 ((uint32_t)(0x1030)) +#define FETCHDECODE9_CLIPWINDOWOFFSET0 ((uint32_t)(0x1034)) +#define FETCHDECODE9_CLIPWINDOWDIMENSIONS0 ((uint32_t)(0x1038)) +#define FETCHDECODE9_CONSTANTCOLOR0 ((uint32_t)(0x103C)) +#define FETCHDECODE9_LAYERPROPERTY0 ((uint32_t)(0x1040)) +#define FETCHDECODE9_FRAMEDIMENSIONS ((uint32_t)(0x1044)) +#define FETCHDECODE9_FRAMERESAMPLING ((uint32_t)(0x1048)) +#define FETCHDECODE9_CONTROL ((uint32_t)(0x1054)) + +#define FETCHWARP9_STATICCONTROL ((uint32_t)(0x1808)) +#define FETCHWARP9_STATICCONTROL_OFFSET ((uint32_t)(0x8)) +#define FETCHWARP9_STATICCONTROL_RESET_VALUE 0xFF000000U +#define FETCHWARP9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU +#define FETCHWARP9_STATICCONTROL_SHDEN_MASK 0x1U +#define FETCHWARP9_STATICCONTROL_SHDEN_SHIFT 0U +#define FETCHWARP9_STATICCONTROL_BASEADDRESSAUTOUPDATE_MASK 0xFF0000U +#define FETCHWARP9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT 16U +#define FETCHWARP9_STATICCONTROL_SHDLDREQSTICKY_MASK 0xFF000000U +#define FETCHWARP9_STATICCONTROL_SHDLDREQSTICKY_SHIFT 24U + +#define FETCHWARP9_BURSTBUFFERMANAGEMENT ((uint32_t)(0x180C)) +#define FETCHWARP9_BASEADDRESS0 ((uint32_t)(0x1810)) +#define FETCHWARP9_SOURCEBUFFERATTRIBUTES0 ((uint32_t)(0x1814)) +#define FETCHWARP9_SOURCEBUFFERDIMENSION0 ((uint32_t)(0x1818)) +#define FETCHWARP9_COLORCOMPONENTBITS0 ((uint32_t)(0x181C)) +#define FETCHWARP9_COLORCOMPONENTSHIFT0 ((uint32_t)(0x1820)) +#define FETCHWARP9_LAYEROFFSET0 ((uint32_t)(0x1824)) +#define FETCHWARP9_CLIPWINDOWOFFSET0 ((uint32_t)(0x1828)) +#define FETCHWARP9_CLIPWINDOWDIMENSIONS0 ((uint32_t)(0x182C)) +#define FETCHWARP9_CONSTANTCOLOR0 ((uint32_t)(0x1830)) +#define FETCHWARP9_LAYERPROPERTY0 ((uint32_t)(0x1834)) +#define FETCHWARP9_FRAMEDIMENSIONS ((uint32_t)(0x1950)) +#define FETCHWARP9_FRAMERESAMPLING ((uint32_t)(0x1954)) +#define FETCHWARP9_CONTROL ((uint32_t)(0x1970)) + + +#define FETCHECO9_STATICCONTROL ((uint32_t)(0x1C08)) +#define FETCHECO9_STATICCONTROL_OFFSET ((uint32_t)(0x8)) +#define FETCHECO9_STATICCONTROL_RESET_VALUE 0U +#define FETCHECO9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU +#define FETCHECO9_STATICCONTROL_SHDEN_MASK 0x1U +#define FETCHECO9_STATICCONTROL_SHDEN_SHIFT 0U +#define FETCHECO9_STATICCONTROL_BASEADDRESSAUTOUPDATE_MASK 0xFF0000U +#define FETCHECO9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT 16U + +#define FETCHECO9_BURSTBUFFERMANAGEMENT ((uint32_t)(0x1C0C)) +#define FETCHECO9_BASEADDRESS0 ((uint32_t)(0x1C10)) +#define FETCHECO9_SOURCEBUFFERATTRIBUTES0 ((uint32_t)(0x1C14)) +#define FETCHECO9_SOURCEBUFFERDIMENSION0 ((uint32_t)(0x1C18)) +#define FETCHECO9_COLORCOMPONENTBITS0 ((uint32_t)(0x1C1C)) +#define FETCHECO9_COLORCOMPONENTSHIFT0 ((uint32_t)(0x1C20)) +#define FETCHECO9_LAYEROFFSET0 ((uint32_t)(0x1C24)) +#define FETCHECO9_CLIPWINDOWOFFSET0 ((uint32_t)(0x1C28)) +#define FETCHECO9_CLIPWINDOWDIMENSIONS0 ((uint32_t)(0x1C2C)) +#define FETCHECO9_CONSTANTCOLOR0 ((uint32_t)(0x1C30)) +#define FETCHECO9_LAYERPROPERTY0 ((uint32_t)(0x1C34)) +#define FETCHECO9_FRAMEDIMENSIONS ((uint32_t)(0x1C38)) +#define FETCHECO9_FRAMERESAMPLING ((uint32_t)(0x1C3C)) +#define FETCHECO9_CONTROL ((uint32_t)(0x1C40)) + + +#define ROP9_STATICCONTROL ((uint32_t)(0x2008)) +#define ROP9_STATICCONTROL_OFFSET ((uint32_t)(0x8)) +#define ROP9_STATICCONTROL_RESET_VALUE 0U +#define ROP9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU +#define ROP9_STATICCONTROL_SHDEN_MASK 0x1U +#define ROP9_STATICCONTROL_SHDEN_SHIFT 0U + +#define ROP9_CONTROL ((uint32_t)(0x200C)) + +#define MATRIX9_STATICCONTROL ((uint32_t)(0x2C08)) +#define MATRIX9_STATICCONTROL_OFFSET ((uint32_t)(0x8)) +#define MATRIX9_STATICCONTROL_RESET_VALUE 0U +#define MATRIX9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU +#define MATRIX9_STATICCONTROL_SHDEN_MASK 0x1U +#define MATRIX9_STATICCONTROL_SHDEN_SHIFT 0U + +#define MATRIX9_CONTROL ((uint32_t)(0x2C0C)) + +#define HSCALER9_SETUP1 ((uint32_t)(0x300C)) +#define HSCALER9_SETUP2 ((uint32_t)(0x3010)) +#define HSCALER9_CONTROL ((uint32_t)(0x3014)) + +#define VSCALER9_STATICCONTROL ((uint32_t)(0x3408)) +#define VSCALER9_STATICCONTROL_OFFSET ((uint32_t)(0x8)) +#define VSCALER9_STATICCONTROL_RESET_VALUE 0U +#define VSCALER9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU +#define VSCALER9_STATICCONTROL_SHDEN_MASK 0x1U +#define VSCALER9_STATICCONTROL_SHDEN_SHIFT 0U + +#define VSCALER9_SETUP1 ((uint32_t)(0x340C)) +#define VSCALER9_SETUP2 ((uint32_t)(0x3410)) +#define VSCALER9_SETUP3 ((uint32_t)(0x3414)) +#define VSCALER9_SETUP4 ((uint32_t)(0x3418)) +#define VSCALER9_SETUP5 ((uint32_t)(0x341C)) +#define VSCALER9_CONTROL ((uint32_t)(0x3420)) + +#define HSCALER9_STATICCONTROL ((uint32_t)(0x3008)) +#define HSCALER9_STATICCONTROL_OFFSET ((uint32_t)(0x8)) +#define HSCALER9_STATICCONTROL_RESET_VALUE 0U +#define HSCALER9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU +#define HSCALER9_STATICCONTROL_SHDEN_MASK 0x1U +#define HSCALER9_STATICCONTROL_SHDEN_SHIFT 0U + +#define BLITBLEND9_STATICCONTROL ((uint32_t)(0x3C08)) +#define BLITBLEND9_STATICCONTROL_OFFSET ((uint32_t)(0x8)) +#define BLITBLEND9_STATICCONTROL_RESET_VALUE 0U +#define BLITBLEND9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU +#define BLITBLEND9_STATICCONTROL_SHDEN_MASK 0x1U +#define BLITBLEND9_STATICCONTROL_SHDEN_SHIFT 0U + +#define BLITBLEND9_CONTROL ((uint32_t)(0x3C0C)) +#define BLITBLEND9_CONSTANTCOLOR ((uint32_t)(0x3C14)) +#define BLITBLEND9_COLORREDBLENDFUNCTION ((uint32_t)(0x3C18)) +#define BLITBLEND9_COLORGREENBLENDFUNCTION ((uint32_t)(0x3C1C)) +#define BLITBLEND9_COLORBLUEBLENDFUNCTION ((uint32_t)(0x3C20)) +#define BLITBLEND9_ALPHABLENDFUNCTION ((uint32_t)(0x3C24)) +#define BLITBLEND9_BLENDMODE1 ((uint32_t)(0x3C28)) +#define BLITBLEND9_BLENDMODE2 ((uint32_t)(0x3C2C)) + + +#define STORE9_STATICCONTROL ((uint32_t)(0x4008)) +#define STORE9_STATICCONTROL_OFFSET ((uint32_t)(0x8)) +#define STORE9_STATICCONTROL_RESET_VALUE 0U +#define STORE9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU +#define STORE9_STATICCONTROL_SHDEN_MASK 0x1U +#define STORE9_STATICCONTROL_SHDEN_SHIFT 0U +#define STORE9_STATICCONTROL_BASEADDRESSAUTOUPDATE_MASK 0x100U +#define STORE9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT 8U + +#define STORE9_BURSTBUFFERMANAGEMENT ((uint32_t)(0x400C)) +#define STORE9_BASEADDRESS ((uint32_t)(0x4018)) +#define STORE9_DESTINATIONBUFFERATTRIBUTES ((uint32_t)(0x401C)) +#define STORE9_DESTINATIONBUFFERDIMENSION ((uint32_t)(0x4020)) +#define STORE9_FRAMEOFFSET ((uint32_t)(0x4024)) +#define STORE9_COLORCOMPONENTBITS ((uint32_t)(0x4028)) +#define STORE9_COLORCOMPONENTSHIFT ((uint32_t)(0x402C)) +#define STORE9_CONTROL ((uint32_t)(0x4030)) + +#define STORE9_START ((uint32_t)(0x403C)) + +/* pixengcfg */ +#define PIXENGCFG_CLKEN_MASK 0x3000000U +#define PIXENGCFG_CLKEN_SHIFT 24U +/* Field Value: _CLKEN__DISABLE, Clock for block is disabled */ +#define PIXENGCFG_CLKEN__DISABLE 0U +#define PIXENGCFG_CLKEN__AUTOMATIC 0x1U +/* Field Value: _CLKEN__FULL, Clock for block is without gating */ +#define PIXENGCFG_CLKEN__FULL 0x3U + +#define PIXENGCFG_DIVIDER_RESET 0x80 + + +/* command sequencer */ +#define CMDSEQ_HIF ((uint32_t)(0x400)) + +#define CMDSEQ_LOCKUNLOCKHIF ((uint32_t)(0x500)) +#define CMDSEQ_LOCKUNLOCKHIF_LOCKUNLOCKHIF__LOCK_KEY 0x5651F763U +#define CMDSEQ_LOCKUNLOCKHIF_LOCKUNLOCKHIF__UNLOCK_KEY 0x691DB936U + +#define CMDSEQ_LOCKUNLOCK ((uint32_t)(0x580)) +#define CMDSEQ_LOCKUNLOCK_LOCKUNLOCK__LOCK_KEY 0x5651F763U +#define CMDSEQ_LOCKUNLOCK_LOCKUNLOCK__UNLOCK_KEY 0x691DB936U + +#define CMDSEQ_BUFFERADDRESS ((uint32_t)(0x588)) +#define CMDSEQ_BUFFERSIZE ((uint32_t)(0x58C)) + +#define CMDSEQ_CONTROL ((uint32_t)(0x594)) +#define CMDSEQ_CONTROL_OFFSET ((uint32_t)(0x194)) +#define CMDSEQ_CONTROL_RESET_VALUE 0U +#define CMDSEQ_CONTROL_RESET_MASK 0xFFFFFFFFU +#define CMDSEQ_CONTROL_CLRAXIW_MASK 0x1U +#define CMDSEQ_CONTROL_CLRAXIW_SHIFT 0U +#define CMDSEQ_CONTROL_CLRRBUF_MASK 0x4U +#define CMDSEQ_CONTROL_CLRRBUF_SHIFT 2U +#define CMDSEQ_CONTROL_CLRCMDBUF_MASK 0x8U +#define CMDSEQ_CONTROL_CLRCMDBUF_SHIFT 3U +#define CMDSEQ_CONTROL_CLEAR_MASK 0x80000000U +#define CMDSEQ_CONTROL_CLEAR_SHIFT 31U + +#define CMDSEQ_STATUS ((uint32_t)(0x598)) +#define CMDSEQ_STATUS_OFFSET ((uint32_t)(0x198)) +#define CMDSEQ_STATUS_RESET_VALUE 0x41000080U +#define CMDSEQ_STATUS_RESET_MASK 0xFFFFFFFFU +#define CMDSEQ_STATUS_FIFOSPACE_MASK 0x1FFFFU +#define CMDSEQ_STATUS_IDLE_MASK 0x40000000U + +#endif diff --git a/drivers/gpu/imx/dpu-blit/dpu-blit.c b/drivers/gpu/imx/dpu-blit/dpu-blit.c new file mode 100644 index 000000000000..72e1c5f234ba --- /dev/null +++ b/drivers/gpu/imx/dpu-blit/dpu-blit.c @@ -0,0 +1,611 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2019,2022 NXP + * + * 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 <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <video/dpu.h> +#include <video/imx8-prefetch.h> + +#include "dpu-blit.h" +#include "dpu-blit-registers.h" +#include "dpu-prv.h" + +void dpu_be_wait(struct dpu_bliteng *dpu_be); + +static inline u32 dpu_be_read(struct dpu_bliteng *dpu_be, unsigned int offset) +{ + return readl(dpu_be->base + offset); +} + +static inline void dpu_be_write(struct dpu_bliteng *dpu_be, u32 value, + unsigned int offset) +{ + writel(value, dpu_be->base + offset); +} + +static void dpu_cs_wait_fifo_space(struct dpu_bliteng *dpu_be) +{ + while ((dpu_be_read(dpu_be, CMDSEQ_STATUS) & + CMDSEQ_STATUS_FIFOSPACE_MASK) < CMDSEQ_FIFO_SPACE_THRESHOLD) + usleep_range(30, 50); +} + +static void dpu_cs_wait_idle(struct dpu_bliteng *dpu_be) +{ + while ((dpu_be_read(dpu_be, CMDSEQ_STATUS) & + CMDSEQ_STATUS_IDLE_MASK) == 0x0) + mdelay(1); +} + +static int dpu_cs_alloc_command_buffer(struct dpu_bliteng *dpu_be) +{ + /* command buffer need 32 bit address */ + dpu_be->buffer_addr_virt = + alloc_pages_exact(COMMAND_BUFFER_SIZE, + GFP_KERNEL | GFP_DMA | GFP_DMA32 | __GFP_ZERO); + if (!dpu_be->buffer_addr_virt) { + dev_err(dpu_be->dev, "memory alloc failed for dpu command buffer\n"); + return -ENOMEM; + } + + dpu_be->buffer_addr_phy = + (u32)virt_to_phys(dpu_be->buffer_addr_virt); + + return 0; +} + +static void dpu_cs_static_setup(struct dpu_bliteng *dpu_be) +{ + dpu_cs_wait_idle(dpu_be); + + /* LockUnlock and LockUnlockHIF */ + dpu_be_write(dpu_be, CMDSEQ_LOCKUNLOCKHIF_LOCKUNLOCKHIF__UNLOCK_KEY, + CMDSEQ_LOCKUNLOCKHIF); + dpu_be_write(dpu_be, CMDSEQ_LOCKUNLOCK_LOCKUNLOCK__UNLOCK_KEY, + CMDSEQ_LOCKUNLOCK); + + /* Control */ + dpu_be_write(dpu_be, 1 << CMDSEQ_CONTROL_CLEAR_SHIFT, + CMDSEQ_CONTROL); + + /* BufferAddress and BufferSize */ + dpu_be_write(dpu_be, dpu_be->buffer_addr_phy, CMDSEQ_BUFFERADDRESS); + dpu_be_write(dpu_be, COMMAND_BUFFER_SIZE / WORD_SIZE, + CMDSEQ_BUFFERSIZE); +} + +static struct dprc * +dpu_be_dprc_get(struct dpu_soc *dpu, int dprc_id) +{ + struct dprc *dprc; + + dprc = dprc_lookup_by_phandle(dpu->dev, + "fsl,dpr-channels", + dprc_id); + + return dprc; +} + +void dpu_be_configure_prefetch(struct dpu_bliteng *dpu_be, + u32 width, u32 height, + u32 x_offset, u32 y_offset, + u32 stride, u32 format, u64 modifier, + u64 baddr, u64 uv_addr) +{ + struct dprc *dprc; + bool dprc_en=false; + + /* Enable DPR, dprc1 is connected to plane0 */ + dprc = dpu_be->dprc[1]; + + /* + * Force sync command sequncer in conditions: + * 1. tile work with dprc/prg (baddr) + * 2. switch tile to linear (!start) + */ + if (!dpu_be->start || baddr) { + dpu_be_wait(dpu_be); + } + + dpu_be->sync = true; + + if (baddr == 0x0) { + if (!dpu_be->start) { + dprc_disable(dprc); + dpu_be->start = true; + } + return; + } + + if (dpu_be->modifier != modifier && !dpu_be->start) { + dprc_disable(dprc); + dprc_en = true; + } + + dpu_be->modifier = modifier; + + dprc_configure(dprc, 0, + width, height, + x_offset, y_offset, + stride, format, modifier, + baddr, uv_addr, + dpu_be->start, + dpu_be->start, + false); + + if (dpu_be->start || dprc_en) { + dprc_enable(dprc); + } + + dprc_reg_update(dprc); + + dpu_be->start = false; +} +EXPORT_SYMBOL(dpu_be_configure_prefetch); + +int dpu_bliteng_get_empty_instance(struct dpu_bliteng **dpu_be, + struct device *dev) +{ + if (!dpu_be || !dev) + return -EINVAL; + + *dpu_be = devm_kzalloc(dev, sizeof(struct dpu_bliteng), GFP_KERNEL); + if (!(*dpu_be)) + return -ENOMEM; + + return 0; +} +EXPORT_SYMBOL(dpu_bliteng_get_empty_instance); + +u32 *dpu_bliteng_get_cmd_list(struct dpu_bliteng *dpu_be) +{ + return dpu_be->cmd_list; +} +EXPORT_SYMBOL(dpu_bliteng_get_cmd_list); + +s32 dpu_bliteng_get_id(struct dpu_bliteng *dpu_be) +{ + return dpu_be->id; +} +EXPORT_SYMBOL(dpu_bliteng_get_id); + +void dpu_bliteng_set_id(struct dpu_bliteng *dpu_be, int id) +{ + dpu_be->id = id; +} +EXPORT_SYMBOL(dpu_bliteng_set_id); + +void dpu_bliteng_set_dev(struct dpu_bliteng *dpu_be, struct device *dev) +{ + dpu_be->dev = dev; +} +EXPORT_SYMBOL(dpu_bliteng_set_dev); + +int dpu_be_get(struct dpu_bliteng *dpu_be) +{ + mutex_lock(&dpu_be->mutex); + + return 0; +} +EXPORT_SYMBOL(dpu_be_get); + +void dpu_be_put(struct dpu_bliteng *dpu_be) +{ + mutex_unlock(&dpu_be->mutex); +} +EXPORT_SYMBOL(dpu_be_put); + +static irqreturn_t dpu_bliteng_irq_handler(int irq, void *dev_id) +{ + int i = 0; + struct dpu_bliteng *dpu_be = dev_id; + struct dpu_be_fence *fence = NULL; + + for (i = 0; i < 4; i++) { + if (irq == dpu_be->irq_comctrl_sw[i]) + break; + } + + if (i < 4) { + /* Found and release the fence */ + fence = dpu_be->fence[i]; + dpu_be->fence[i] = NULL; + up(&dpu_be->sema[i]); + } + + if (fence) { + /* Signal the fence when all triggered */ + if (atomic_dec_and_test(&fence->refcnt)) { + dma_fence_signal(&fence->base); + fence->signaled = true; + } + + dma_fence_put(&fence->base); + } + + return IRQ_HANDLED; +} + +static const char *dpu_be_fence_get_driver_name(struct dma_fence *fence) +{ + return "imx-dpu-bliteng"; +} + +static const char *dpu_be_fence_get_timeline_name(struct dma_fence *fence) +{ + return "dpu-bliteng-fence"; +} + +static bool dpu_be_fence_signaled(struct dma_fence *fence) +{ + struct dpu_be_fence *bfence = (struct dpu_be_fence *)fence; + + return bfence->signaled; +} + +static bool dpu_be_fence_enable_signaling(struct dma_fence *fence) +{ + return !dpu_be_fence_signaled(fence); +} + +static struct dma_fence_ops dpu_be_fence_ops = { + .get_driver_name = dpu_be_fence_get_driver_name, + .get_timeline_name = dpu_be_fence_get_timeline_name, + .enable_signaling = dpu_be_fence_enable_signaling, + .signaled = dpu_be_fence_signaled, + .release = dma_fence_free, +}; + +int dpu_be_get_fence(struct dpu_bliteng *dpu_be) +{ + int fd = -1; + u64 seqno = 0; + struct dpu_be_fence *fence = NULL; + struct sync_file *sync = NULL; + + /* Allocate a fence pointer */ + fence = kzalloc(sizeof(struct dpu_be_fence), GFP_KERNEL); + if (!fence) + return -1; + + /* Init fence pointer */ + fence->signaled = false; + spin_lock_init(&fence->lock); + atomic_set(&fence->refcnt, 0); + + /* Init dma fence base data */ + seqno = atomic64_inc_return(&dpu_be->seqno); + dma_fence_init(&fence->base, &dpu_be_fence_ops, &fence->lock, dpu_be->context, seqno); + + /* Create sync file pointer */ + sync = sync_file_create(&fence->base); + if (!sync) { + dev_err(dpu_be->dev, "failed to create fence sync file.\n"); + goto failed; + } + + /* Get the unused file descriptor */ + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) { + dev_err(dpu_be->dev, "failed to get unused file descriptor.\n"); + goto failed; + } + + /* Setup fd to fence sync */ + fd_install(fd, sync->file); + + return fd; + +failed: + if (sync) + fput(sync->file); + + kfree(fence); + + return -1; +} +EXPORT_SYMBOL(dpu_be_get_fence); + +static int dpu_be_emit_fence(struct dpu_bliteng *dpu_be, struct dpu_be_fence *fence, bool stall) +{ + int i = 0; + + /* Get the available fence index with spin-lock */ + spin_lock(&dpu_be->lock); + i = dpu_be->next_fence_idx++; + dpu_be->next_fence_idx &= 0x3; + spin_unlock(&dpu_be->lock); + + /*Acquire fence semaphore released by irq handler */ + down(&dpu_be->sema[i]); + + WARN_ON(dpu_be->fence[i]); + dpu_be->fence[i] = fence; + + dpu_cs_wait_fifo_space(dpu_be); + + /* Write comctrl interrupt PRESET to command sequencer */ + dpu_be_write(dpu_be, 0x14000001, CMDSEQ_HIF); + dpu_be_write(dpu_be, COMCTRL_USERINTERRUPTPRESET1, CMDSEQ_HIF); + dpu_be_write(dpu_be, 1 << (i + (IRQ_COMCTRL_SW0 & 0x1F)), CMDSEQ_HIF); + + /*Stall until semaphore released */ + if (stall) { + down(&dpu_be->sema[i]); + up(&dpu_be->sema[i]); + } + + return 0; +} + +int dpu_be_set_fence(struct dpu_bliteng *dpu_be, int fd) +{ + struct dpu_be_fence *fence = NULL; + + /* Retrieve fence pointer from sync file descriptor */ + fence = (struct dpu_be_fence *)sync_file_get_fence(fd); + if (!fence) { + dev_err(dpu_be->dev, "failed to get fence pointer.\n"); + return -1; + } + + /* Setup the fence and active it asynchronously */ + dpu_be_emit_fence(dpu_be, fence, false); + + /* Increase fence and base reference */ + atomic_inc(&fence->refcnt); + dma_fence_get(&fence->base); + + return 0; +} +EXPORT_SYMBOL(dpu_be_set_fence); + +int dpu_be_blit(struct dpu_bliteng *dpu_be, + u32 *cmdlist, u32 cmdnum) +{ + int i; + + if (cmdnum > CMDSEQ_FIFO_SPACE_THRESHOLD) { + dev_err(dpu_be->dev, "dpu blit cmdnum[%d] should be less than %d !\n", + cmdnum, CMDSEQ_FIFO_SPACE_THRESHOLD); + return -EINVAL; + } + dpu_cs_wait_fifo_space(dpu_be); + + for (i = 0; i < cmdnum; i++) + dpu_be_write(dpu_be, cmdlist[i], CMDSEQ_HIF); + + return 0; +} +EXPORT_SYMBOL(dpu_be_blit); + +void dpu_be_wait(struct dpu_bliteng *dpu_be) +{ + if (!dpu_be->sync) return; + + /* Emit empty fence and stall until signaled */ + dpu_be_emit_fence(dpu_be, NULL, true); + + dpu_be->sync = false; +} +EXPORT_SYMBOL(dpu_be_wait); + +static void dpu_be_init_units(struct dpu_bliteng *dpu_be) +{ + u32 staticcontrol; + u32 pixengcfg_unit_dynamic; + + staticcontrol = + 1 << FETCHDECODE9_STATICCONTROL_SHDEN_SHIFT | + 0 << FETCHDECODE9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT | + FETCHDECODE9_STATICCONTROL_RESET_VALUE; + dpu_be_write(dpu_be, staticcontrol, FETCHDECODE9_STATICCONTROL); + + staticcontrol = + 1 << FETCHWARP9_STATICCONTROL_SHDEN_SHIFT | + 0 << FETCHWARP9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT | + FETCHWARP9_STATICCONTROL_RESET_VALUE; + dpu_be_write(dpu_be, staticcontrol, FETCHWARP9_STATICCONTROL); + + staticcontrol = + 1 << FETCHECO9_STATICCONTROL_SHDEN_SHIFT | + 0 << FETCHECO9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT | + FETCHECO9_STATICCONTROL_RESET_VALUE; + dpu_be_write(dpu_be, staticcontrol, FETCHECO9_STATICCONTROL); + + staticcontrol = + 1 << HSCALER9_STATICCONTROL_SHDEN_SHIFT | + HSCALER9_STATICCONTROL_RESET_VALUE; + dpu_be_write(dpu_be, staticcontrol, HSCALER9_STATICCONTROL); + + staticcontrol = + 1 << VSCALER9_STATICCONTROL_SHDEN_SHIFT | + VSCALER9_STATICCONTROL_RESET_VALUE; + dpu_be_write(dpu_be, staticcontrol, VSCALER9_STATICCONTROL); + + staticcontrol = + 1 << ROP9_STATICCONTROL_SHDEN_SHIFT | + ROP9_STATICCONTROL_RESET_VALUE; + dpu_be_write(dpu_be, staticcontrol, ROP9_STATICCONTROL); + + staticcontrol = + 1 << MATRIX9_STATICCONTROL_SHDEN_SHIFT | + MATRIX9_STATICCONTROL_RESET_VALUE; + dpu_be_write(dpu_be, staticcontrol, MATRIX9_STATICCONTROL); + + staticcontrol = + 1 << BLITBLEND9_STATICCONTROL_SHDEN_SHIFT | + BLITBLEND9_STATICCONTROL_RESET_VALUE; + dpu_be_write(dpu_be, staticcontrol, BLITBLEND9_STATICCONTROL); + + staticcontrol = + 1 << STORE9_STATICCONTROL_SHDEN_SHIFT | + 0 << STORE9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT | + STORE9_STATICCONTROL_RESET_VALUE; + dpu_be_write(dpu_be, staticcontrol, STORE9_STATICCONTROL); + + /* Safety_Pixengcfg Dynamic */ + pixengcfg_unit_dynamic = + PIXENGCFG_CLKEN__AUTOMATIC << PIXENGCFG_CLKEN_SHIFT | + PIXENGCFG_FETCHDECODE9_DYNAMIC_RESET_VALUE; + dpu_be_write(dpu_be, pixengcfg_unit_dynamic, + PIXENGCFG_FETCHDECODE9_DYNAMIC); + + pixengcfg_unit_dynamic = + PIXENGCFG_CLKEN__AUTOMATIC << PIXENGCFG_CLKEN_SHIFT | + PIXENGCFG_FETCHWARP9_DYNAMIC_RESET_VALUE; + dpu_be_write(dpu_be, pixengcfg_unit_dynamic, + PIXENGCFG_FETCHWARP9_DYNAMIC); + + pixengcfg_unit_dynamic = + PIXENGCFG_CLKEN__AUTOMATIC << PIXENGCFG_CLKEN_SHIFT | + PIXENGCFG_ROP9_DYNAMIC_RESET_VALUE; + dpu_be_write(dpu_be, pixengcfg_unit_dynamic, + PIXENGCFG_ROP9_DYNAMIC); + + pixengcfg_unit_dynamic = + PIXENGCFG_CLKEN__AUTOMATIC << PIXENGCFG_CLKEN_SHIFT | + PIXENGCFG_MATRIX9_DYNAMIC_RESET_VALUE; + dpu_be_write(dpu_be, pixengcfg_unit_dynamic, + PIXENGCFG_MATRIX9_DYNAMIC); + + pixengcfg_unit_dynamic = + PIXENGCFG_CLKEN__AUTOMATIC << PIXENGCFG_CLKEN_SHIFT | + PIXENGCFG_HSCALER9_DYNAMIC_RESET_VALUE; + dpu_be_write(dpu_be, pixengcfg_unit_dynamic, + PIXENGCFG_HSCALER9_DYNAMIC); + + pixengcfg_unit_dynamic = + PIXENGCFG_CLKEN__AUTOMATIC << PIXENGCFG_CLKEN_SHIFT | + PIXENGCFG_VSCALER9_DYNAMIC_RESET_VALUE; + dpu_be_write(dpu_be, pixengcfg_unit_dynamic, + PIXENGCFG_VSCALER9_DYNAMIC); + + pixengcfg_unit_dynamic = + PIXENGCFG_CLKEN__AUTOMATIC << PIXENGCFG_CLKEN_SHIFT | + PIXENGCFG_BLITBLEND9_DYNAMIC_RESET_VALUE; + dpu_be_write(dpu_be, pixengcfg_unit_dynamic, + PIXENGCFG_BLITBLEND9_DYNAMIC); +} + +int dpu_bliteng_init(struct dpu_bliteng *dpu_bliteng) +{ + struct dpu_soc *dpu = dev_get_drvdata(dpu_bliteng->dev->parent); + struct platform_device *dpu_pdev = + container_of(dpu->dev, struct platform_device, dev); + struct resource *res; + unsigned long dpu_base; + void __iomem *base; + u32 *cmd_list; + int ret; + int i, virq, sw_irqs[4] = {IRQ_COMCTRL_SW0, IRQ_COMCTRL_SW1, + IRQ_COMCTRL_SW2, IRQ_COMCTRL_SW3}; + + cmd_list = kzalloc(sizeof(*cmd_list) * CMDSEQ_FIFO_SPACE_THRESHOLD, + GFP_KERNEL); + if (!cmd_list) + return -ENOMEM; + dpu_bliteng->cmd_list = cmd_list; + + res = platform_get_resource(dpu_pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + dpu_base = res->start; + + /* remap with bigger size */ + base = devm_ioremap(dpu->dev, dpu_base, 64*SZ_1K); + dpu_bliteng->base = base; + dpu_bliteng->dpu = dpu; + + mutex_init(&dpu_bliteng->mutex); + + /* Init the uints used by blit engine */ + /* Maybe this should be in dpu-common.c */ + dpu_be_init_units(dpu_bliteng); + + /* Init for command sequencer */ + ret = dpu_cs_alloc_command_buffer(dpu_bliteng); + if (ret) + return ret; + + dpu_cs_static_setup(dpu_bliteng); + + /*Request general SW interrupts to implement DPU fence */ + for (i = 0; i < 4; i++) { + virq = dpu_map_irq(dpu, sw_irqs[i]); + dpu_bliteng->irq_comctrl_sw[i] = virq; + + irq_set_status_flags(virq, IRQ_DISABLE_UNLAZY); + ret = devm_request_irq(dpu->dev, virq, dpu_bliteng_irq_handler, 0, + "imx-dpu-bliteng", dpu_bliteng); + if (ret < 0) { + dev_err(dpu->dev, "irq_comctrl_sw%d irq request failed with %d.\n", i, ret); + return ret; + } + + sema_init(&dpu_bliteng->sema[i], 1); + } + + /* Init fence context and seqno */ + dpu_bliteng->context = dma_fence_context_alloc(1); + atomic64_set(&dpu_bliteng->seqno, 0); + spin_lock_init(&dpu_bliteng->lock); + + /* DPR, each blit engine has two dprc, 0 & 1 */ + dpu_bliteng->dprc[0] = dpu_be_dprc_get(dpu, 0); + dpu_bliteng->dprc[1] = dpu_be_dprc_get(dpu, 1); + + dprc_disable(dpu_bliteng->dprc[0]); + dprc_disable(dpu_bliteng->dprc[1]); + + dpu_bliteng->start = true; + dpu_bliteng->sync = false; + + dpu_bliteng->modifier = 0; + + return 0; +} +EXPORT_SYMBOL_GPL(dpu_bliteng_init); + +void dpu_bliteng_fini(struct dpu_bliteng *dpu_bliteng) +{ + int i; + + /* LockUnlock and LockUnlockHIF */ + dpu_be_write(dpu_bliteng, CMDSEQ_LOCKUNLOCKHIF_LOCKUNLOCKHIF__LOCK_KEY, + CMDSEQ_LOCKUNLOCKHIF); + dpu_be_write(dpu_bliteng, CMDSEQ_LOCKUNLOCK_LOCKUNLOCK__LOCK_KEY, + CMDSEQ_LOCKUNLOCK); + + kfree(dpu_bliteng->cmd_list); + + for (i = 0; i < 4; i++) + free_irq(dpu_bliteng->irq_comctrl_sw[i], dpu_bliteng); + + if (dpu_bliteng->buffer_addr_virt) + free_pages_exact(dpu_bliteng->buffer_addr_virt, + COMMAND_BUFFER_SIZE); +} +EXPORT_SYMBOL_GPL(dpu_bliteng_fini); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_DESCRIPTION("i.MX DPU BLITENG"); +MODULE_ALIAS("platform:imx-dpu-bliteng"); diff --git a/drivers/gpu/imx/dpu-blit/dpu-blit.h b/drivers/gpu/imx/dpu-blit/dpu-blit.h new file mode 100644 index 000000000000..fd8cc4908345 --- /dev/null +++ b/drivers/gpu/imx/dpu-blit/dpu-blit.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2018,2022 NXP + * + * 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. + */ + +#ifndef __DPU_BLIT_H__ +#define __DPU_BLIT_H__ + +#include <linux/file.h> +#include <linux/fdtable.h> +#include <linux/sync_file.h> +#include <linux/dma-fence.h> +#include <linux/dma-fence-array.h> + +#define COMMAND_BUFFER_SIZE 65536 /* up to 64k bytes */ +#define CMDSEQ_FIFO_SPACE_THRESHOLD 192 +#define WORD_SIZE 4 + +struct dpu_be_fence { + struct dma_fence base; + spinlock_t lock; + atomic_t refcnt; + bool signaled; +}; + +struct dpu_bliteng { + struct device *dev; + void __iomem *base; + s32 id; + struct mutex mutex; + + s32 irq_comctrl_sw[4]; + + struct semaphore sema[4]; + struct dpu_be_fence *fence[4]; + s32 next_fence_idx; + + atomic64_t seqno; + spinlock_t lock; + u64 context; + + void *buffer_addr_virt; + u32 buffer_addr_phy; + + u32 *cmd_list; + + struct dpu_soc *dpu; + + struct dprc *dprc[2]; + + bool start; + bool sync; + + u64 modifier; +}; + +#endif diff --git a/drivers/gpu/imx/dpu/Kconfig b/drivers/gpu/imx/dpu/Kconfig new file mode 100644 index 000000000000..d62891118907 --- /dev/null +++ b/drivers/gpu/imx/dpu/Kconfig @@ -0,0 +1,8 @@ +config IMX_DPU_CORE + tristate "i.MX DPU core support" + depends on ARCH_MXC + select GENERIC_IRQ_CHIP + help + Choose this if you have a Freescale i.MX8QM or i.MX8QXP system and + want to use the Display Processing Unit. This option only enables + DPU base support. diff --git a/drivers/gpu/imx/dpu/Makefile b/drivers/gpu/imx/dpu/Makefile new file mode 100644 index 000000000000..5b568e998d7e --- /dev/null +++ b/drivers/gpu/imx/dpu/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_IMX_DPU_CORE) += imx-dpu-core.o + +imx-dpu-core-objs := dpu-common.o dpu-constframe.o dpu-disengcfg.o \ + dpu-extdst.o dpu-fetchdecode.o dpu-fetcheco.o \ + dpu-fetchlayer.o dpu-fetchwarp.o dpu-fetchunit.o \ + dpu-framegen.o dpu-hscaler.o dpu-layerblend.o \ + dpu-sc-misc.o dpu-signature.o dpu-store.o dpu-tcon.o \ + dpu-vscaler.o diff --git a/drivers/gpu/imx/dpu/dpu-common.c b/drivers/gpu/imx/dpu/dpu-common.c new file mode 100644 index 000000000000..bda0dba8fede --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-common.c @@ -0,0 +1,1361 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2020,2022 NXP + * + * 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 <linux/clk.h> +#include <linux/fb.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/irqdomain.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_domain.h> +#include <video/dpu.h> +#include <video/imx8-pc.h> +#include <video/imx8-prefetch.h> +#include "dpu-prv.h" + +#define IMX_DPU_BLITENG_NAME "imx-drm-dpu-bliteng" + +static bool display_plane_video_proc = true; +module_param(display_plane_video_proc, bool, 0444); +MODULE_PARM_DESC(display_plane_video_proc, + "Enable video processing for display [default=true]"); + +#define DPU_CM_REG_DEFINE1(name1, name2) \ +static inline u32 name1(const struct cm_reg_ofs *ofs) \ +{ \ + return ofs->name2; \ +} + +#define DPU_CM_REG_DEFINE2(name1, name2) \ +static inline u32 name1(const struct cm_reg_ofs *ofs, \ + unsigned int n) \ +{ \ + return ofs->name2 + (4 * n); \ +} + +DPU_CM_REG_DEFINE1(LOCKUNLOCK, lockunlock); +DPU_CM_REG_DEFINE1(LOCKSTATUS, lockstatus); +DPU_CM_REG_DEFINE2(USERINTERRUPTMASK, userinterruptmask); +DPU_CM_REG_DEFINE2(INTERRUPTENABLE, interruptenable); +DPU_CM_REG_DEFINE2(INTERRUPTPRESET, interruptpreset); +DPU_CM_REG_DEFINE2(INTERRUPTCLEAR, interruptclear); +DPU_CM_REG_DEFINE2(INTERRUPTSTATUS, interruptstatus); +DPU_CM_REG_DEFINE2(USERINTERRUPTENABLE, userinterruptenable); +DPU_CM_REG_DEFINE2(USERINTERRUPTPRESET, userinterruptpreset); +DPU_CM_REG_DEFINE2(USERINTERRUPTCLEAR, userinterruptclear); +DPU_CM_REG_DEFINE2(USERINTERRUPTSTATUS, userinterruptstatus); +DPU_CM_REG_DEFINE1(GENERALPURPOSE, generalpurpose); + +static inline u32 dpu_cm_read(struct dpu_soc *dpu, unsigned int offset) +{ + return readl(dpu->cm_reg + offset); +} + +static inline void dpu_cm_write(struct dpu_soc *dpu, + unsigned int offset, u32 value) +{ + writel(value, dpu->cm_reg + offset); +} + +/* Constant Frame Unit */ +static const unsigned long cf_ofss[] = {0x4400, 0x5400, 0x4c00, 0x5c00}; +static const unsigned long cf_pec_ofss[] = {0x960, 0x9e0, 0x9a0, 0xa20}; + +/* Display Engine Configuration Unit */ +static const unsigned long dec_ofss[] = {0xb400, 0xb420}; + +/* External Destination Unit */ +static const unsigned long ed_ofss[] = {0x4800, 0x5800, 0x5000, 0x6000}; +static const unsigned long ed_pec_ofss[] = {0x980, 0xa00, 0x9c0, 0xa40}; + +/* Fetch Decode Unit */ +static const unsigned long fd_ofss[] = {0x6c00, 0x7800}; +static const unsigned long fd_pec_ofss[] = {0xa80, 0xaa0}; + +/* Fetch ECO Unit */ +static const unsigned long fe_ofss[] = {0x7400, 0x8000, 0x6800, 0x1c00}; +static const unsigned long fe_pec_ofss[] = {0xa90, 0xab0, 0xa70, 0x850}; + +/* Frame Generator Unit */ +static const unsigned long fg_ofss[] = {0xb800, 0xd400}; + +/* Fetch Layer Unit */ +static const unsigned long fl_ofss[] = {0x8400}; +static const unsigned long fl_pec_ofss[] = {0xac0}; + +/* Fetch Warp Unit */ +static const unsigned long fw_ofss[] = {0x6400}; +static const unsigned long fw_pec_ofss[] = {0xa60}; + +/* Horizontal Scaler Unit */ +static const unsigned long hs_ofss[] = {0x9000, 0x9c00, 0x3000}; +static const unsigned long hs_pec_ofss[] = {0xb00, 0xb60, 0x8c0}; + +/* Layer Blend Unit */ +static const unsigned long lb_ofss[] = {0xa400, 0xa800, 0xac00, 0xb000}; +static const unsigned long lb_pec_ofss[] = {0xba0, 0xbc0, 0xbe0, 0xc00}; + +/* Signature Unit */ +static const unsigned long sig_ofss[] = {0xd000, 0xec00}; + +/* Store Unit */ +static const unsigned long st_ofss[] = {0x4000}; +static const unsigned long st_pec_ofss[] = {0x940}; + +/* Timing Controller Unit */ +static const unsigned long tcon_ofss[] = {0xcc00, 0xe800}; + +/* Vertical Scaler Unit */ +static const unsigned long vs_ofss[] = {0x9400, 0xa000, 0x3400}; +static const unsigned long vs_pec_ofss[] = {0xb20, 0xb80, 0x8e0}; + +static const struct dpu_unit _cfs = { + .name = "ConstFrame", + .num = ARRAY_SIZE(cf_ids), + .ids = cf_ids, + .pec_ofss = cf_pec_ofss, + .ofss = cf_ofss, +}; + +static const struct dpu_unit _decs = { + .name = "DisEngCfg", + .num = ARRAY_SIZE(dec_ids), + .ids = dec_ids, + .pec_ofss = NULL, + .ofss = dec_ofss, +}; + +static const struct dpu_unit _eds = { + .name = "ExtDst", + .num = ARRAY_SIZE(ed_ids), + .ids = ed_ids, + .pec_ofss = ed_pec_ofss, + .ofss = ed_ofss, +}; + +static const struct dpu_unit _fds = { + .name = "FetchDecode", + .num = ARRAY_SIZE(fd_ids), + .ids = fd_ids, + .pec_ofss = fd_pec_ofss, + .ofss = fd_ofss, + .dprc_ids = fd_dprc_ids, +}; + +static const struct dpu_unit _fes = { + .name = "FetchECO", + .num = ARRAY_SIZE(fe_ids), + .ids = fe_ids, + .pec_ofss = fe_pec_ofss, + .ofss = fe_ofss, +}; + +static const struct dpu_unit _fgs = { + .name = "FrameGen", + .num = ARRAY_SIZE(fg_ids), + .ids = fg_ids, + .pec_ofss = NULL, + .ofss = fg_ofss, +}; + +static const struct dpu_unit _fls = { + .name = "FetchLayer", + .num = ARRAY_SIZE(fl_ids), + .ids = fl_ids, + .pec_ofss = fl_pec_ofss, + .ofss = fl_ofss, + .dprc_ids = fl_dprc_ids, +}; + +static const struct dpu_unit _fws = { + .name = "FetchWarp", + .num = ARRAY_SIZE(fw_ids), + .ids = fw_ids, + .pec_ofss = fw_pec_ofss, + .ofss = fw_ofss, + .dprc_ids = fw_dprc_ids, +}; + +static const struct dpu_unit _hss = { + .name = "HScaler", + .num = ARRAY_SIZE(hs_ids), + .ids = hs_ids, + .pec_ofss = hs_pec_ofss, + .ofss = hs_ofss, +}; + +static const struct dpu_unit _lbs = { + .name = "LayerBlend", + .num = ARRAY_SIZE(lb_ids), + .ids = lb_ids, + .pec_ofss = lb_pec_ofss, + .ofss = lb_ofss, +}; + +static const struct dpu_unit _sigs = { + .name = "Signature", + .num = ARRAY_SIZE(sig_ids), + .ids = sig_ids, + .pec_ofss = NULL, + .ofss = sig_ofss, +}; + +static const struct dpu_unit _sts = { + .name = "Store", + .num = ARRAY_SIZE(st_ids), + .ids = st_ids, + .pec_ofss = st_pec_ofss, + .ofss = st_ofss, +}; + +static const struct dpu_unit _tcons = { + .name = "TCon", + .num = ARRAY_SIZE(tcon_ids), + .ids = tcon_ids, + .pec_ofss = NULL, + .ofss = tcon_ofss, +}; + +static const struct dpu_unit _vss = { + .name = "VScaler", + .num = ARRAY_SIZE(vs_ids), + .ids = vs_ids, + .pec_ofss = vs_pec_ofss, + .ofss = vs_ofss, +}; + +static const struct cm_reg_ofs _cm_reg_ofs = { + .ipidentifier = 0, + .lockunlock = 0x40, + .lockstatus = 0x44, + .userinterruptmask = 0x48, + .interruptenable = 0x50, + .interruptpreset = 0x58, + .interruptclear = 0x60, + .interruptstatus = 0x68, + .userinterruptenable = 0x80, + .userinterruptpreset = 0x88, + .userinterruptclear = 0x90, + .userinterruptstatus = 0x98, + .generalpurpose = 0x100, +}; + +static const unsigned long unused_irq[] = {0x00000000, 0xfffe0008}; + +static const struct dpu_data dpu_data_qxp = { + .cm_ofs = 0x0, + .cfs = &_cfs, + .decs = &_decs, + .eds = &_eds, + .fds = &_fds, + .fes = &_fes, + .fgs = &_fgs, + .fls = &_fls, + .fws = &_fws, + .hss = &_hss, + .lbs = &_lbs, + .sigs = &_sigs, + .sts = &_sts, + .tcons = &_tcons, + .vss = &_vss, + .cm_reg_ofs = &_cm_reg_ofs, + .unused_irq = unused_irq, + .plane_src_mask = DPU_PLANE_SRC_FL0_ID | DPU_PLANE_SRC_FW2_ID | + DPU_PLANE_SRC_FD0_ID | DPU_PLANE_SRC_FD1_ID, + .has_dual_ldb = true, + .syncmode_min_prate = UINT_MAX, /* pc is unused */ + .singlemode_max_width = UINT_MAX, /* pc is unused */ +}; + +static const struct dpu_data dpu_data_qm = { + .cm_ofs = 0x0, + .cfs = &_cfs, + .decs = &_decs, + .eds = &_eds, + .fds = &_fds, + .fes = &_fes, + .fgs = &_fgs, + .fls = &_fls, + .fws = &_fws, + .hss = &_hss, + .lbs = &_lbs, + .sigs = &_sigs, + .sts = &_sts, + .tcons = &_tcons, + .vss = &_vss, + .cm_reg_ofs = &_cm_reg_ofs, + .unused_irq = unused_irq, + .plane_src_mask = DPU_PLANE_SRC_FL0_ID | DPU_PLANE_SRC_FW2_ID | + DPU_PLANE_SRC_FD0_ID | DPU_PLANE_SRC_FD1_ID, + .has_dual_ldb = false, + .syncmode_min_prate = 300000, + .singlemode_max_width = 2560, + .master_stream_id = 1, +}; + +static const struct of_device_id dpu_dt_ids[] = { + { + .compatible = "fsl,imx8qxp-dpu", + .data = &dpu_data_qxp, + }, { + .compatible = "fsl,imx8qm-dpu", + .data = &dpu_data_qm, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, dpu_dt_ids); + +unsigned int dpu_get_syncmode_min_prate(struct dpu_soc *dpu) +{ + return dpu->data->syncmode_min_prate; +} +EXPORT_SYMBOL_GPL(dpu_get_syncmode_min_prate); + +unsigned int dpu_get_singlemode_max_width(struct dpu_soc *dpu) +{ + return dpu->data->singlemode_max_width; +} +EXPORT_SYMBOL_GPL(dpu_get_singlemode_max_width); + +unsigned int dpu_get_master_stream_id(struct dpu_soc *dpu) +{ + return dpu->data->master_stream_id; +} +EXPORT_SYMBOL_GPL(dpu_get_master_stream_id); + +bool dpu_vproc_has_fetcheco_cap(u32 cap_mask) +{ + return !!(cap_mask & DPU_VPROC_CAP_FETCHECO); +} +EXPORT_SYMBOL_GPL(dpu_vproc_has_fetcheco_cap); + +bool dpu_vproc_has_hscale_cap(u32 cap_mask) +{ + return !!(cap_mask & DPU_VPROC_CAP_HSCALE); +} +EXPORT_SYMBOL_GPL(dpu_vproc_has_hscale_cap); + +bool dpu_vproc_has_vscale_cap(u32 cap_mask) +{ + return !!(cap_mask & DPU_VPROC_CAP_VSCALE); +} +EXPORT_SYMBOL_GPL(dpu_vproc_has_vscale_cap); + +u32 dpu_vproc_get_fetcheco_cap(u32 cap_mask) +{ + return cap_mask & DPU_VPROC_CAP_FETCHECO; +} +EXPORT_SYMBOL_GPL(dpu_vproc_get_fetcheco_cap); + +u32 dpu_vproc_get_hscale_cap(u32 cap_mask) +{ + return cap_mask & DPU_VPROC_CAP_HSCALE; +} +EXPORT_SYMBOL_GPL(dpu_vproc_get_hscale_cap); + +u32 dpu_vproc_get_vscale_cap(u32 cap_mask) +{ + return cap_mask & DPU_VPROC_CAP_VSCALE; +} +EXPORT_SYMBOL_GPL(dpu_vproc_get_vscale_cap); + +int dpu_format_horz_chroma_subsampling(u32 format) +{ + switch (format) { + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + return 2; + default: + return 1; + } +} + +int dpu_format_vert_chroma_subsampling(u32 format) +{ + switch (format) { + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + return 2; + default: + return 1; + } +} + +int dpu_format_num_planes(u32 format) +{ + switch (format) { + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + case DRM_FORMAT_NV24: + case DRM_FORMAT_NV42: + return 2; + default: + return 1; + } +} + +int dpu_format_plane_width(int width, u32 format, int plane) +{ + if (plane >= dpu_format_num_planes(format)) + return 0; + + if (plane == 0) + return width; + + return width / dpu_format_horz_chroma_subsampling(format); +} + +int dpu_format_plane_height(int height, u32 format, int plane) +{ + if (plane >= dpu_format_num_planes(format)) + return 0; + + if (plane == 0) + return height; + + return height / dpu_format_vert_chroma_subsampling(format); +} + +static void dpu_detach_pm_domains(struct dpu_soc *dpu) +{ + if (dpu->pd_pll1_link && !IS_ERR(dpu->pd_pll1_link)) + device_link_del(dpu->pd_pll1_link); + if (dpu->pd_pll1_dev && !IS_ERR(dpu->pd_pll1_dev)) + dev_pm_domain_detach(dpu->pd_pll1_dev, true); + + if (dpu->pd_pll0_link && !IS_ERR(dpu->pd_pll0_link)) + device_link_del(dpu->pd_pll0_link); + if (dpu->pd_pll0_dev && !IS_ERR(dpu->pd_pll0_dev)) + dev_pm_domain_detach(dpu->pd_pll0_dev, true); + + if (dpu->pd_dc_link && !IS_ERR(dpu->pd_dc_link)) + device_link_del(dpu->pd_dc_link); + if (dpu->pd_dc_dev && !IS_ERR(dpu->pd_dc_dev)) + dev_pm_domain_detach(dpu->pd_dc_dev, true); + + dpu->pd_dc_dev = NULL; + dpu->pd_dc_link = NULL; + dpu->pd_pll0_dev = NULL; + dpu->pd_pll0_link = NULL; + dpu->pd_pll1_dev = NULL; + dpu->pd_pll1_link = NULL; +} + +static int dpu_attach_pm_domains(struct dpu_soc *dpu) +{ + struct device *dev = dpu->dev; + u32 flags = DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE; + int ret = 0; + + dpu->pd_dc_dev = dev_pm_domain_attach_by_name(dev, "dc"); + if (IS_ERR(dpu->pd_dc_dev)) { + ret = PTR_ERR(dpu->pd_dc_dev); + dev_err(dev, "Failed to attach dc pd dev: %d\n", ret); + goto fail; + } + dpu->pd_dc_link = device_link_add(dev, dpu->pd_dc_dev, flags); + if (IS_ERR(dpu->pd_dc_link)) { + ret = PTR_ERR(dpu->pd_dc_link); + dev_err(dev, "Failed to add device link to dc pd dev: %d\n", + ret); + goto fail; + } + + dpu->pd_pll0_dev = dev_pm_domain_attach_by_name(dev, "pll0"); + if (IS_ERR(dpu->pd_pll0_dev)) { + ret = PTR_ERR(dpu->pd_pll0_dev); + dev_err(dev, "Failed to attach pll0 pd dev: %d\n", ret); + goto fail; + } + dpu->pd_pll0_link = device_link_add(dev, dpu->pd_pll0_dev, flags); + if (IS_ERR(dpu->pd_pll0_link)) { + ret = PTR_ERR(dpu->pd_pll0_link); + dev_err(dev, "Failed to add device link to pll0 pd dev: %d\n", + ret); + goto fail; + } + + dpu->pd_pll1_dev = dev_pm_domain_attach_by_name(dev, "pll1"); + if (IS_ERR(dpu->pd_pll1_dev)) { + ret = PTR_ERR(dpu->pd_pll1_dev); + dev_err(dev, "Failed to attach pll0 pd dev: %d\n", ret); + goto fail; + } + dpu->pd_pll1_link = device_link_add(dev, dpu->pd_pll1_dev, flags); + if (IS_ERR(dpu->pd_pll1_link)) { + ret = PTR_ERR(dpu->pd_pll1_link); + dev_err(dev, "Failed to add device link to pll1 pd dev: %d\n", + ret); + goto fail; + } + + return ret; +fail: + dpu_detach_pm_domains(dpu); + return ret; +} + +#define DPU_UNITS_ADDR_DBG(unit) \ +{ \ + const struct dpu_unit *us = data->unit##s; \ + int i; \ + for (i = 0; i < us->num; i++) { \ + if (us->pec_ofss) { \ + dev_dbg(&pdev->dev, "%s%d: pixengcfg @ 0x%08lx,"\ + " unit @ 0x%08lx\n", us->name, \ + us->ids[i], \ + dpu_base + us->pec_ofss[i], \ + dpu_base + us->ofss[i]); \ + } else { \ + dev_dbg(&pdev->dev, \ + "%s%d: unit @ 0x%08lx\n", us->name, \ + us->ids[i], dpu_base + us->ofss[i]); \ + } \ + } \ +} + +static void dpu_units_addr_dbg(struct dpu_soc *dpu, + struct platform_device *pdev, unsigned long dpu_base) +{ + const struct dpu_data *data = dpu->data; + + dev_dbg(dpu->dev, "Common: 0x%08lx\n", dpu_base + data->cm_ofs); + DPU_UNITS_ADDR_DBG(cf); + DPU_UNITS_ADDR_DBG(dec); + DPU_UNITS_ADDR_DBG(ed); + DPU_UNITS_ADDR_DBG(fd); + DPU_UNITS_ADDR_DBG(fe); + DPU_UNITS_ADDR_DBG(fg); + DPU_UNITS_ADDR_DBG(fl); + DPU_UNITS_ADDR_DBG(fw); + DPU_UNITS_ADDR_DBG(hs); + DPU_UNITS_ADDR_DBG(lb); + DPU_UNITS_ADDR_DBG(sig); + DPU_UNITS_ADDR_DBG(st); + DPU_UNITS_ADDR_DBG(tcon); + DPU_UNITS_ADDR_DBG(vs); +} + +static int dpu_get_irq(struct platform_device *pdev, struct dpu_soc *dpu) +{ +#define DPU_GET_IRQ(name) \ +{ \ + dpu->irq_##name = platform_get_irq_byname(pdev, "" #name ""); \ + dev_dbg(dpu->dev, "irq_" #name ": %d\n", dpu->irq_##name); \ + if (dpu->irq_##name < 0) { \ + dev_err(dpu->dev, "failed to get irq " #name "\n"); \ + return dpu->irq_##name; \ + } \ +} + + DPU_GET_IRQ(extdst0_shdload); + DPU_GET_IRQ(extdst4_shdload); + DPU_GET_IRQ(extdst1_shdload); + DPU_GET_IRQ(extdst5_shdload); + DPU_GET_IRQ(disengcfg_shdload0); + DPU_GET_IRQ(disengcfg_framecomplete0); + DPU_GET_IRQ(sig0_shdload); + DPU_GET_IRQ(sig0_valid); + DPU_GET_IRQ(disengcfg_shdload1); + DPU_GET_IRQ(disengcfg_framecomplete1); + DPU_GET_IRQ(sig1_shdload); + DPU_GET_IRQ(sig1_valid); + DPU_GET_IRQ(comctrl_sw0); + DPU_GET_IRQ(comctrl_sw1); + DPU_GET_IRQ(comctrl_sw2); + DPU_GET_IRQ(comctrl_sw3); + + return 0; +} + +static void dpu_irq_handle(struct irq_desc *desc, enum dpu_irq irq) +{ + struct dpu_soc *dpu = irq_desc_get_handler_data(desc); + const struct dpu_data *data = dpu->data; + const struct cm_reg_ofs *ofs = data->cm_reg_ofs; + struct irq_chip *chip = irq_desc_get_chip(desc); + unsigned int virq; + u32 status; + + chained_irq_enter(chip, desc); + + status = dpu_cm_read(dpu, USERINTERRUPTSTATUS(ofs, irq / 32)); + status &= dpu_cm_read(dpu, USERINTERRUPTENABLE(ofs, irq / 32)); + + if (status & BIT(irq % 32)) { + virq = irq_linear_revmap(dpu->domain, irq); + if (virq) + generic_handle_irq(virq); + } + + chained_irq_exit(chip, desc); +} + +#define DPU_IRQ_HANDLER_DEFINE(name1, name2) \ +static void dpu_##name1##_irq_handler(struct irq_desc *desc) \ +{ \ + dpu_irq_handle(desc, IRQ_##name2); \ +} + +DPU_IRQ_HANDLER_DEFINE(extdst0_shdload, EXTDST0_SHDLOAD) +DPU_IRQ_HANDLER_DEFINE(extdst4_shdload, EXTDST4_SHDLOAD) +DPU_IRQ_HANDLER_DEFINE(extdst1_shdload, EXTDST1_SHDLOAD) +DPU_IRQ_HANDLER_DEFINE(extdst5_shdload, EXTDST5_SHDLOAD) +DPU_IRQ_HANDLER_DEFINE(disengcfg_shdload0, DISENGCFG_SHDLOAD0) +DPU_IRQ_HANDLER_DEFINE(disengcfg_framecomplete0, DISENGCFG_FRAMECOMPLETE0) +DPU_IRQ_HANDLER_DEFINE(sig0_shdload, SIG0_SHDLOAD); +DPU_IRQ_HANDLER_DEFINE(sig0_valid, SIG0_VALID); +DPU_IRQ_HANDLER_DEFINE(disengcfg_shdload1, DISENGCFG_SHDLOAD1) +DPU_IRQ_HANDLER_DEFINE(disengcfg_framecomplete1, DISENGCFG_FRAMECOMPLETE1) +DPU_IRQ_HANDLER_DEFINE(sig1_shdload, SIG1_SHDLOAD); +DPU_IRQ_HANDLER_DEFINE(sig1_valid, SIG1_VALID); +DPU_IRQ_HANDLER_DEFINE(comctrl_sw0, COMCTRL_SW0); +DPU_IRQ_HANDLER_DEFINE(comctrl_sw1, COMCTRL_SW1); +DPU_IRQ_HANDLER_DEFINE(comctrl_sw2, COMCTRL_SW2); +DPU_IRQ_HANDLER_DEFINE(comctrl_sw3, COMCTRL_SW3); + +int dpu_map_irq(struct dpu_soc *dpu, int irq) +{ + int virq = irq_linear_revmap(dpu->domain, irq); + + if (!virq) + virq = irq_create_mapping(dpu->domain, irq); + + return virq; +} +EXPORT_SYMBOL_GPL(dpu_map_irq); + +static int dpu_irq_init(struct dpu_soc *dpu) +{ + const struct dpu_data *data = dpu->data; + const struct cm_reg_ofs *ofs = data->cm_reg_ofs; + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + int ret, i; + + dpu->domain = irq_domain_add_linear(dpu->dev->of_node, + dpu->irq_line_num, + &irq_generic_chip_ops, dpu); + if (!dpu->domain) { + dev_err(dpu->dev, "failed to add irq domain\n"); + return -ENODEV; + } + + ret = irq_alloc_domain_generic_chips(dpu->domain, 32, 1, "DPU", + handle_level_irq, 0, 0, 0); + if (ret < 0) { + dev_err(dpu->dev, "failed to alloc generic irq chips\n"); + irq_domain_remove(dpu->domain); + return ret; + } + + for (i = 0; i < dpu->irq_line_num; i += 32) { + /* Mask and clear all interrupts */ + dpu_cm_write(dpu, USERINTERRUPTENABLE(ofs, i / 32), 0); + dpu_cm_write(dpu, USERINTERRUPTCLEAR(ofs, i / 32), + ~data->unused_irq[i / 32]); + dpu_cm_write(dpu, INTERRUPTENABLE(ofs, i / 32), 0); + dpu_cm_write(dpu, INTERRUPTCLEAR(ofs, i / 32), + ~data->unused_irq[i / 32]); + + /* Set all interrupts to user mode */ + dpu_cm_write(dpu, USERINTERRUPTMASK(ofs, i / 32), + ~data->unused_irq[i / 32]); + + gc = irq_get_domain_generic_chip(dpu->domain, i); + gc->reg_base = dpu->cm_reg; + gc->unused = data->unused_irq[i / 32]; + ct = gc->chip_types; + ct->chip.irq_ack = irq_gc_ack_set_bit; + ct->chip.irq_mask = irq_gc_mask_clr_bit; + ct->chip.irq_unmask = irq_gc_mask_set_bit; + ct->regs.ack = USERINTERRUPTCLEAR(ofs, i / 32); + ct->regs.mask = USERINTERRUPTENABLE(ofs, i / 32); + } + +#define DPU_IRQ_CHIP_PM_GET(name) \ +{ \ + ret = irq_chip_pm_get(irq_get_irq_data(dpu->irq_##name)); \ + if (ret < 0) { \ + dev_err(dpu->dev, \ + "failed to get irq chip PM for irq%d %d\n", \ + dpu->irq_##name, ret); \ + goto pm_get_rollback; \ + } \ + dpu->irq_chip_pm_get_##name = true; \ +} + + DPU_IRQ_CHIP_PM_GET(extdst0_shdload); + DPU_IRQ_CHIP_PM_GET(extdst4_shdload); + DPU_IRQ_CHIP_PM_GET(extdst1_shdload); + DPU_IRQ_CHIP_PM_GET(extdst5_shdload); + DPU_IRQ_CHIP_PM_GET(disengcfg_shdload0); + DPU_IRQ_CHIP_PM_GET(disengcfg_framecomplete0); + DPU_IRQ_CHIP_PM_GET(sig0_shdload); + DPU_IRQ_CHIP_PM_GET(sig0_valid); + DPU_IRQ_CHIP_PM_GET(disengcfg_shdload1); + DPU_IRQ_CHIP_PM_GET(disengcfg_framecomplete1); + DPU_IRQ_CHIP_PM_GET(sig1_shdload); + DPU_IRQ_CHIP_PM_GET(sig1_valid); + DPU_IRQ_CHIP_PM_GET(comctrl_sw0); + DPU_IRQ_CHIP_PM_GET(comctrl_sw1); + DPU_IRQ_CHIP_PM_GET(comctrl_sw2); + DPU_IRQ_CHIP_PM_GET(comctrl_sw3); + +#define DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(name) \ +irq_set_chained_handler_and_data(dpu->irq_##name, dpu_##name##_irq_handler, dpu) + + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(extdst0_shdload); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(extdst4_shdload); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(extdst1_shdload); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(extdst5_shdload); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(disengcfg_shdload0); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(disengcfg_framecomplete0); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(sig0_shdload); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(sig0_valid); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(disengcfg_shdload1); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(disengcfg_framecomplete1); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(sig1_shdload); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(sig1_valid); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(comctrl_sw0); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(comctrl_sw1); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(comctrl_sw2); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(comctrl_sw3); + +#define DPU_IRQ_CHIP_PM_PUT_CHECK(name) \ +{ \ + if (dpu->irq_chip_pm_get_##name) { \ + irq_chip_pm_put(irq_get_irq_data(dpu->irq_##name)); \ + dpu->irq_chip_pm_get_##name = false; \ + } \ +} + + return 0; + +pm_get_rollback: + DPU_IRQ_CHIP_PM_PUT_CHECK(extdst0_shdload); + DPU_IRQ_CHIP_PM_PUT_CHECK(extdst4_shdload); + DPU_IRQ_CHIP_PM_PUT_CHECK(extdst1_shdload); + DPU_IRQ_CHIP_PM_PUT_CHECK(extdst5_shdload); + DPU_IRQ_CHIP_PM_PUT_CHECK(disengcfg_shdload0); + DPU_IRQ_CHIP_PM_PUT_CHECK(disengcfg_framecomplete0); + DPU_IRQ_CHIP_PM_PUT_CHECK(sig0_shdload); + DPU_IRQ_CHIP_PM_PUT_CHECK(sig0_valid); + DPU_IRQ_CHIP_PM_PUT_CHECK(disengcfg_shdload1); + DPU_IRQ_CHIP_PM_PUT_CHECK(disengcfg_framecomplete1); + DPU_IRQ_CHIP_PM_PUT_CHECK(sig1_shdload); + DPU_IRQ_CHIP_PM_PUT_CHECK(sig1_valid); + DPU_IRQ_CHIP_PM_PUT_CHECK(comctrl_sw0); + DPU_IRQ_CHIP_PM_PUT_CHECK(comctrl_sw1); + DPU_IRQ_CHIP_PM_PUT_CHECK(comctrl_sw2); + DPU_IRQ_CHIP_PM_PUT_CHECK(comctrl_sw3); + + return ret; +} + +static void dpu_irq_exit(struct dpu_soc *dpu) +{ + unsigned int i, irq; + +#define DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(name) \ +irq_set_chained_handler_and_data(dpu->irq_##name, NULL, NULL) + + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(extdst0_shdload); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(extdst4_shdload); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(extdst1_shdload); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(extdst5_shdload); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(disengcfg_shdload0); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(disengcfg_framecomplete0); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(sig0_shdload); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(sig0_valid); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(disengcfg_shdload1); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(disengcfg_framecomplete1); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(sig1_shdload); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(sig1_valid); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(comctrl_sw0); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(comctrl_sw1); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(comctrl_sw2); + DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(comctrl_sw3); + +#define DPU_IRQ_CHIP_PM_PUT(name) \ +{ \ + irq_chip_pm_put(irq_get_irq_data(dpu->irq_##name)); \ + dpu->irq_chip_pm_get_##name = false; \ +} + + DPU_IRQ_CHIP_PM_PUT(extdst0_shdload); + DPU_IRQ_CHIP_PM_PUT(extdst4_shdload); + DPU_IRQ_CHIP_PM_PUT(extdst1_shdload); + DPU_IRQ_CHIP_PM_PUT(extdst5_shdload); + DPU_IRQ_CHIP_PM_PUT(disengcfg_shdload0); + DPU_IRQ_CHIP_PM_PUT(disengcfg_framecomplete0); + DPU_IRQ_CHIP_PM_PUT(sig0_shdload); + DPU_IRQ_CHIP_PM_PUT(sig0_valid); + DPU_IRQ_CHIP_PM_PUT(disengcfg_shdload1); + DPU_IRQ_CHIP_PM_PUT(disengcfg_framecomplete1); + DPU_IRQ_CHIP_PM_PUT(sig1_shdload); + DPU_IRQ_CHIP_PM_PUT(sig1_valid); + DPU_IRQ_CHIP_PM_PUT(comctrl_sw0); + DPU_IRQ_CHIP_PM_PUT(comctrl_sw1); + DPU_IRQ_CHIP_PM_PUT(comctrl_sw2); + DPU_IRQ_CHIP_PM_PUT(comctrl_sw3); + + for (i = 0; i < dpu->irq_line_num; i++) { + irq = irq_linear_revmap(dpu->domain, i); + if (irq) + irq_dispose_mapping(irq); + } + + irq_domain_remove(dpu->domain); +} + +#define _DPU_UNITS_INIT(unit) \ +{ \ + const struct dpu_unit *us = data->unit##s; \ + int i; \ + \ + /* software check */ \ + if (WARN_ON(us->num > ARRAY_SIZE(unit##_ids))) \ + return -EINVAL; \ + \ + for (i = 0; i < us->num; i++) \ + _dpu_##unit##_init(dpu, us->ids[i]); \ +} + +static int +_dpu_submodules_init(struct dpu_soc *dpu, struct platform_device *pdev) +{ + const struct dpu_data *data = dpu->data; + + _DPU_UNITS_INIT(cf); + _DPU_UNITS_INIT(dec); + _DPU_UNITS_INIT(ed); + _DPU_UNITS_INIT(fd); + _DPU_UNITS_INIT(fe); + _DPU_UNITS_INIT(fg); + _DPU_UNITS_INIT(fl); + _DPU_UNITS_INIT(fw); + _DPU_UNITS_INIT(hs); + _DPU_UNITS_INIT(lb); + _DPU_UNITS_INIT(sig); + _DPU_UNITS_INIT(st); + _DPU_UNITS_INIT(tcon); + _DPU_UNITS_INIT(vs); + + return 0; +} + +#define DPU_UNIT_INIT(dpu, base, unit, name, id, pec_ofs, ofs) \ +{ \ + int ret; \ + ret = dpu_##unit##_init((dpu), (id), \ + (pec_ofs) ? (base) + (pec_ofs) : 0, \ + (base) + (ofs)); \ + if (ret) { \ + dev_err((dpu)->dev, "init %s%d failed with %d\n", \ + (name), (id), ret); \ + return ret; \ + } \ +} + +#define DPU_UNITS_INIT(unit) \ +{ \ + const struct dpu_unit *us = data->unit##s; \ + int i; \ + \ + /* software check */ \ + if (WARN_ON(us->num > ARRAY_SIZE(unit##_ids))) \ + return -EINVAL; \ + \ + for (i = 0; i < us->num; i++) \ + DPU_UNIT_INIT(dpu, dpu_base, unit, us->name, \ + us->ids[i], \ + us->pec_ofss ? us->pec_ofss[i] : 0, \ + us->ofss[i]); \ +} + +static int dpu_submodules_init(struct dpu_soc *dpu, + struct platform_device *pdev, unsigned long dpu_base) +{ + const struct dpu_data *data = dpu->data; + const struct dpu_unit *fds = data->fds; + const struct dpu_unit *fls = data->fls; + const struct dpu_unit *fws = data->fws; + const struct dpu_unit *tcons = data->tcons; + struct dpu_fetchunit *fu; + struct dprc *dprc; + struct dpu_tcon *tcon; + struct pc *pc; + int i; + + DPU_UNITS_INIT(cf); + DPU_UNITS_INIT(dec); + DPU_UNITS_INIT(ed); + DPU_UNITS_INIT(fd); + DPU_UNITS_INIT(fe); + DPU_UNITS_INIT(fg); + DPU_UNITS_INIT(fl); + DPU_UNITS_INIT(fw); + DPU_UNITS_INIT(hs); + DPU_UNITS_INIT(lb); + DPU_UNITS_INIT(sig); + DPU_UNITS_INIT(st); + DPU_UNITS_INIT(tcon); + DPU_UNITS_INIT(vs); + + for (i = 0; i < fds->num; i++) { + dprc = dprc_lookup_by_phandle(dpu->dev, "fsl,dpr-channels", + fds->dprc_ids[i]); + if (!dprc) + return -EPROBE_DEFER; + + fu = dpu_fd_get(dpu, i); + fetchunit_get_dprc(fu, dprc); + dpu_fd_put(fu); + } + + for (i = 0; i < fls->num; i++) { + dprc = dprc_lookup_by_phandle(dpu->dev, "fsl,dpr-channels", + fls->dprc_ids[i]); + if (!dprc) + return -EPROBE_DEFER; + + fu = dpu_fl_get(dpu, i); + fetchunit_get_dprc(fu, dprc); + dpu_fl_put(fu); + } + + for (i = 0; i < fws->num; i++) { + dprc = dprc_lookup_by_phandle(dpu->dev, "fsl,dpr-channels", + fws->dprc_ids[i]); + if (!dprc) + return -EPROBE_DEFER; + + fu = dpu_fw_get(dpu, fw_ids[i]); + fetchunit_get_dprc(fu, dprc); + dpu_fw_put(fu); + } + + pc = pc_lookup_by_phandle(dpu->dev, "fsl,pixel-combiner"); + if (!pc) + return -EPROBE_DEFER; + + for (i = 0; i < tcons->num; i++) { + tcon = dpu_tcon_get(dpu, i); + tcon_get_pc(tcon, pc); + dpu_tcon_put(tcon); + } + + return 0; +} + +static int platform_remove_devices_fn(struct device *dev, void *unused) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + + return 0; +} + +static void platform_device_unregister_children(struct platform_device *pdev) +{ + device_for_each_child(&pdev->dev, NULL, platform_remove_devices_fn); +} + +struct dpu_platform_reg { + struct dpu_client_platformdata pdata; + const char *name; +}; + +static struct dpu_platform_reg client_reg[] = { + { + .pdata = { + .stream_id = 0, + }, + .name = "imx-dpu-crtc", + }, { + .pdata = { + .stream_id = 1, + }, + .name = "imx-dpu-crtc", + }, { + .pdata = { }, + .name = IMX_DPU_BLITENG_NAME, + } +}; + +static DEFINE_MUTEX(dpu_client_id_mutex); +static int dpu_client_id; + +static int dpu_get_plane_resource(struct dpu_soc *dpu, + struct dpu_plane_res *res) +{ + const struct dpu_unit *fds = dpu->data->fds; + const struct dpu_unit *fls = dpu->data->fls; + const struct dpu_unit *fws = dpu->data->fws; + const struct dpu_unit *lbs = dpu->data->lbs; + struct dpu_plane_grp *grp = plane_res_to_grp(res); + int i; + + for (i = 0; i < ARRAY_SIZE(res->ed); i++) { + res->ed[i] = dpu_ed_get(dpu, i); + if (IS_ERR(res->ed[i])) + return PTR_ERR(res->ed[i]); + } + for (i = 0; i < fds->num; i++) { + res->fd[i] = dpu_fd_get(dpu, i); + if (IS_ERR(res->fd[i])) + return PTR_ERR(res->fd[i]); + } + for (i = 0; i < ARRAY_SIZE(res->fe); i++) { + res->fe[i] = dpu_fe_get(dpu, i); + if (IS_ERR(res->fe[i])) + return PTR_ERR(res->fe[i]); + grp->hw_plane_fetcheco_num = ARRAY_SIZE(res->fe); + } + for (i = 0; i < fls->num; i++) { + res->fl[i] = dpu_fl_get(dpu, i); + if (IS_ERR(res->fl[i])) + return PTR_ERR(res->fl[i]); + } + for (i = 0; i < fws->num; i++) { + res->fw[i] = dpu_fw_get(dpu, fw_ids[i]); + if (IS_ERR(res->fw[i])) + return PTR_ERR(res->fw[i]); + } + /* HScaler could be shared with capture. */ + if (display_plane_video_proc) { + for (i = 0; i < ARRAY_SIZE(res->hs); i++) { + res->hs[i] = dpu_hs_get(dpu, hs_ids[i]); + if (IS_ERR(res->hs[i])) + return PTR_ERR(res->hs[i]); + } + grp->hw_plane_hscaler_num = ARRAY_SIZE(res->hs); + } + for (i = 0; i < lbs->num; i++) { + res->lb[i] = dpu_lb_get(dpu, i); + if (IS_ERR(res->lb[i])) + return PTR_ERR(res->lb[i]); + } + /* VScaler could be shared with capture. */ + if (display_plane_video_proc) { + for (i = 0; i < ARRAY_SIZE(res->vs); i++) { + res->vs[i] = dpu_vs_get(dpu, vs_ids[i]); + if (IS_ERR(res->vs[i])) + return PTR_ERR(res->vs[i]); + } + grp->hw_plane_vscaler_num = ARRAY_SIZE(res->vs); + } + + grp->hw_plane_num = fds->num + fls->num + fws->num; + + return 0; +} + +static void dpu_put_plane_resource(struct dpu_plane_res *res) +{ + struct dpu_plane_grp *grp = plane_res_to_grp(res); + int i; + + for (i = 0; i < ARRAY_SIZE(res->ed); i++) { + if (!IS_ERR_OR_NULL(res->ed[i])) + dpu_ed_put(res->ed[i]); + } + for (i = 0; i < ARRAY_SIZE(res->fd); i++) { + if (!IS_ERR_OR_NULL(res->fd[i])) + dpu_fd_put(res->fd[i]); + } + for (i = 0; i < ARRAY_SIZE(res->fe); i++) { + if (!IS_ERR_OR_NULL(res->fe[i])) + dpu_fe_put(res->fe[i]); + } + for (i = 0; i < ARRAY_SIZE(res->fl); i++) { + if (!IS_ERR_OR_NULL(res->fl[i])) + dpu_fl_put(res->fl[i]); + } + for (i = 0; i < ARRAY_SIZE(res->fw); i++) { + if (!IS_ERR_OR_NULL(res->fw[i])) + dpu_fw_put(res->fw[i]); + } + for (i = 0; i < ARRAY_SIZE(res->hs); i++) { + if (!IS_ERR_OR_NULL(res->hs[i])) + dpu_hs_put(res->hs[i]); + } + for (i = 0; i < ARRAY_SIZE(res->lb); i++) { + if (!IS_ERR_OR_NULL(res->lb[i])) + dpu_lb_put(res->lb[i]); + } + for (i = 0; i < ARRAY_SIZE(res->vs); i++) { + if (!IS_ERR_OR_NULL(res->vs[i])) + dpu_vs_put(res->vs[i]); + } + + grp->hw_plane_num = 0; +} + +static int dpu_add_client_devices(struct dpu_soc *dpu) +{ + const struct dpu_data *data = dpu->data; + struct device *dev = dpu->dev; + struct dpu_platform_reg *reg; + struct dpu_plane_grp *plane_grp; + struct dpu_store *st9 = NULL; + size_t client_num, reg_size; + int i, id, ret; + + client_num = ARRAY_SIZE(client_reg); + + reg = devm_kcalloc(dev, client_num, sizeof(*reg), GFP_KERNEL); + if (!reg) + return -ENODEV; + + plane_grp = devm_kzalloc(dev, sizeof(*plane_grp), GFP_KERNEL); + if (!plane_grp) + return -ENODEV; + + mutex_init(&plane_grp->mutex); + + mutex_lock(&dpu_client_id_mutex); + id = dpu_client_id; + dpu_client_id += client_num; + mutex_unlock(&dpu_client_id_mutex); + + reg_size = client_num * sizeof(struct dpu_platform_reg); + memcpy(reg, &client_reg[0], reg_size); + + plane_grp->src_mask = data->plane_src_mask; + plane_grp->id = id / client_num; + plane_grp->has_vproc = display_plane_video_proc; + + ret = dpu_get_plane_resource(dpu, &plane_grp->res); + if (ret) + goto err_get_plane_res; + + st9 = dpu_st_get(dpu, 9); + if (IS_ERR(st9)) { + ret = PTR_ERR(st9); + goto err_get_plane_res; + } + + for (i = 0; i < client_num; i++) { + struct platform_device *pdev; + struct device_node *of_node = NULL; + + if (!strcmp(reg[i].name, IMX_DPU_BLITENG_NAME)) { + /* As bliteng has no of_node, so to use dpu's. */ + of_node = dev->of_node; + } else { + /* Associate subdevice with the corresponding port node. */ + of_node = of_graph_get_port_by_id(dev->of_node, i); + if (!of_node) { + dev_info(dev, + "no port@%d node in %s, not using DISP%d\n", + i, dev->of_node->full_name, i); + continue; + } + } + + reg[i].pdata.plane_grp = plane_grp; + reg[i].pdata.di_grp_id = plane_grp->id; + reg[i].pdata.st9 = st9; + + pdev = platform_device_alloc(reg[i].name, id++); + if (!pdev) { + ret = -ENOMEM; + goto err_register; + } + + pdev->dev.parent = dev; + + reg[i].pdata.of_node = of_node; + ret = platform_device_add_data(pdev, ®[i].pdata, + sizeof(reg[i].pdata)); + if (!ret) + ret = platform_device_add(pdev); + if (ret) { + platform_device_put(pdev); + goto err_register; + } + } + + return 0; + +err_register: + platform_device_unregister_children(to_platform_device(dev)); + dpu_st_put(st9); +err_get_plane_res: + dpu_put_plane_resource(&plane_grp->res); + + return ret; +} + +static int dpu_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id; + struct device_node *np = pdev->dev.of_node; + struct dpu_soc *dpu; + struct resource *res; + unsigned long dpu_base; + const struct dpu_data *data; + int ret; + + of_id = of_match_device(dpu_dt_ids, &pdev->dev); + if (!of_id) + return -ENODEV; + + data = of_id->data; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + dpu_base = res->start; + + dpu = devm_kzalloc(&pdev->dev, sizeof(*dpu), GFP_KERNEL); + if (!dpu) + return -ENODEV; + + dpu->dev = &pdev->dev; + dpu->data = data; + dpu->id = of_alias_get_id(np, "dpu"); + dpu->irq_line_num = platform_irq_count(pdev); + if (dpu->irq_line_num < 0) + return dpu->irq_line_num; + + dpu_units_addr_dbg(dpu, pdev, dpu_base); + + ret = dpu_get_irq(pdev, dpu); + if (ret < 0) + return ret; + + ret = dpu_sc_misc_get_handle(dpu); + if (ret < 0) + return ret; + + spin_lock_init(&dpu->lock); + + dpu->cm_reg = devm_ioremap(dpu->dev, dpu_base + data->cm_ofs, SZ_1K); + if (!dpu->cm_reg) + return -ENOMEM; + + ret = dpu_attach_pm_domains(dpu); + if (ret) + return ret; + + ret = dpu_irq_init(dpu); + if (ret) + goto failed_irq; + + ret = dpu_submodules_init(dpu, pdev, dpu_base); + if (ret) + goto failed_submodules_init; + + ret = dpu_sc_misc_init(dpu); + if (ret < 0) { + dev_err(dpu->dev, + "failed to initialize pixel link %d\n", ret); + goto failed_sc_misc_init; + } + + platform_set_drvdata(pdev, dpu); + + ret = dpu_add_client_devices(dpu); + if (ret) { + dev_err(dpu->dev, + "adding client devices failed with %d\n", ret); + goto failed_add_clients; + } + + dev_info(dpu->dev, "driver probed\n"); + + return 0; + +failed_add_clients: + platform_set_drvdata(pdev, NULL); +failed_sc_misc_init: +failed_submodules_init: + dpu_irq_exit(dpu); +failed_irq: + dpu_detach_pm_domains(dpu); + return ret; +} + +static int dpu_remove(struct platform_device *pdev) +{ + struct dpu_soc *dpu = platform_get_drvdata(pdev); + + platform_device_unregister_children(pdev); + + dpu_irq_exit(dpu); + dpu_detach_pm_domains(dpu); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int dpu_suspend(struct device *dev) +{ + /* + * The dpu core driver currently depends on the client drivers + * to do suspend operations to leave dpu a cleaned up state + * machine status before the system enters sleep mode. + */ + return 0; +} + +static int dpu_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dpu_soc *dpu = platform_get_drvdata(pdev); + + dpu_sc_misc_init(dpu); + + _dpu_submodules_init(dpu, pdev); + + return 0; +} +#endif + +static const struct dev_pm_ops dpu_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(dpu_suspend, dpu_resume) +}; + +static struct platform_driver dpu_driver = { + .driver = { + .pm = &dpu_pm_ops, + .name = "dpu-core", + .of_match_table = dpu_dt_ids, + }, + .probe = dpu_probe, + .remove = dpu_remove, +}; + +module_platform_driver(dpu_driver); + +MODULE_DESCRIPTION("i.MX DPU driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/imx/dpu/dpu-constframe.c b/drivers/gpu/imx/dpu/dpu-constframe.c new file mode 100644 index 000000000000..26c7f85fa67a --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-constframe.c @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2019 NXP + * + * 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 <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <video/dpu.h> +#include "dpu-prv.h" + +static unsigned int safety_stream_cf_color = 0x0; +module_param(safety_stream_cf_color, uint, 0444); +MODULE_PARM_DESC(safety_stream_cf_color, +"Safety stream constframe color in hex(0xRRGGBBAA) [default=0x00000000]"); + +#define FRAMEDIMENSIONS 0xC +#define WIDTH(w) (((w) - 1) & 0x3FFF) +#define HEIGHT(h) ((((h) - 1) & 0x3FFF) << 16) +#define CONSTANTCOLOR 0x10 +#define RED(r) (((r) & 0xFF) << 24) +#define GREEN(g) (((g) & 0xFF) << 16) +#define BLUE(b) (((b) & 0xFF) << 8) +#define ALPHA(a) ((a) & 0xFF) +#define CONTROLTRIGGER 0x14 +#define START 0x18 +#define STATUS 0x1C + +struct dpu_constframe { + void __iomem *pec_base; + void __iomem *base; + struct mutex mutex; + int id; + bool inuse; + struct dpu_soc *dpu; +}; + +static inline u32 dpu_cf_read(struct dpu_constframe *cf, unsigned int offset) +{ + return readl(cf->base + offset); +} + +static inline void dpu_cf_write(struct dpu_constframe *cf, + unsigned int offset, u32 value) +{ + writel(value, cf->base + offset); +} + +void constframe_shden(struct dpu_constframe *cf, bool enable) +{ + u32 val; + + val = enable ? SHDEN : 0; + + mutex_lock(&cf->mutex); + dpu_cf_write(cf, STATICCONTROL, val); + mutex_unlock(&cf->mutex); +} +EXPORT_SYMBOL_GPL(constframe_shden); + +void constframe_framedimensions(struct dpu_constframe *cf, unsigned int w, + unsigned int h) +{ + u32 val; + + val = WIDTH(w) | HEIGHT(h); + + mutex_lock(&cf->mutex); + dpu_cf_write(cf, FRAMEDIMENSIONS, val); + mutex_unlock(&cf->mutex); +} +EXPORT_SYMBOL_GPL(constframe_framedimensions); + +void constframe_framedimensions_copy_prim(struct dpu_constframe *cf) +{ + struct dpu_constframe *prim_cf = NULL; + unsigned int prim_id; + int i; + u32 val; + + if (cf->id != 0 && cf->id != 1) { + dev_warn(cf->dpu->dev, "ConstFrame%d is not a secondary one\n", + cf->id); + return; + } + + prim_id = cf->id + 4; + + for (i = 0; i < ARRAY_SIZE(cf_ids); i++) + if (cf_ids[i] == prim_id) + prim_cf = cf->dpu->cf_priv[i]; + + if (!prim_cf) { + dev_warn(cf->dpu->dev, "cannot find ConstFrame%d's primary peer\n", + cf->id); + return; + } + + mutex_lock(&cf->mutex); + val = dpu_cf_read(prim_cf, FRAMEDIMENSIONS); + dpu_cf_write(cf, FRAMEDIMENSIONS, val); + mutex_unlock(&cf->mutex); +} +EXPORT_SYMBOL_GPL(constframe_framedimensions_copy_prim); + +void constframe_constantcolor(struct dpu_constframe *cf, unsigned int r, + unsigned int g, unsigned int b, unsigned int a) +{ + u32 val; + + val = RED(r) | GREEN(g) | BLUE(b) | ALPHA(a); + + mutex_lock(&cf->mutex); + dpu_cf_write(cf, CONSTANTCOLOR, val); + mutex_unlock(&cf->mutex); +} +EXPORT_SYMBOL_GPL(constframe_constantcolor); + +void constframe_controltrigger(struct dpu_constframe *cf, bool trigger) +{ + u32 val; + + val = trigger ? SHDTOKGEN : 0; + + mutex_lock(&cf->mutex); + dpu_cf_write(cf, CONTROLTRIGGER, val); + mutex_unlock(&cf->mutex); +} +EXPORT_SYMBOL_GPL(constframe_controltrigger); + +struct dpu_constframe *dpu_cf_get(struct dpu_soc *dpu, int id) +{ + struct dpu_constframe *cf; + int i; + + for (i = 0; i < ARRAY_SIZE(cf_ids); i++) + if (cf_ids[i] == id) + break; + + if (i == ARRAY_SIZE(cf_ids)) + return ERR_PTR(-EINVAL); + + cf = dpu->cf_priv[i]; + + mutex_lock(&cf->mutex); + + if (cf->inuse) { + mutex_unlock(&cf->mutex); + return ERR_PTR(-EBUSY); + } + + cf->inuse = true; + + mutex_unlock(&cf->mutex); + + return cf; +} +EXPORT_SYMBOL_GPL(dpu_cf_get); + +void dpu_cf_put(struct dpu_constframe *cf) +{ + mutex_lock(&cf->mutex); + + cf->inuse = false; + + mutex_unlock(&cf->mutex); +} +EXPORT_SYMBOL_GPL(dpu_cf_put); + +struct dpu_constframe *dpu_aux_cf_peek(struct dpu_constframe *cf) +{ + unsigned int aux_id = cf->id ^ 1; + int i; + + for (i = 0; i < ARRAY_SIZE(cf_ids); i++) + if (cf_ids[i] == aux_id) + return cf->dpu->cf_priv[i]; + + return NULL; +} +EXPORT_SYMBOL_GPL(dpu_aux_cf_peek); + +void _dpu_cf_init(struct dpu_soc *dpu, unsigned int id) +{ + struct dpu_constframe *cf; + int i; + + for (i = 0; i < ARRAY_SIZE(cf_ids); i++) + if (cf_ids[i] == id) + break; + + if (WARN_ON(i == ARRAY_SIZE(cf_ids))) + return; + + cf = dpu->cf_priv[i]; + + constframe_shden(cf, true); + + if (id == 4 || id == 5) { + mutex_lock(&cf->mutex); + dpu_cf_write(cf, CONSTANTCOLOR, safety_stream_cf_color); + mutex_unlock(&cf->mutex); + } +} + +int dpu_cf_init(struct dpu_soc *dpu, unsigned int id, + unsigned long pec_base, unsigned long base) +{ + struct dpu_constframe *cf; + int i; + + cf = devm_kzalloc(dpu->dev, sizeof(*cf), GFP_KERNEL); + if (!cf) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(cf_ids); i++) + if (cf_ids[i] == id) + break; + + if (i == ARRAY_SIZE(cf_ids)) + return -EINVAL; + + dpu->cf_priv[i] = cf; + + cf->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_16); + if (!cf->pec_base) + return -ENOMEM; + + cf->base = devm_ioremap(dpu->dev, base, SZ_32); + if (!cf->base) + return -ENOMEM; + + cf->dpu = dpu; + cf->id = id; + + mutex_init(&cf->mutex); + + _dpu_cf_init(dpu, id); + + return 0; +} diff --git a/drivers/gpu/imx/dpu/dpu-disengcfg.c b/drivers/gpu/imx/dpu/dpu-disengcfg.c new file mode 100644 index 000000000000..be7e8011c270 --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-disengcfg.c @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2020 NXP + * + * 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 <drm/drm_mode.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include "dpu-prv.h" + +#define CLOCKCTRL 0x8 +typedef enum { + DSPCLKDIVIDE__DIV1, /* Ext disp clk signal has pix clk freq. */ + DSPCLKDIVIDE__DIV2, /* Ext disp clk signal has 2x the pix clk freq. */ +} clkdivide_t; +#define POLARITYCTRL 0xC +#define POLHS_HIGH BIT(0) +#define POLVS_HIGH BIT(1) +#define POLEN_HIGH BIT(2) +#define PIXINV_INV BIT(3) +#define SRCSELECT 0x10 +#define SIG_SELECT_MASK 0x3 + +struct dpu_disengcfg { + void __iomem *base; + struct mutex mutex; + int id; + bool inuse; + struct dpu_soc *dpu; +}; + +static inline u32 dpu_dec_read(struct dpu_disengcfg *dec, unsigned int offset) +{ + return readl(dec->base + offset); +} + +static inline void dpu_dec_write(struct dpu_disengcfg *dec, + unsigned int offset, u32 value) +{ + writel(value, dec->base + offset); +} + +void disengcfg_sig_select(struct dpu_disengcfg *dec, dec_sig_sel_t sig_sel) +{ + u32 val; + + mutex_lock(&dec->mutex); + val = dpu_dec_read(dec, SRCSELECT); + val &= ~SIG_SELECT_MASK; + val |= sig_sel; + dpu_dec_write(dec, SRCSELECT, val); + mutex_unlock(&dec->mutex); +} +EXPORT_SYMBOL_GPL(disengcfg_sig_select); + +struct dpu_disengcfg *dpu_dec_get(struct dpu_soc *dpu, int id) +{ + struct dpu_disengcfg *dec; + int i; + + for (i = 0; i < ARRAY_SIZE(dec_ids); i++) + if (dec_ids[i] == id) + break; + + if (i == ARRAY_SIZE(dec_ids)) + return ERR_PTR(-EINVAL); + + dec = dpu->dec_priv[i]; + + mutex_lock(&dec->mutex); + + if (dec->inuse) { + mutex_unlock(&dec->mutex); + return ERR_PTR(-EBUSY); + } + + dec->inuse = true; + + mutex_unlock(&dec->mutex); + + return dec; +} +EXPORT_SYMBOL_GPL(dpu_dec_get); + +void dpu_dec_put(struct dpu_disengcfg *dec) +{ + mutex_lock(&dec->mutex); + + dec->inuse = false; + + mutex_unlock(&dec->mutex); +} +EXPORT_SYMBOL_GPL(dpu_dec_put); + +struct dpu_disengcfg *dpu_aux_dec_peek(struct dpu_disengcfg *dec) +{ + return dec->dpu->dec_priv[dec->id ^ 1]; +} +EXPORT_SYMBOL_GPL(dpu_aux_dec_peek); + +void _dpu_dec_init(struct dpu_soc *dpu, unsigned int id) +{ + struct dpu_disengcfg *dec; + u32 val; + int i; + + for (i = 0; i < ARRAY_SIZE(dec_ids); i++) + if (ed_ids[i] == id) + break; + + if (WARN_ON(i == ARRAY_SIZE(dec_ids))) + return; + + dec = dpu->dec_priv[i]; + + val = dpu_dec_read(dec, POLARITYCTRL); + val &= ~POLHS_HIGH; + val &= ~POLVS_HIGH; + dpu_dec_write(dec, POLARITYCTRL, val); + + disengcfg_sig_select(dec, DEC_SIG_SEL_FRAMEGEN); +} + +int dpu_dec_init(struct dpu_soc *dpu, unsigned int id, + unsigned long unused, unsigned long base) +{ + struct dpu_disengcfg *dec; + + dec = devm_kzalloc(dpu->dev, sizeof(*dec), GFP_KERNEL); + if (!dec) + return -ENOMEM; + + dpu->dec_priv[id] = dec; + + dec->base = devm_ioremap(dpu->dev, base, SZ_16); + if (!dec->base) + return -ENOMEM; + + dec->dpu = dpu; + dec->id = id; + mutex_init(&dec->mutex); + + _dpu_dec_init(dpu, id); + + return 0; +} diff --git a/drivers/gpu/imx/dpu/dpu-extdst.c b/drivers/gpu/imx/dpu/dpu-extdst.c new file mode 100644 index 000000000000..013e03a2537e --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-extdst.c @@ -0,0 +1,521 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2019 NXP + * + * 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 <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <video/dpu.h> +#include "dpu-prv.h" + +#define PIXENGCFG_STATIC 0x8 +#define POWERDOWN BIT(4) +#define SYNC_MODE BIT(8) +#define SW_RESET BIT(11) +#define DIV(n) (((n) & 0xFF) << 16) +#define DIV_RESET 0x80 +#define PIXENGCFG_DYNAMIC 0xC +#define PIXENGCFG_REQUEST 0x10 +#define SHDLDREQ(n) BIT(n) +#define SEL_SHDLDREQ BIT(0) +#define PIXENGCFG_TRIGGER 0x14 +#define SYNC_TRIGGER BIT(0) +#define TRIGGER_SEQUENCE_COMPLETE BIT(4) +#define PIXENGCFG_STATUS 0x18 +#define SYNC_BUSY BIT(8) +#define KICK_MODE BIT(8) +#define PERFCOUNTMODE BIT(12) +#define CONTROL 0xC +#define GAMMAAPPLYENABLE BIT(0) +#define SOFTWAREKICK 0x10 +#define KICK BIT(0) +#define STATUS 0x14 +#define CNT_ERR_STS BIT(0) +#define CONTROLWORD 0x18 +#define CURPIXELCNT 0x1C +static u16 get_xval(u32 pixel_cnt) +{ + return pixel_cnt & 0xFFFF; +} + +static u16 get_yval(u32 pixel_cnt) +{ + return pixel_cnt >> 16; +} +#define LASTPIXELCNT 0x20 +#define PERFCOUNTER 0x24 + +struct dpu_extdst { + void __iomem *pec_base; + void __iomem *base; + struct mutex mutex; + int id; + bool inuse; + struct dpu_soc *dpu; +}; + +static inline u32 dpu_pec_ed_read(struct dpu_extdst *ed, unsigned int offset) +{ + return readl(ed->pec_base + offset); +} + +static inline void dpu_pec_ed_write(struct dpu_extdst *ed, + unsigned int offset, u32 value) +{ + writel(value, ed->pec_base + offset); +} + +static inline u32 dpu_ed_read(struct dpu_extdst *ed, unsigned int offset) +{ + return readl(ed->base + offset); +} + +static inline void dpu_ed_write(struct dpu_extdst *ed, + unsigned int offset, u32 value) +{ + writel(value, ed->base + offset); +} + +static inline bool dpu_ed_is_safety_stream(struct dpu_extdst *ed) +{ + if (ed->id == 4 || ed->id == 5) + return true; + + return false; +} + +void extdst_pixengcfg_shden(struct dpu_extdst *ed, bool enable) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_pec_ed_read(ed, PIXENGCFG_STATIC); + if (enable) + val |= SHDEN; + else + val &= ~SHDEN; + dpu_pec_ed_write(ed, PIXENGCFG_STATIC, val); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_pixengcfg_shden); + +void extdst_pixengcfg_powerdown(struct dpu_extdst *ed, bool powerdown) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_pec_ed_read(ed, PIXENGCFG_STATIC); + if (powerdown) + val |= POWERDOWN; + else + val &= ~POWERDOWN; + dpu_pec_ed_write(ed, PIXENGCFG_STATIC, val); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_pixengcfg_powerdown); + +void extdst_pixengcfg_sync_mode(struct dpu_extdst *ed, ed_sync_mode_t mode) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_pec_ed_read(ed, PIXENGCFG_STATIC); + if (mode == AUTO) + val |= SYNC_MODE; + else + val &= ~SYNC_MODE; + dpu_pec_ed_write(ed, PIXENGCFG_STATIC, val); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_pixengcfg_sync_mode); + +void extdst_pixengcfg_reset(struct dpu_extdst *ed, bool reset) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_pec_ed_read(ed, PIXENGCFG_STATIC); + if (reset) + val |= SW_RESET; + else + val &= ~SW_RESET; + dpu_pec_ed_write(ed, PIXENGCFG_STATIC, val); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_pixengcfg_reset); + +void extdst_pixengcfg_div(struct dpu_extdst *ed, u16 div) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_pec_ed_read(ed, PIXENGCFG_STATIC); + val &= ~0xFF0000; + val |= DIV(div); + dpu_pec_ed_write(ed, PIXENGCFG_STATIC, val); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_pixengcfg_div); + +void extdst_pixengcfg_syncmode_master(struct dpu_extdst *ed, bool enable) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_pec_ed_read(ed, PIXENGCFG_STATIC); + if (enable) + val |= BIT(16); + else + val &= ~BIT(16); + dpu_pec_ed_write(ed, PIXENGCFG_STATIC, val); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_pixengcfg_syncmode_master); + +int extdst_pixengcfg_src_sel(struct dpu_extdst *ed, extdst_src_sel_t src) +{ + mutex_lock(&ed->mutex); + dpu_pec_ed_write(ed, PIXENGCFG_DYNAMIC, src); + mutex_unlock(&ed->mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(extdst_pixengcfg_src_sel); + +void extdst_pixengcfg_sel_shdldreq(struct dpu_extdst *ed) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_pec_ed_read(ed, PIXENGCFG_REQUEST); + val |= SEL_SHDLDREQ; + dpu_pec_ed_write(ed, PIXENGCFG_REQUEST, val); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_pixengcfg_sel_shdldreq); + +void extdst_pixengcfg_shdldreq(struct dpu_extdst *ed, u32 req_mask) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_pec_ed_read(ed, PIXENGCFG_REQUEST); + val |= req_mask; + dpu_pec_ed_write(ed, PIXENGCFG_REQUEST, val); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_pixengcfg_shdldreq); + +void extdst_pixengcfg_sync_trigger(struct dpu_extdst *ed) +{ + mutex_lock(&ed->mutex); + dpu_pec_ed_write(ed, PIXENGCFG_TRIGGER, SYNC_TRIGGER); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_pixengcfg_sync_trigger); + +void extdst_pixengcfg_trigger_sequence_complete(struct dpu_extdst *ed) +{ + mutex_lock(&ed->mutex); + dpu_pec_ed_write(ed, PIXENGCFG_TRIGGER, TRIGGER_SEQUENCE_COMPLETE); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_pixengcfg_trigger_sequence_complete); + +bool extdst_pixengcfg_is_sync_busy(struct dpu_extdst *ed) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_pec_ed_read(ed, PIXENGCFG_STATUS); + mutex_unlock(&ed->mutex); + + return val & SYNC_BUSY; +} +EXPORT_SYMBOL_GPL(extdst_pixengcfg_is_sync_busy); + +ed_pipeline_status_t extdst_pixengcfg_pipeline_status(struct dpu_extdst *ed) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_pec_ed_read(ed, PIXENGCFG_STATUS); + mutex_unlock(&ed->mutex); + + return val & 0x3; +} +EXPORT_SYMBOL_GPL(extdst_pixengcfg_pipeline_status); + +void extdst_shden(struct dpu_extdst *ed, bool enable) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_ed_read(ed, STATICCONTROL); + if (enable) + val |= SHDEN; + else + val &= ~SHDEN; + dpu_ed_write(ed, STATICCONTROL, val); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_shden); + +void extdst_kick_mode(struct dpu_extdst *ed, ed_kick_mode_t mode) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_ed_read(ed, STATICCONTROL); + val &= ~KICK_MODE; + val |= mode; + dpu_ed_write(ed, STATICCONTROL, val); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_kick_mode); + +void extdst_perfcountmode(struct dpu_extdst *ed, bool enable) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_ed_read(ed, STATICCONTROL); + if (enable) + val |= PERFCOUNTMODE; + else + val &= ~PERFCOUNTMODE; + dpu_ed_write(ed, STATICCONTROL, val); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_perfcountmode); + +void extdst_gamma_apply_enable(struct dpu_extdst *ed, bool enable) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_ed_read(ed, CONTROL); + if (enable) + val |= GAMMAAPPLYENABLE; + else + val &= ~GAMMAAPPLYENABLE; + dpu_ed_write(ed, CONTROL, val); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_gamma_apply_enable); + +void extdst_kick(struct dpu_extdst *ed) +{ + mutex_lock(&ed->mutex); + dpu_ed_write(ed, SOFTWAREKICK, KICK); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_kick); + +void extdst_cnt_err_clear(struct dpu_extdst *ed) +{ + mutex_lock(&ed->mutex); + dpu_ed_write(ed, STATUS, CNT_ERR_STS); + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(extdst_cnt_err_clear); + +bool extdst_cnt_err_status(struct dpu_extdst *ed) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_ed_read(ed, STATUS); + mutex_unlock(&ed->mutex); + + return val & CNT_ERR_STS; +} +EXPORT_SYMBOL_GPL(extdst_cnt_err_status); + +u32 extdst_last_control_word(struct dpu_extdst *ed) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_ed_read(ed, CONTROLWORD); + mutex_unlock(&ed->mutex); + + return val; +} +EXPORT_SYMBOL_GPL(extdst_last_control_word); + +void extdst_pixel_cnt(struct dpu_extdst *ed, u16 *x, u16 *y) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_ed_read(ed, CURPIXELCNT); + mutex_unlock(&ed->mutex); + + *x = get_xval(val); + *y = get_yval(val); +} +EXPORT_SYMBOL_GPL(extdst_pixel_cnt); + +void extdst_last_pixel_cnt(struct dpu_extdst *ed, u16 *x, u16 *y) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_ed_read(ed, LASTPIXELCNT); + mutex_unlock(&ed->mutex); + + *x = get_xval(val); + *y = get_yval(val); +} +EXPORT_SYMBOL_GPL(extdst_last_pixel_cnt); + +u32 extdst_perfresult(struct dpu_extdst *ed) +{ + u32 val; + + mutex_lock(&ed->mutex); + val = dpu_ed_read(ed, PERFCOUNTER); + mutex_unlock(&ed->mutex); + + return val; +} +EXPORT_SYMBOL_GPL(extdst_perfresult); + +bool extdst_is_master(struct dpu_extdst *ed) +{ + const struct dpu_data *data = ed->dpu->data; + + return ed->id == data->master_stream_id; +} +EXPORT_SYMBOL_GPL(extdst_is_master); + +struct dpu_extdst *dpu_ed_get(struct dpu_soc *dpu, int id) +{ + struct dpu_extdst *ed; + int i; + + for (i = 0; i < ARRAY_SIZE(ed_ids); i++) + if (ed_ids[i] == id) + break; + + if (i == ARRAY_SIZE(ed_ids)) + return ERR_PTR(-EINVAL); + + ed = dpu->ed_priv[i]; + + mutex_lock(&ed->mutex); + + if (ed->inuse) { + mutex_unlock(&ed->mutex); + return ERR_PTR(-EBUSY); + } + + ed->inuse = true; + + mutex_unlock(&ed->mutex); + + return ed; +} +EXPORT_SYMBOL_GPL(dpu_ed_get); + +void dpu_ed_put(struct dpu_extdst *ed) +{ + mutex_lock(&ed->mutex); + + ed->inuse = false; + + mutex_unlock(&ed->mutex); +} +EXPORT_SYMBOL_GPL(dpu_ed_put); + +struct dpu_extdst *dpu_aux_ed_peek(struct dpu_extdst *ed) +{ + unsigned int aux_id = ed->id ^ 1; + int i; + + for (i = 0; i < ARRAY_SIZE(ed_ids); i++) + if (ed_ids[i] == aux_id) + return ed->dpu->ed_priv[i]; + + return NULL; +} +EXPORT_SYMBOL_GPL(dpu_aux_ed_peek); + +void _dpu_ed_init(struct dpu_soc *dpu, unsigned int id) +{ + struct dpu_extdst *ed; + int i; + + for (i = 0; i < ARRAY_SIZE(ed_ids); i++) + if (ed_ids[i] == id) + break; + + if (WARN_ON(i == ARRAY_SIZE(ed_ids))) + return; + + ed = dpu->ed_priv[i]; + + extdst_pixengcfg_src_sel(ed, ED_SRC_DISABLE); + extdst_pixengcfg_shden(ed, true); + extdst_pixengcfg_powerdown(ed, false); + extdst_pixengcfg_sync_mode(ed, SINGLE); + extdst_pixengcfg_reset(ed, false); + extdst_pixengcfg_div(ed, DIV_RESET); + extdst_shden(ed, true); + extdst_perfcountmode(ed, false); + extdst_kick_mode(ed, EXTERNAL); +} + +int dpu_ed_init(struct dpu_soc *dpu, unsigned int id, + unsigned long pec_base, unsigned long base) +{ + struct dpu_extdst *ed; + int ret, i; + + ed = devm_kzalloc(dpu->dev, sizeof(*ed), GFP_KERNEL); + if (!ed) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(ed_ids); i++) + if (ed_ids[i] == id) + break; + + if (i == ARRAY_SIZE(ed_ids)) + return -EINVAL; + + dpu->ed_priv[i] = ed; + + ed->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_32); + if (!ed->pec_base) + return -ENOMEM; + + ed->base = devm_ioremap(dpu->dev, base, SZ_64); + if (!ed->base) + return -ENOMEM; + + ed->dpu = dpu; + ed->id = id; + mutex_init(&ed->mutex); + + ret = extdst_pixengcfg_src_sel(ed, ED_SRC_DISABLE); + if (ret < 0) + return ret; + + _dpu_ed_init(dpu, id); + + return 0; +} diff --git a/drivers/gpu/imx/dpu/dpu-fetchdecode.c b/drivers/gpu/imx/dpu/dpu-fetchdecode.c new file mode 100644 index 000000000000..66d5862fbfa1 --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-fetchdecode.c @@ -0,0 +1,673 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2019,2021 NXP + * + * 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 <drm/drm_blend.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <video/dpu.h> +#include "dpu-prv.h" + +static const u32 fd_vproc_cap[2] = { + DPU_VPROC_CAP_HSCALER4 | DPU_VPROC_CAP_VSCALER4 | + DPU_VPROC_CAP_FETCHECO0, + DPU_VPROC_CAP_HSCALER5 | DPU_VPROC_CAP_VSCALER5 | + DPU_VPROC_CAP_FETCHECO1, +}; + +#define PIXENGCFG_DYNAMIC 0x8 +static const fd_dynamic_src_sel_t fd_srcs[2][4] = { + { + FD_SRC_DISABLE, FD_SRC_FETCHECO0, + FD_SRC_FETCHDECODE1, FD_SRC_FETCHWARP2 + }, { + FD_SRC_DISABLE, FD_SRC_FETCHECO1, + FD_SRC_FETCHDECODE0, FD_SRC_FETCHWARP2 + }, +}; + +#define PIXENGCFG_STATUS 0xC + +#define RINGBUFSTARTADDR0 0x10 +#define RINGBUFWRAPADDR0 0x14 +#define FRAMEPROPERTIES0 0x18 +#define BASEADDRESS0 0x1C +#define SOURCEBUFFERATTRIBUTES0 0x20 +#define SOURCEBUFFERDIMENSION0 0x24 +#define COLORCOMPONENTBITS0 0x28 +#define COLORCOMPONENTSHIFT0 0x2C +#define LAYEROFFSET0 0x30 +#define CLIPWINDOWOFFSET0 0x34 +#define CLIPWINDOWDIMENSIONS0 0x38 +#define CONSTANTCOLOR0 0x3C +#define LAYERPROPERTY0 0x40 +#define FRAMEDIMENSIONS 0x44 +#define FRAMERESAMPLING 0x48 +#define DECODECONTROL 0x4C +#define SOURCEBUFFERLENGTH 0x50 +#define CONTROL 0x54 +#define CONTROLTRIGGER 0x58 +#define START 0x5C +#define FETCHTYPE 0x60 +#define DECODERSTATUS 0x64 +#define READADDRESS0 0x68 +#define BURSTBUFFERPROPERTIES 0x6C +#define STATUS 0x70 +#define HIDDENSTATUS 0x74 + +struct dpu_fetchdecode { + struct dpu_fetchunit fu; + fetchtype_t fetchtype; +}; + +int fetchdecode_pixengcfg_dynamic_src_sel(struct dpu_fetchunit *fu, + fd_dynamic_src_sel_t src) +{ + int i; + + mutex_lock(&fu->mutex); + for (i = 0; i < 4; i++) { + if (fd_srcs[fu->id][i] == src) { + dpu_pec_fu_write(fu, PIXENGCFG_DYNAMIC, src); + mutex_unlock(&fu->mutex); + return 0; + } + } + mutex_unlock(&fu->mutex); + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(fetchdecode_pixengcfg_dynamic_src_sel); + +static void +fetchdecode_set_baseaddress(struct dpu_fetchunit *fu, unsigned int width, + unsigned int x_offset, unsigned int y_offset, + unsigned int mt_w, unsigned int mt_h, + int bpp, dma_addr_t baddr) +{ + unsigned int burst_size, stride; + bool nonzero_mod = !!mt_w; + + if (nonzero_mod) { + /* consider PRG x offset to calculate buffer address */ + baddr += (dma_addr_t)(x_offset % mt_w) * (bpp / 8); + + burst_size = fetchunit_burst_size_fixup_tkt343664(baddr); + + stride = width * (bpp / 8); + stride = fetchunit_stride_fixup_tkt339017(stride, burst_size, + baddr, nonzero_mod); + + /* consider PRG y offset to calculate buffer address */ + baddr += (dma_addr_t)(y_offset % mt_h) * stride; + } + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, BASEADDRESS0, baddr); + mutex_unlock(&fu->mutex); +} + +static void fetchdecode_set_src_bpp(struct dpu_fetchunit *fu, int bpp) +{ + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, SOURCEBUFFERATTRIBUTES0); + val &= ~0x3f0000; + val |= BITSPERPIXEL(bpp); + dpu_fu_write(fu, SOURCEBUFFERATTRIBUTES0, val); + mutex_unlock(&fu->mutex); +} + +static void +fetchdecode_set_src_stride(struct dpu_fetchunit *fu, + unsigned int width, unsigned int x_offset, + unsigned int mt_w, int bpp, unsigned int stride, + dma_addr_t baddr, bool use_prefetch) +{ + unsigned int burst_size; + bool nonzero_mod = !!mt_w; + u32 val; + + if (use_prefetch) { + /* consider PRG x offset to calculate buffer address */ + if (nonzero_mod) + baddr += (dma_addr_t)(x_offset % mt_w) * (bpp / 8); + + burst_size = fetchunit_burst_size_fixup_tkt343664(baddr); + + stride = width * (bpp / 8); + stride = fetchunit_stride_fixup_tkt339017(stride, burst_size, + baddr, nonzero_mod); + } + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, SOURCEBUFFERATTRIBUTES0); + val &= ~0xffff; + val |= STRIDE(stride); + dpu_fu_write(fu, SOURCEBUFFERATTRIBUTES0, val); + mutex_unlock(&fu->mutex); +} + +static void +fetchdecode_set_src_buf_dimensions(struct dpu_fetchunit *fu, + unsigned int w, unsigned int h, + u32 unused, bool deinterlace) +{ + u32 val; + + if (deinterlace) + h /= 2; + + val = LINEWIDTH(w) | LINECOUNT(h); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, SOURCEBUFFERDIMENSION0, val); + mutex_unlock(&fu->mutex); +} + +static void fetchdecode_set_fmt(struct dpu_fetchunit *fu, + u32 fmt, + enum drm_color_encoding color_encoding, + enum drm_color_range color_range, + bool deinterlace) +{ + u32 val, bits, shift; + bool is_planar_yuv = false, is_rastermode_yuv422 = false; + bool is_yuv422upsamplingmode_interpolate = false; + bool is_inputselect_compact = false; + bool need_csc = false; + int i; + + switch (fmt) { + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + is_rastermode_yuv422 = true; + is_yuv422upsamplingmode_interpolate = true; + need_csc = true; + break; + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + is_yuv422upsamplingmode_interpolate = true; + fallthrough; + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + if (deinterlace) + is_yuv422upsamplingmode_interpolate = true; + is_planar_yuv = true; + is_rastermode_yuv422 = true; + is_inputselect_compact = true; + need_csc = true; + break; + case DRM_FORMAT_NV24: + case DRM_FORMAT_NV42: + is_planar_yuv = true; + is_yuv422upsamplingmode_interpolate = true; + is_inputselect_compact = true; + need_csc = true; + break; + default: + break; + } + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, CONTROL); + val &= ~YUV422UPSAMPLINGMODE_MASK; + val &= ~INPUTSELECT_MASK; + val &= ~RASTERMODE_MASK; + if (is_yuv422upsamplingmode_interpolate) + val |= YUV422UPSAMPLINGMODE(YUV422UPSAMPLINGMODE__INTERPOLATE); + else + val |= YUV422UPSAMPLINGMODE(YUV422UPSAMPLINGMODE__REPLICATE); + if (is_inputselect_compact) + val |= INPUTSELECT(INPUTSELECT__COMPPACK); + else + val |= INPUTSELECT(INPUTSELECT__INACTIVE); + if (is_rastermode_yuv422) + val |= RASTERMODE(RASTERMODE__YUV422); + else + val |= RASTERMODE(RASTERMODE__NORMAL); + dpu_fu_write(fu, CONTROL, val); + + val = dpu_fu_read(fu, LAYERPROPERTY0); + val &= ~YUVCONVERSIONMODE_MASK; + if (need_csc) { + /* assuming fetchdecode always ouputs RGB pixel formats */ + if (color_encoding == DRM_COLOR_YCBCR_BT709) + val |= YUVCONVERSIONMODE(YUVCONVERSIONMODE__ITU709); + else if (color_encoding == DRM_COLOR_YCBCR_BT601 && + color_range == DRM_COLOR_YCBCR_FULL_RANGE) + val |= YUVCONVERSIONMODE(YUVCONVERSIONMODE__ITU601_FR); + else + val |= YUVCONVERSIONMODE(YUVCONVERSIONMODE__ITU601); + } else { + val |= YUVCONVERSIONMODE(YUVCONVERSIONMODE__OFF); + } + dpu_fu_write(fu, LAYERPROPERTY0, val); + mutex_unlock(&fu->mutex); + + for (i = 0; i < ARRAY_SIZE(dpu_pixel_format_matrix); i++) { + if (dpu_pixel_format_matrix[i].pixel_format == fmt) { + bits = dpu_pixel_format_matrix[i].bits; + shift = dpu_pixel_format_matrix[i].shift; + + if (is_planar_yuv) { + bits &= ~(U_BITS_MASK | V_BITS_MASK); + shift &= ~(U_SHIFT_MASK | V_SHIFT_MASK); + } + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, COLORCOMPONENTBITS0, bits); + dpu_fu_write(fu, COLORCOMPONENTSHIFT0, shift); + mutex_unlock(&fu->mutex); + return; + } + } + + WARN_ON(1); +} + +void fetchdecode_layeroffset(struct dpu_fetchunit *fu, unsigned int x, + unsigned int y) +{ + u32 val; + + val = LAYERXOFFSET(x) | LAYERYOFFSET(y); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, LAYEROFFSET0, val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchdecode_layeroffset); + +void fetchdecode_clipoffset(struct dpu_fetchunit *fu, unsigned int x, + unsigned int y) +{ + u32 val; + + val = CLIPWINDOWXOFFSET(x) | CLIPWINDOWYOFFSET(y); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, CLIPWINDOWOFFSET0, val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchdecode_clipoffset); + +static void +fetchdecode_set_pixel_blend_mode(struct dpu_fetchunit *fu, + unsigned int pixel_blend_mode, u16 alpha, + u32 fb_format) +{ + u32 mode = 0, val; + + if (pixel_blend_mode == DRM_MODE_BLEND_PREMULTI || + pixel_blend_mode == DRM_MODE_BLEND_COVERAGE) { + mode = ALPHACONSTENABLE; + + switch (fb_format) { + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRA8888: + mode |= ALPHASRCENABLE; + break; + } + } + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, LAYERPROPERTY0); + val &= ~(PREMULCONSTRGB | ALPHA_ENABLE_MASK | RGB_ENABLE_MASK); + val |= mode; + dpu_fu_write(fu, LAYERPROPERTY0, val); + + val = dpu_fu_read(fu, CONSTANTCOLOR0); + val &= ~CONSTANTALPHA_MASK; + val |= CONSTANTALPHA(alpha >> 8); + dpu_fu_write(fu, CONSTANTCOLOR0, val); + mutex_unlock(&fu->mutex); +} + +static void fetchdecode_enable_src_buf(struct dpu_fetchunit *fu) +{ + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, LAYERPROPERTY0); + val |= SOURCEBUFFERENABLE; + dpu_fu_write(fu, LAYERPROPERTY0, val); + mutex_unlock(&fu->mutex); +} + +static void fetchdecode_disable_src_buf(struct dpu_fetchunit *fu) +{ + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, LAYERPROPERTY0); + val &= ~SOURCEBUFFERENABLE; + dpu_fu_write(fu, LAYERPROPERTY0, val); + mutex_unlock(&fu->mutex); +} + +static bool fetchdecode_is_enabled(struct dpu_fetchunit *fu) +{ + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, LAYERPROPERTY0); + mutex_unlock(&fu->mutex); + + return !!(val & SOURCEBUFFERENABLE); +} + +void fetchdecode_clipdimensions(struct dpu_fetchunit *fu, unsigned int w, + unsigned int h) +{ + u32 val; + + val = CLIPWINDOWWIDTH(w) | CLIPWINDOWHEIGHT(h); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, CLIPWINDOWDIMENSIONS0, val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchdecode_clipdimensions); + +static void +fetchdecode_set_framedimensions(struct dpu_fetchunit *fu, + unsigned int w, unsigned int h, + bool deinterlace) +{ + u32 val; + + if (deinterlace) + h /= 2; + + val = FRAMEWIDTH(w) | FRAMEHEIGHT(h); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, FRAMEDIMENSIONS, val); + mutex_unlock(&fu->mutex); +} + +void fetchdecode_rgb_constantcolor(struct dpu_fetchunit *fu, + u8 r, u8 g, u8 b, u8 a) +{ + u32 val; + + val = rgb_color(r, g, b, a); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, CONSTANTCOLOR0, val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchdecode_rgb_constantcolor); + +void fetchdecode_yuv_constantcolor(struct dpu_fetchunit *fu, u8 y, u8 u, u8 v) +{ + u32 val; + + val = yuv_color(y, u, v); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, CONSTANTCOLOR0, val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchdecode_yuv_constantcolor); + +static void fetchdecode_set_controltrigger(struct dpu_fetchunit *fu) +{ + mutex_lock(&fu->mutex); + dpu_fu_write(fu, CONTROLTRIGGER, SHDTOKGEN); + mutex_unlock(&fu->mutex); +} + +int fetchdecode_fetchtype(struct dpu_fetchunit *fu, fetchtype_t *type) +{ + struct dpu_soc *dpu = fu->dpu; + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, FETCHTYPE); + val &= FETCHTYPE_MASK; + mutex_unlock(&fu->mutex); + + switch (val) { + case FETCHTYPE__DECODE: + case FETCHTYPE__LAYER: + case FETCHTYPE__WARP: + case FETCHTYPE__ECO: + case FETCHTYPE__PERSP: + case FETCHTYPE__ROT: + case FETCHTYPE__DECODEL: + case FETCHTYPE__LAYERL: + case FETCHTYPE__ROTL: + break; + default: + dev_warn(dpu->dev, "Invalid fetch type %u for FetchDecode%d\n", + val, fu->id); + return -EINVAL; + } + + *type = val; + return 0; +} +EXPORT_SYMBOL_GPL(fetchdecode_fetchtype); + +u32 fetchdecode_get_vproc_mask(struct dpu_fetchunit *fu) +{ + return fd_vproc_cap[fu->id]; +} +EXPORT_SYMBOL_GPL(fetchdecode_get_vproc_mask); + +struct dpu_fetchunit *fetchdecode_get_fetcheco(struct dpu_fetchunit *fu) +{ + struct dpu_soc *dpu = fu->dpu; + + switch (fu->id) { + case 0: + case 1: + return dpu->fe_priv[fu->id]; + default: + WARN_ON(1); + } + + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL_GPL(fetchdecode_get_fetcheco); + +bool fetchdecode_need_fetcheco(struct dpu_fetchunit *fu, u32 fmt) +{ + struct dpu_fetchunit *fe = fetchdecode_get_fetcheco(fu); + + if (IS_ERR_OR_NULL(fe)) + return false; + + switch (fmt) { + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + case DRM_FORMAT_NV24: + case DRM_FORMAT_NV42: + return true; + } + + return false; +} +EXPORT_SYMBOL_GPL(fetchdecode_need_fetcheco); + +struct dpu_hscaler *fetchdecode_get_hscaler(struct dpu_fetchunit *fu) +{ + struct dpu_soc *dpu = fu->dpu; + + switch (fu->id) { + case 0: + case 2: + return dpu->hs_priv[0]; + case 1: + case 3: + return dpu->hs_priv[1]; + default: + WARN_ON(1); + } + + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL_GPL(fetchdecode_get_hscaler); + +struct dpu_vscaler *fetchdecode_get_vscaler(struct dpu_fetchunit *fu) +{ + struct dpu_soc *dpu = fu->dpu; + + switch (fu->id) { + case 0: + case 2: + return dpu->vs_priv[0]; + case 1: + case 3: + return dpu->vs_priv[1]; + default: + WARN_ON(1); + } + + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL_GPL(fetchdecode_get_vscaler); + +struct dpu_fetchunit *dpu_fd_get(struct dpu_soc *dpu, int id) +{ + struct dpu_fetchunit *fu; + int i; + + for (i = 0; i < ARRAY_SIZE(fd_ids); i++) + if (fd_ids[i] == id) + break; + + if (i == ARRAY_SIZE(fd_ids)) + return ERR_PTR(-EINVAL); + + fu = dpu->fd_priv[i]; + + mutex_lock(&fu->mutex); + + if (fu->inuse) { + mutex_unlock(&fu->mutex); + return ERR_PTR(-EBUSY); + } + + fu->inuse = true; + + mutex_unlock(&fu->mutex); + + return fu; +} +EXPORT_SYMBOL_GPL(dpu_fd_get); + +void dpu_fd_put(struct dpu_fetchunit *fu) +{ + mutex_lock(&fu->mutex); + + fu->inuse = false; + + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(dpu_fd_put); + +static const struct dpu_fetchunit_ops fd_ops = { + .set_burstlength = fetchunit_set_burstlength, + .set_baseaddress = fetchdecode_set_baseaddress, + .set_src_bpp = fetchdecode_set_src_bpp, + .set_src_stride = fetchdecode_set_src_stride, + .set_src_buf_dimensions = fetchdecode_set_src_buf_dimensions, + .set_fmt = fetchdecode_set_fmt, + .set_pixel_blend_mode = fetchdecode_set_pixel_blend_mode, + .enable_src_buf = fetchdecode_enable_src_buf, + .disable_src_buf = fetchdecode_disable_src_buf, + .is_enabled = fetchdecode_is_enabled, + .set_framedimensions = fetchdecode_set_framedimensions, + .set_controltrigger = fetchdecode_set_controltrigger, + .get_stream_id = fetchunit_get_stream_id, + .set_stream_id = fetchunit_set_stream_id, +}; + +void _dpu_fd_init(struct dpu_soc *dpu, unsigned int id) +{ + struct dpu_fetchunit *fu; + int i; + + for (i = 0; i < ARRAY_SIZE(fd_ids); i++) + if (fd_ids[i] == id) + break; + + if (WARN_ON(i == ARRAY_SIZE(fd_ids))) + return; + + fu = dpu->fd_priv[i]; + + fetchdecode_pixengcfg_dynamic_src_sel(fu, FD_SRC_DISABLE); + fetchunit_baddr_autoupdate(fu, 0x0); + fetchunit_shden(fu, true); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, BURSTBUFFERMANAGEMENT, + SETNUMBUFFERS(16) | SETBURSTLENGTH(16)); + mutex_unlock(&fu->mutex); +} + +int dpu_fd_init(struct dpu_soc *dpu, unsigned int id, + unsigned long pec_base, unsigned long base) +{ + struct dpu_fetchdecode *fd; + struct dpu_fetchunit *fu; + int ret; + + fd = devm_kzalloc(dpu->dev, sizeof(*fd), GFP_KERNEL); + if (!fd) + return -ENOMEM; + + fu = &fd->fu; + dpu->fd_priv[id] = fu; + + fu->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_16); + if (!fu->pec_base) + return -ENOMEM; + + fu->base = devm_ioremap(dpu->dev, base, SZ_1K); + if (!fu->base) + return -ENOMEM; + + fu->dpu = dpu; + fu->id = id; + fu->type = FU_T_FD; + fu->ops = &fd_ops; + fu->name = "fetchdecode"; + + mutex_init(&fu->mutex); + + ret = fetchdecode_pixengcfg_dynamic_src_sel(fu, FD_SRC_DISABLE); + if (ret < 0) + return ret; + + ret = fetchdecode_fetchtype(fu, &fd->fetchtype); + if (ret < 0) + return ret; + + _dpu_fd_init(dpu, id); + + return 0; +} diff --git a/drivers/gpu/imx/dpu/dpu-fetcheco.c b/drivers/gpu/imx/dpu/dpu-fetcheco.c new file mode 100644 index 000000000000..d8bfb79e3ae9 --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-fetcheco.c @@ -0,0 +1,407 @@ +/* + * Copyright 2017-2019 NXP + * + * 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 <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <video/dpu.h> +#include "dpu-prv.h" + +#define BASEADDRESS0 0x10 +#define SOURCEBUFFERATTRIBUTES0 0x14 +#define SOURCEBUFFERDIMENSION0 0x18 +#define COLORCOMPONENTBITS0 0x1C +#define COLORCOMPONENTSHIFT0 0x20 +#define LAYEROFFSET0 0x24 +#define CLIPWINDOWOFFSET0 0x28 +#define CLIPWINDOWDIMENSIONS0 0x2C +#define CONSTANTCOLOR0 0x30 +#define LAYERPROPERTY0 0x34 +#define FRAMEDIMENSIONS 0x38 +#define FRAMERESAMPLING 0x3C +#define CONTROL 0x40 +#define CONTROLTRIGGER 0x44 +#define START 0x48 +#define FETCHTYPE 0x4C +#define BURSTBUFFERPROPERTIES 0x50 +#define HIDDENSTATUS 0x54 + +struct dpu_fetcheco { + struct dpu_fetchunit fu; +}; + +static void +fetcheco_set_src_buf_dimensions(struct dpu_fetchunit *fu, + unsigned int w, unsigned int h, + u32 fmt, bool deinterlace) +{ + int width, height; + u32 val; + + if (deinterlace) { + width = w; + height = h / 2; + } else { + width = dpu_format_plane_width(w, fmt, 1); + height = dpu_format_plane_height(h, fmt, 1); + } + + switch (fmt) { + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + case DRM_FORMAT_NV24: + case DRM_FORMAT_NV42: + break; + default: + WARN(1, "Unsupported FetchEco pixel format 0x%08x\n", fmt); + return; + } + + val = LINEWIDTH(width) | LINECOUNT(height); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, SOURCEBUFFERDIMENSION0, val); + mutex_unlock(&fu->mutex); +} + +static void fetcheco_set_fmt(struct dpu_fetchunit *fu, + u32 fmt, + enum drm_color_encoding unused1, + enum drm_color_range unused2, + bool unused3) +{ + u32 val, bits, shift; + int i, hsub, vsub; + unsigned int x, y; + + switch (fmt) { + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + case DRM_FORMAT_NV24: + case DRM_FORMAT_NV42: + break; + default: + WARN(1, "Unsupported FetchEco pixel format 0x%08x\n", fmt); + return; + } + + hsub = dpu_format_horz_chroma_subsampling(fmt); + switch (hsub) { + case 1: + x = 0x4; + break; + case 2: + x = 0x2; + break; + default: + WARN_ON(1); + return; + } + + vsub = dpu_format_vert_chroma_subsampling(fmt); + switch (vsub) { + case 1: + y = 0x4; + break; + case 2: + y = 0x2; + break; + default: + WARN_ON(1); + return; + } + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, FRAMERESAMPLING); + val &= ~(DELTAX_MASK | DELTAY_MASK); + val |= DELTAX(x) | DELTAY(y); + dpu_fu_write(fu, FRAMERESAMPLING, val); + + val = dpu_fu_read(fu, CONTROL); + val &= ~RASTERMODE_MASK; + val |= RASTERMODE(RASTERMODE__NORMAL); + dpu_fu_write(fu, CONTROL, val); + mutex_unlock(&fu->mutex); + + for (i = 0; i < ARRAY_SIZE(dpu_pixel_format_matrix); i++) { + if (dpu_pixel_format_matrix[i].pixel_format == fmt) { + bits = dpu_pixel_format_matrix[i].bits; + shift = dpu_pixel_format_matrix[i].shift; + + bits &= ~Y_BITS_MASK; + shift &= ~Y_SHIFT_MASK; + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, COLORCOMPONENTBITS0, bits); + dpu_fu_write(fu, COLORCOMPONENTSHIFT0, shift); + mutex_unlock(&fu->mutex); + return; + } + } + + WARN_ON(1); +} + +void fetcheco_layeroffset(struct dpu_fetchunit *fu, unsigned int x, + unsigned int y) +{ + u32 val; + + val = LAYERXOFFSET(x) | LAYERYOFFSET(y); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, LAYEROFFSET0, val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetcheco_layeroffset); + +void fetcheco_clipoffset(struct dpu_fetchunit *fu, unsigned int x, + unsigned int y) +{ + u32 val; + + val = CLIPWINDOWXOFFSET(x) | CLIPWINDOWYOFFSET(y); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, CLIPWINDOWOFFSET0, val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetcheco_clipoffset); + +void fetcheco_clipdimensions(struct dpu_fetchunit *fu, unsigned int w, + unsigned int h) +{ + u32 val; + + val = CLIPWINDOWWIDTH(w) | CLIPWINDOWHEIGHT(h); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, CLIPWINDOWDIMENSIONS0, val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetcheco_clipdimensions); + +static void +fetcheco_set_framedimensions(struct dpu_fetchunit *fu, + unsigned int w, unsigned int h, + bool deinterlace) +{ + u32 val; + + if (deinterlace) + h /= 2; + + val = FRAMEWIDTH(w) | FRAMEHEIGHT(h); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, FRAMEDIMENSIONS, val); + mutex_unlock(&fu->mutex); +} + +void fetcheco_frameresampling(struct dpu_fetchunit *fu, unsigned int x, + unsigned int y) +{ + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, FRAMERESAMPLING); + val &= ~(DELTAX_MASK | DELTAY_MASK); + val |= DELTAX(x) | DELTAY(y); + dpu_fu_write(fu, FRAMERESAMPLING, val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetcheco_frameresampling); + +static void fetcheco_set_controltrigger(struct dpu_fetchunit *fu) +{ + mutex_lock(&fu->mutex); + dpu_fu_write(fu, CONTROLTRIGGER, SHDTOKGEN); + mutex_unlock(&fu->mutex); +} + +int fetcheco_fetchtype(struct dpu_fetchunit *fu, fetchtype_t *type) +{ + struct dpu_soc *dpu = fu->dpu; + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, FETCHTYPE); + val &= FETCHTYPE_MASK; + mutex_unlock(&fu->mutex); + + switch (val) { + case FETCHTYPE__DECODE: + case FETCHTYPE__LAYER: + case FETCHTYPE__WARP: + case FETCHTYPE__ECO: + case FETCHTYPE__PERSP: + case FETCHTYPE__ROT: + case FETCHTYPE__DECODEL: + case FETCHTYPE__LAYERL: + case FETCHTYPE__ROTL: + break; + default: + dev_warn(dpu->dev, "Invalid fetch type %u for FetchEco%d\n", + val, fu->id); + return -EINVAL; + } + + *type = val; + return 0; +} +EXPORT_SYMBOL_GPL(fetcheco_fetchtype); + +dpu_block_id_t fetcheco_get_block_id(struct dpu_fetchunit *fu) +{ + switch (fu->id) { + case 0: + return ID_FETCHECO0; + case 1: + return ID_FETCHECO1; + case 2: + return ID_FETCHECO2; + case 9: + return ID_FETCHECO9; + default: + WARN_ON(1); + } + + return ID_NONE; +} +EXPORT_SYMBOL_GPL(fetcheco_get_block_id); + +struct dpu_fetchunit *dpu_fe_get(struct dpu_soc *dpu, int id) +{ + struct dpu_fetchunit *fu; + int i; + + for (i = 0; i < ARRAY_SIZE(fe_ids); i++) + if (fe_ids[i] == id) + break; + + if (i == ARRAY_SIZE(fe_ids)) + return ERR_PTR(-EINVAL); + + fu = dpu->fe_priv[i]; + + mutex_lock(&fu->mutex); + + if (fu->inuse) { + mutex_unlock(&fu->mutex); + return ERR_PTR(-EBUSY); + } + + fu->inuse = true; + + mutex_unlock(&fu->mutex); + + return fu; +} +EXPORT_SYMBOL_GPL(dpu_fe_get); + +void dpu_fe_put(struct dpu_fetchunit *fu) +{ + mutex_lock(&fu->mutex); + + fu->inuse = false; + + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(dpu_fe_put); + +static const struct dpu_fetchunit_ops fe_ops = { + .set_burstlength = fetchunit_set_burstlength, + .set_baseaddress = fetchunit_set_baseaddress, + .set_src_bpp = fetchunit_set_src_bpp, + .set_src_stride = fetchunit_set_src_stride, + .set_src_buf_dimensions = fetcheco_set_src_buf_dimensions, + .set_fmt = fetcheco_set_fmt, + .enable_src_buf = fetchunit_enable_src_buf, + .disable_src_buf = fetchunit_disable_src_buf, + .is_enabled = fetchunit_is_enabled, + .set_framedimensions = fetcheco_set_framedimensions, + .set_controltrigger = fetcheco_set_controltrigger, + .get_stream_id = fetchunit_get_stream_id, + .set_stream_id = fetchunit_set_stream_id, +}; + +void _dpu_fe_init(struct dpu_soc *dpu, unsigned int id) +{ + struct dpu_fetchunit *fu; + int i; + + for (i = 0; i < ARRAY_SIZE(fe_ids); i++) + if (fe_ids[i] == id) + break; + + if (WARN_ON(i == ARRAY_SIZE(fe_ids))) + return; + + fu = dpu->fe_priv[i]; + + fetchunit_shden(fu, true); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, BURSTBUFFERMANAGEMENT, + SETNUMBUFFERS(16) | SETBURSTLENGTH(16)); + mutex_unlock(&fu->mutex); +} + +int dpu_fe_init(struct dpu_soc *dpu, unsigned int id, + unsigned long pec_base, unsigned long base) +{ + struct dpu_fetcheco *fe; + struct dpu_fetchunit *fu; + int i; + + fe = devm_kzalloc(dpu->dev, sizeof(*fe), GFP_KERNEL); + if (!fe) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(fe_ids); i++) + if (fe_ids[i] == id) + break; + + if (i == ARRAY_SIZE(fe_ids)) + return -EINVAL; + + fu = &fe->fu; + dpu->fe_priv[i] = fu; + + fu->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_16); + if (!fu->pec_base) + return -ENOMEM; + + fu->base = devm_ioremap(dpu->dev, base, SZ_128); + if (!fu->base) + return -ENOMEM; + + fu->dpu = dpu; + fu->id = id; + fu->type = FU_T_FE; + fu->ops = &fe_ops; + fu->name = "fetcheco"; + + mutex_init(&fu->mutex); + + _dpu_fe_init(dpu, id); + + return 0; +} diff --git a/drivers/gpu/imx/dpu/dpu-fetchlayer.c b/drivers/gpu/imx/dpu/dpu-fetchlayer.c new file mode 100644 index 000000000000..3a72a8e65a17 --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-fetchlayer.c @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2019 NXP + * + * 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 <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <video/dpu.h> +#include "dpu-prv.h" + +#define PIXENGCFG_STATUS 0x8 +#define BASEADDRESS(n) (0x10 + (n) * 0x28) +#define SOURCEBUFFERATTRIBUTES(n) (0x14 + (n) * 0x28) +#define SOURCEBUFFERDIMENSION(n) (0x18 + (n) * 0x28) +#define COLORCOMPONENTBITS(n) (0x1C + (n) * 0x28) +#define COLORCOMPONENTSHIFT(n) (0x20 + (n) * 0x28) +#define LAYEROFFSET(n) (0x24 + (n) * 0x28) +#define CLIPWINDOWOFFSET(n) (0x28 + (n) * 0x28) +#define CLIPWINDOWDIMENSIONS(n) (0x2C + (n) * 0x28) +#define CONSTANTCOLOR(n) (0x30 + (n) * 0x28) +#define LAYERPROPERTY(n) (0x34 + (n) * 0x28) +#define FRAMEDIMENSIONS 0x150 +#define FRAMERESAMPLING 0x154 +#define CONTROL 0x158 +#define TRIGGERENABLE 0x15C +#define SHDLDREQ(lm) ((lm) & 0xFF) +#define CONTROLTRIGGER 0x160 +#define START 0x164 +#define FETCHTYPE 0x168 +#define BURSTBUFFERPROPERTIES 0x16C +#define STATUS 0x170 +#define HIDDENSTATUS 0x174 + +struct dpu_fetchlayer { + struct dpu_fetchunit fu; + fetchtype_t fetchtype; +}; + +static void +fetchlayer_set_src_buf_dimensions(struct dpu_fetchunit *fu, + unsigned int w, unsigned int h, + u32 unused1, bool unused2) +{ + u32 val; + + val = LINEWIDTH(w) | LINECOUNT(h); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, SOURCEBUFFERDIMENSION(fu->sub_id), val); + mutex_unlock(&fu->mutex); +} + +static void fetchlayer_set_fmt(struct dpu_fetchunit *fu, + u32 fmt, + enum drm_color_encoding color_encoding, + enum drm_color_range color_range, + bool unused) +{ + u32 val, bits, shift; + int i, sub_id = fu->sub_id; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, LAYERPROPERTY(sub_id)); + val &= ~YUVCONVERSIONMODE_MASK; + val |= YUVCONVERSIONMODE(YUVCONVERSIONMODE__OFF); + dpu_fu_write(fu, LAYERPROPERTY(sub_id), val); + mutex_unlock(&fu->mutex); + + for (i = 0; i < ARRAY_SIZE(dpu_pixel_format_matrix); i++) { + if (dpu_pixel_format_matrix[i].pixel_format == fmt) { + bits = dpu_pixel_format_matrix[i].bits; + shift = dpu_pixel_format_matrix[i].shift; + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, COLORCOMPONENTBITS(sub_id), bits); + dpu_fu_write(fu, COLORCOMPONENTSHIFT(sub_id), shift); + mutex_unlock(&fu->mutex); + return; + } + } + + WARN_ON(1); +} + +static void +fetchlayer_set_framedimensions(struct dpu_fetchunit *fu, unsigned int w, + unsigned int h, bool unused) +{ + u32 val; + + val = FRAMEWIDTH(w) | FRAMEHEIGHT(h); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, FRAMEDIMENSIONS, val); + mutex_unlock(&fu->mutex); +} + +void fetchlayer_rgb_constantcolor(struct dpu_fetchunit *fu, + u8 r, u8 g, u8 b, u8 a) +{ + u32 val; + + val = rgb_color(r, g, b, a); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, CONSTANTCOLOR(fu->id), val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchlayer_rgb_constantcolor); + +void fetchlayer_yuv_constantcolor(struct dpu_fetchunit *fu, u8 y, u8 u, u8 v) +{ + u32 val; + + val = yuv_color(y, u, v); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, CONSTANTCOLOR(fu->id), val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchlayer_yuv_constantcolor); + +static void fetchlayer_set_controltrigger(struct dpu_fetchunit *fu) +{ + mutex_lock(&fu->mutex); + dpu_fu_write(fu, CONTROLTRIGGER, SHDTOKGEN); + mutex_unlock(&fu->mutex); +} + +int fetchlayer_fetchtype(struct dpu_fetchunit *fu, fetchtype_t *type) +{ + struct dpu_soc *dpu = fu->dpu; + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, FETCHTYPE); + val &= FETCHTYPE_MASK; + mutex_unlock(&fu->mutex); + + switch (val) { + case FETCHTYPE__DECODE: + case FETCHTYPE__LAYER: + case FETCHTYPE__WARP: + case FETCHTYPE__ECO: + case FETCHTYPE__PERSP: + case FETCHTYPE__ROT: + case FETCHTYPE__DECODEL: + case FETCHTYPE__LAYERL: + case FETCHTYPE__ROTL: + break; + default: + dev_warn(dpu->dev, "Invalid fetch type %u for FetchLayer%d\n", + val, fu->id); + return -EINVAL; + } + + *type = val; + return 0; +} +EXPORT_SYMBOL_GPL(fetchlayer_fetchtype); + +struct dpu_fetchunit *dpu_fl_get(struct dpu_soc *dpu, int id) +{ + struct dpu_fetchunit *fu; + int i; + + for (i = 0; i < ARRAY_SIZE(fl_ids); i++) + if (fl_ids[i] == id) + break; + + if (i == ARRAY_SIZE(fl_ids)) + return ERR_PTR(-EINVAL); + + fu = dpu->fl_priv[i]; + + mutex_lock(&fu->mutex); + + if (fu->inuse) { + mutex_unlock(&fu->mutex); + return ERR_PTR(-EBUSY); + } + + fu->inuse = true; + + mutex_unlock(&fu->mutex); + + return fu; +} +EXPORT_SYMBOL_GPL(dpu_fl_get); + +void dpu_fl_put(struct dpu_fetchunit *fu) +{ + mutex_lock(&fu->mutex); + + fu->inuse = false; + + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(dpu_fl_put); + +static const struct dpu_fetchunit_ops fl_ops = { + .set_burstlength = fetchunit_set_burstlength, + .set_baseaddress = fetchunit_set_baseaddress, + .set_src_bpp = fetchunit_set_src_bpp, + .set_src_stride = fetchunit_set_src_stride, + .set_src_buf_dimensions = fetchlayer_set_src_buf_dimensions, + .set_fmt = fetchlayer_set_fmt, + .set_pixel_blend_mode = fetchunit_set_pixel_blend_mode, + .enable_src_buf = fetchunit_enable_src_buf, + .disable_src_buf = fetchunit_disable_src_buf, + .is_enabled = fetchunit_is_enabled, + .set_framedimensions = fetchlayer_set_framedimensions, + .set_controltrigger = fetchlayer_set_controltrigger, + .get_stream_id = fetchunit_get_stream_id, + .set_stream_id = fetchunit_set_stream_id, +}; + +void _dpu_fl_init(struct dpu_soc *dpu, unsigned int id) +{ + struct dpu_fetchunit *fu; + int i; + + for (i = 0; i < ARRAY_SIZE(fl_ids); i++) + if (fl_ids[i] == id) + break; + + if (WARN_ON(i == ARRAY_SIZE(fl_ids))) + return; + + fu = dpu->fl_priv[i]; + + fetchunit_baddr_autoupdate(fu, 0x0); + fetchunit_shden(fu, true); + fetchunit_shdldreq_sticky(fu, 0xFF); + fetchunit_disable_src_buf(fu); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, BURSTBUFFERMANAGEMENT, + SETNUMBUFFERS(16) | SETBURSTLENGTH(16)); + mutex_unlock(&fu->mutex); +} + +int dpu_fl_init(struct dpu_soc *dpu, unsigned int id, + unsigned long pec_base, unsigned long base) +{ + struct dpu_fetchlayer *fl; + struct dpu_fetchunit *fu; + int ret; + + fl = devm_kzalloc(dpu->dev, sizeof(*fl), GFP_KERNEL); + if (!fl) + return -ENOMEM; + + fu = &fl->fu; + dpu->fl_priv[id] = fu; + + fu->pec_base = devm_ioremap(dpu->dev, base, SZ_16); + if (!fu->pec_base) + return -ENOMEM; + + fu->base = devm_ioremap(dpu->dev, base, SZ_512); + if (!fu->base) + return -ENOMEM; + + fu->dpu = dpu; + fu->id = id; + fu->sub_id = 0; + fu->type = FU_T_FL; + fu->ops = &fl_ops; + fu->name = "fetchlayer"; + + mutex_init(&fu->mutex); + + ret = fetchlayer_fetchtype(fu, &fl->fetchtype); + if (ret < 0) + return ret; + + _dpu_fl_init(dpu, id); + + return 0; +} diff --git a/drivers/gpu/imx/dpu/dpu-fetchunit.c b/drivers/gpu/imx/dpu/dpu-fetchunit.c new file mode 100644 index 000000000000..e6a49874ac02 --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-fetchunit.c @@ -0,0 +1,346 @@ +/* + * Copyright 2018-2019,2021 NXP + * + * 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 <drm/drm_blend.h> +#include <video/dpu.h> +#include "dpu-prv.h" + +#define BASEADDRESS(n) (0x10 + (n) * 0x28) +#define SOURCEBUFFERATTRIBUTES(n) (0x14 + (n) * 0x28) +#define SOURCEBUFFERDIMENSION(n) (0x18 + (n) * 0x28) +#define COLORCOMPONENTBITS(n) (0x1C + (n) * 0x28) +#define COLORCOMPONENTSHIFT(n) (0x20 + (n) * 0x28) +#define LAYEROFFSET(n) (0x24 + (n) * 0x28) +#define CLIPWINDOWOFFSET(n) (0x28 + (n) * 0x28) +#define CLIPWINDOWDIMENSIONS(n) (0x2C + (n) * 0x28) +#define CONSTANTCOLOR(n) (0x30 + (n) * 0x28) +#define LAYERPROPERTY(n) (0x34 + (n) * 0x28) + +/* base address has to align to burst size */ +unsigned int fetchunit_burst_size_fixup_tkt343664(dma_addr_t baddr) +{ + unsigned int burst_size; + + burst_size = 1 << (ffs(baddr) - 1); + burst_size = round_up(burst_size, 8); + burst_size = min(burst_size, 128U); + + return burst_size; +} +EXPORT_SYMBOL_GPL(fetchunit_burst_size_fixup_tkt343664); + +/* fixup for burst size vs stride mismatch */ +unsigned int +fetchunit_stride_fixup_tkt339017(unsigned int stride, unsigned int burst_size, + dma_addr_t baddr, bool nonzero_mod) +{ + if (nonzero_mod) + stride = round_up(stride + round_up(baddr % 8, 8), burst_size); + else + stride = round_up(stride, burst_size); + + return stride; +} +EXPORT_SYMBOL_GPL(fetchunit_stride_fixup_tkt339017); + +void fetchunit_get_dprc(struct dpu_fetchunit *fu, void *data) +{ + if (WARN_ON(!fu)) + return; + + fu->dprc = data; +} +EXPORT_SYMBOL_GPL(fetchunit_get_dprc); + +void fetchunit_shden(struct dpu_fetchunit *fu, bool enable) +{ + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, STATICCONTROL); + if (enable) + val |= SHDEN; + else + val &= ~SHDEN; + dpu_fu_write(fu, STATICCONTROL, val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchunit_shden); + +void fetchunit_baddr_autoupdate(struct dpu_fetchunit *fu, u8 layer_mask) +{ + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, STATICCONTROL); + val &= ~BASEADDRESSAUTOUPDATE_MASK; + val |= BASEADDRESSAUTOUPDATE(layer_mask); + dpu_fu_write(fu, STATICCONTROL, val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchunit_baddr_autoupdate); + +void fetchunit_shdldreq_sticky(struct dpu_fetchunit *fu, u8 layer_mask) +{ + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, STATICCONTROL); + val &= ~SHDLDREQSTICKY_MASK; + val |= SHDLDREQSTICKY(layer_mask); + dpu_fu_write(fu, STATICCONTROL, val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchunit_shdldreq_sticky); + +void fetchunit_set_burstlength(struct dpu_fetchunit *fu, + unsigned int x_offset, unsigned int mt_w, + int bpp, dma_addr_t baddr, bool use_prefetch) +{ + struct dpu_soc *dpu = fu->dpu; + unsigned int burst_size, burst_length; + bool nonzero_mod = !!mt_w; + u32 val; + + if (use_prefetch) { + /* consider PRG x offset to calculate buffer address */ + if (nonzero_mod) + baddr += (dma_addr_t)(x_offset % mt_w) * (bpp / 8); + + burst_size = fetchunit_burst_size_fixup_tkt343664(baddr); + burst_length = burst_size / 8; + } else { + burst_length = 16; + } + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, BURSTBUFFERMANAGEMENT); + val &= ~SETBURSTLENGTH_MASK; + val |= SETBURSTLENGTH(burst_length); + dpu_fu_write(fu, BURSTBUFFERMANAGEMENT, val); + mutex_unlock(&fu->mutex); + + dev_dbg(dpu->dev, "%s%d burst length is %u\n", + fu->name, fu->id, burst_length); +} +EXPORT_SYMBOL_GPL(fetchunit_set_burstlength); + +void fetchunit_set_baseaddress(struct dpu_fetchunit *fu, unsigned int width, + unsigned int x_offset, unsigned int y_offset, + unsigned int mt_w, unsigned int mt_h, + int bpp, dma_addr_t baddr) +{ + unsigned int burst_size, stride; + bool nonzero_mod = !!mt_w; + + if (nonzero_mod) { + /* consider PRG x offset to calculate buffer address */ + baddr += (dma_addr_t)(x_offset % mt_w) * (bpp / 8); + + burst_size = fetchunit_burst_size_fixup_tkt343664(baddr); + + stride = width * (bpp / 8); + stride = fetchunit_stride_fixup_tkt339017(stride, burst_size, + baddr, nonzero_mod); + + /* consider PRG y offset to calculate buffer address */ + baddr += (dma_addr_t)(y_offset % mt_h) * stride; + } + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, BASEADDRESS(fu->sub_id), baddr); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchunit_set_baseaddress); + +void fetchunit_set_src_bpp(struct dpu_fetchunit *fu, int bpp) +{ + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, SOURCEBUFFERATTRIBUTES(fu->sub_id)); + val &= ~0x3f0000; + val |= BITSPERPIXEL(bpp); + dpu_fu_write(fu, SOURCEBUFFERATTRIBUTES(fu->sub_id), val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchunit_set_src_bpp); + +/* + * The arguments width and bpp are valid only when use_prefetch is true. + * For fetcheco, since the pixel format has to be NV12 or NV21 when + * use_prefetch is true, we assume width stands for how many UV we have + * in bytes for one line, while bpp should be 8bits for every U or V component. + */ +void fetchunit_set_src_stride(struct dpu_fetchunit *fu, + unsigned int width, unsigned int x_offset, + unsigned int mt_w, int bpp, unsigned int stride, + dma_addr_t baddr, bool use_prefetch) +{ + unsigned int burst_size; + bool nonzero_mod = !!mt_w; + u32 val; + + if (use_prefetch) { + /* consider PRG x offset to calculate buffer address */ + if (nonzero_mod) + baddr += (dma_addr_t)(x_offset % mt_w) * (bpp / 8); + + burst_size = fetchunit_burst_size_fixup_tkt343664(baddr); + + stride = width * (bpp / 8); + stride = fetchunit_stride_fixup_tkt339017(stride, burst_size, + baddr, nonzero_mod); + } + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, SOURCEBUFFERATTRIBUTES(fu->sub_id)); + val &= ~0xffff; + val |= STRIDE(stride); + dpu_fu_write(fu, SOURCEBUFFERATTRIBUTES(fu->sub_id), val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchunit_set_src_stride); + +void fetchunit_set_pixel_blend_mode(struct dpu_fetchunit *fu, + unsigned int pixel_blend_mode, u16 alpha, + u32 fb_format) +{ + u32 mode = 0, val; + + if (pixel_blend_mode == DRM_MODE_BLEND_PREMULTI || + pixel_blend_mode == DRM_MODE_BLEND_COVERAGE) { + mode = ALPHACONSTENABLE; + + switch (fb_format) { + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRA8888: + mode |= ALPHASRCENABLE; + break; + } + } + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, LAYERPROPERTY(fu->sub_id)); + val &= ~(PREMULCONSTRGB | ALPHA_ENABLE_MASK | RGB_ENABLE_MASK); + val |= mode; + dpu_fu_write(fu, LAYERPROPERTY(fu->sub_id), val); + + val = dpu_fu_read(fu, CONSTANTCOLOR(fu->sub_id)); + val &= ~CONSTANTALPHA_MASK; + val |= CONSTANTALPHA(alpha >> 8); + dpu_fu_write(fu, CONSTANTCOLOR(fu->sub_id), val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchunit_set_pixel_blend_mode); + +void fetchunit_enable_src_buf(struct dpu_fetchunit *fu) +{ + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, LAYERPROPERTY(fu->sub_id)); + val |= SOURCEBUFFERENABLE; + dpu_fu_write(fu, LAYERPROPERTY(fu->sub_id), val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchunit_enable_src_buf); + +void fetchunit_disable_src_buf(struct dpu_fetchunit *fu) +{ + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, LAYERPROPERTY(fu->sub_id)); + val &= ~SOURCEBUFFERENABLE; + dpu_fu_write(fu, LAYERPROPERTY(fu->sub_id), val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchunit_disable_src_buf); + +bool fetchunit_is_enabled(struct dpu_fetchunit *fu) +{ + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, LAYERPROPERTY(fu->sub_id)); + mutex_unlock(&fu->mutex); + + return !!(val & SOURCEBUFFERENABLE); +} +EXPORT_SYMBOL_GPL(fetchunit_is_enabled); + +unsigned int fetchunit_get_stream_id(struct dpu_fetchunit *fu) +{ + if (WARN_ON(!fu)) + return DPU_PLANE_SRC_DISABLED; + + return fu->stream_id; +} +EXPORT_SYMBOL_GPL(fetchunit_get_stream_id); + +void fetchunit_set_stream_id(struct dpu_fetchunit *fu, unsigned int id) +{ + if (WARN_ON(!fu)) + return; + + switch (id) { + case DPU_PLANE_SRC_TO_DISP_STREAM0: + case DPU_PLANE_SRC_TO_DISP_STREAM1: + case DPU_PLANE_SRC_DISABLED: + fu->stream_id = id; + break; + default: + WARN_ON(1); + } +} +EXPORT_SYMBOL_GPL(fetchunit_set_stream_id); + +bool fetchunit_is_fetchdecode(struct dpu_fetchunit *fu) +{ + if (WARN_ON(!fu)) + return false; + + return fu->type == FU_T_FD; +} +EXPORT_SYMBOL_GPL(fetchunit_is_fetchdecode); + +bool fetchunit_is_fetcheco(struct dpu_fetchunit *fu) +{ + if (WARN_ON(!fu)) + return false; + + return fu->type == FU_T_FE; +} +EXPORT_SYMBOL_GPL(fetchunit_is_fetcheco); + +bool fetchunit_is_fetchlayer(struct dpu_fetchunit *fu) +{ + if (WARN_ON(!fu)) + return false; + + return fu->type == FU_T_FL; +} +EXPORT_SYMBOL_GPL(fetchunit_is_fetchlayer); + +bool fetchunit_is_fetchwarp(struct dpu_fetchunit *fu) +{ + if (WARN_ON(!fu)) + return false; + + return fu->type == FU_T_FW; +} +EXPORT_SYMBOL_GPL(fetchunit_is_fetchwarp); diff --git a/drivers/gpu/imx/dpu/dpu-fetchwarp.c b/drivers/gpu/imx/dpu/dpu-fetchwarp.c new file mode 100644 index 000000000000..953368e3fade --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-fetchwarp.c @@ -0,0 +1,305 @@ +/* + * Copyright 2018-2019 NXP + * + * 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 <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <video/dpu.h> +#include "dpu-prv.h" + +#define PIXENGCFG_STATUS 0x8 +#define BASEADDRESS(n) (0x10 + (n) * 0x28) +#define SOURCEBUFFERATTRIBUTES(n) (0x14 + (n) * 0x28) +#define SOURCEBUFFERDIMENSION(n) (0x18 + (n) * 0x28) +#define COLORCOMPONENTBITS(n) (0x1C + (n) * 0x28) +#define COLORCOMPONENTSHIFT(n) (0x20 + (n) * 0x28) +#define LAYEROFFSET(n) (0x24 + (n) * 0x28) +#define CLIPWINDOWOFFSET(n) (0x28 + (n) * 0x28) +#define CLIPWINDOWDIMENSIONS(n) (0x2C + (n) * 0x28) +#define CONSTANTCOLOR(n) (0x30 + (n) * 0x28) +#define LAYERPROPERTY(n) (0x34 + (n) * 0x28) +#define FRAMEDIMENSIONS 0x150 +#define FRAMERESAMPLING 0x154 +#define WARPCONTROL 0x158 +#define ARBSTARTX 0x15c +#define ARBSTARTY 0x160 +#define ARBDELTA 0x164 +#define FIRPOSITIONS 0x168 +#define FIRCOEFFICIENTS 0x16c +#define CONTROL 0x170 +#define TRIGGERENABLE 0x174 +#define SHDLDREQ(lm) ((lm) & 0xFF) +#define CONTROLTRIGGER 0x178 +#define START 0x17c +#define FETCHTYPE 0x180 +#define BURSTBUFFERPROPERTIES 0x184 +#define STATUS 0x188 +#define HIDDENSTATUS 0x18c + +struct dpu_fetchwarp { + struct dpu_fetchunit fu; + fetchtype_t fetchtype; +}; + +static void +fetchwarp_set_src_buf_dimensions(struct dpu_fetchunit *fu, + unsigned int w, unsigned int h, + u32 unused1, bool unused2) +{ + u32 val; + + val = LINEWIDTH(w) | LINECOUNT(h); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, SOURCEBUFFERDIMENSION(fu->sub_id), val); + mutex_unlock(&fu->mutex); +} + +static void fetchwarp_set_fmt(struct dpu_fetchunit *fu, + u32 fmt, + enum drm_color_encoding color_encoding, + enum drm_color_range color_range, + bool unused) +{ + u32 val, bits, shift; + int i, sub_id = fu->sub_id; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, LAYERPROPERTY(sub_id)); + val &= ~YUVCONVERSIONMODE_MASK; + dpu_fu_write(fu, LAYERPROPERTY(sub_id), val); + mutex_unlock(&fu->mutex); + + for (i = 0; i < ARRAY_SIZE(dpu_pixel_format_matrix); i++) { + if (dpu_pixel_format_matrix[i].pixel_format == fmt) { + bits = dpu_pixel_format_matrix[i].bits; + shift = dpu_pixel_format_matrix[i].shift; + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, COLORCOMPONENTBITS(sub_id), bits); + dpu_fu_write(fu, COLORCOMPONENTSHIFT(sub_id), shift); + mutex_unlock(&fu->mutex); + return; + } + } + + WARN_ON(1); +} + +static void +fetchwarp_set_framedimensions(struct dpu_fetchunit *fu, + unsigned int w, unsigned int h, bool unused) +{ + u32 val; + + val = FRAMEWIDTH(w) | FRAMEHEIGHT(h); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, FRAMEDIMENSIONS, val); + mutex_unlock(&fu->mutex); +} + +void fetchwarp_rgb_constantcolor(struct dpu_fetchunit *fu, + u8 r, u8 g, u8 b, u8 a) +{ + u32 val; + + val = rgb_color(r, g, b, a); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, CONSTANTCOLOR(fu->id), val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchwarp_rgb_constantcolor); + +void fetchwarp_yuv_constantcolor(struct dpu_fetchunit *fu, u8 y, u8 u, u8 v) +{ + u32 val; + + val = yuv_color(y, u, v); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, CONSTANTCOLOR(fu->id), val); + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(fetchwarp_yuv_constantcolor); + +static void fetchwarp_set_controltrigger(struct dpu_fetchunit *fu) +{ + mutex_lock(&fu->mutex); + dpu_fu_write(fu, CONTROLTRIGGER, SHDTOKGEN); + mutex_unlock(&fu->mutex); +} + +int fetchwarp_fetchtype(struct dpu_fetchunit *fu, fetchtype_t *type) +{ + struct dpu_soc *dpu = fu->dpu; + u32 val; + + mutex_lock(&fu->mutex); + val = dpu_fu_read(fu, FETCHTYPE); + val &= FETCHTYPE_MASK; + mutex_unlock(&fu->mutex); + + switch (val) { + case FETCHTYPE__DECODE: + case FETCHTYPE__LAYER: + case FETCHTYPE__WARP: + case FETCHTYPE__ECO: + case FETCHTYPE__PERSP: + case FETCHTYPE__ROT: + case FETCHTYPE__DECODEL: + case FETCHTYPE__LAYERL: + case FETCHTYPE__ROTL: + break; + default: + dev_warn(dpu->dev, "Invalid fetch type %u for FetchWarp%d\n", + val, fu->id); + return -EINVAL; + } + + *type = val; + return 0; +} +EXPORT_SYMBOL_GPL(fetchwarp_fetchtype); + +struct dpu_fetchunit *dpu_fw_get(struct dpu_soc *dpu, int id) +{ + struct dpu_fetchunit *fu; + int i; + + for (i = 0; i < ARRAY_SIZE(fw_ids); i++) + if (fw_ids[i] == id) + break; + + if (i == ARRAY_SIZE(fw_ids)) + return ERR_PTR(-EINVAL); + + fu = dpu->fw_priv[i]; + + mutex_lock(&fu->mutex); + + if (fu->inuse) { + mutex_unlock(&fu->mutex); + return ERR_PTR(-EBUSY); + } + + fu->inuse = true; + + mutex_unlock(&fu->mutex); + + return fu; +} +EXPORT_SYMBOL_GPL(dpu_fw_get); + +void dpu_fw_put(struct dpu_fetchunit *fu) +{ + mutex_lock(&fu->mutex); + + fu->inuse = false; + + mutex_unlock(&fu->mutex); +} +EXPORT_SYMBOL_GPL(dpu_fw_put); + +static const struct dpu_fetchunit_ops fw_ops = { + .set_burstlength = fetchunit_set_burstlength, + .set_baseaddress = fetchunit_set_baseaddress, + .set_src_bpp = fetchunit_set_src_bpp, + .set_src_stride = fetchunit_set_src_stride, + .set_src_buf_dimensions = fetchwarp_set_src_buf_dimensions, + .set_fmt = fetchwarp_set_fmt, + .set_pixel_blend_mode = fetchunit_set_pixel_blend_mode, + .enable_src_buf = fetchunit_enable_src_buf, + .disable_src_buf = fetchunit_disable_src_buf, + .is_enabled = fetchunit_is_enabled, + .set_framedimensions = fetchwarp_set_framedimensions, + .set_controltrigger = fetchwarp_set_controltrigger, + .get_stream_id = fetchunit_get_stream_id, + .set_stream_id = fetchunit_set_stream_id, +}; + +void _dpu_fw_init(struct dpu_soc *dpu, unsigned int id) +{ + struct dpu_fetchunit *fu; + int i; + + for (i = 0; i < ARRAY_SIZE(fw_ids); i++) + if (fw_ids[i] == id) + break; + + if (WARN_ON(i == ARRAY_SIZE(fw_ids))) + return; + + fu = dpu->fw_priv[i]; + + fetchunit_baddr_autoupdate(fu, 0x0); + fetchunit_shden(fu, true); + fetchunit_shdldreq_sticky(fu, 0xFF); + fetchunit_disable_src_buf(fu); + + mutex_lock(&fu->mutex); + dpu_fu_write(fu, BURSTBUFFERMANAGEMENT, + SETNUMBUFFERS(16) | SETBURSTLENGTH(16)); + mutex_unlock(&fu->mutex); +} + +int dpu_fw_init(struct dpu_soc *dpu, unsigned int id, + unsigned long pec_base, unsigned long base) +{ + struct dpu_fetchwarp *fw; + struct dpu_fetchunit *fu; + int i, ret; + + fw = devm_kzalloc(dpu->dev, sizeof(*fw), GFP_KERNEL); + if (!fw) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(fw_ids); i++) + if (fw_ids[i] == id) + break; + + if (i == ARRAY_SIZE(fw_ids)) + return -EINVAL; + + fu = &fw->fu; + dpu->fw_priv[i] = fu; + + fu->pec_base = devm_ioremap(dpu->dev, base, SZ_16); + if (!fu->pec_base) + return -ENOMEM; + + fu->base = devm_ioremap(dpu->dev, base, SZ_512); + if (!fu->base) + return -ENOMEM; + + fu->dpu = dpu; + fu->id = id; + fu->sub_id = 0; + fu->type = FU_T_FW; + fu->ops = &fw_ops; + fu->name = "fetchwarp"; + + mutex_init(&fu->mutex); + + ret = fetchwarp_fetchtype(fu, &fw->fetchtype); + if (ret < 0) + return ret; + + _dpu_fw_init(dpu, id); + + return 0; +} diff --git a/drivers/gpu/imx/dpu/dpu-framegen.c b/drivers/gpu/imx/dpu/dpu-framegen.c new file mode 100644 index 000000000000..50d7d50e1ff8 --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-framegen.c @@ -0,0 +1,601 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2020 NXP + * + * 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 <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <drm/drm_mode.h> +#include <video/dpu.h> +#include "dpu-prv.h" + +#define FGSTCTRL 0x8 +#define FGSYNCMODE_MASK 0x6 +#define HTCFG1 0xC +#define HTOTAL(n) ((((n) - 1) & 0x3FFF) << 16) +#define HACT(n) ((n) & 0x3FFF) +#define HTCFG2 0x10 +#define HSEN BIT(31) +#define HSBP(n) ((((n) - 1) & 0x3FFF) << 16) +#define HSYNC(n) (((n) - 1) & 0x3FFF) +#define VTCFG1 0x14 +#define VTOTAL(n) ((((n) - 1) & 0x3FFF) << 16) +#define VACT(n) ((n) & 0x3FFF) +#define VTCFG2 0x18 +#define VSEN BIT(31) +#define VSBP(n) ((((n) - 1) & 0x3FFF) << 16) +#define VSYNC(n) (((n) - 1) & 0x3FFF) +#define INTCONFIG(n) (0x1C + 4 * (n)) +#define EN BIT(31) +#define ROW(n) (((n) & 0x3FFF) << 16) +#define COL(n) ((n) & 0x3FFF) +#define PKICKCONFIG 0x2C +#define SKICKCONFIG 0x30 +#define SECSTATCONFIG 0x34 +#define FGSRCR1 0x38 +#define FGSRCR2 0x3C +#define FGSRCR3 0x40 +#define FGSRCR4 0x44 +#define FGSRCR5 0x48 +#define FGSRCR6 0x4C +#define FGKSDR 0x50 +#define PACFG 0x54 +#define STARTX(n) (((n) + 1) & 0x3FFF) +#define STARTY(n) (((((n) + 1) & 0x3FFF)) << 16) +#define SACFG 0x58 +#define FGINCTRL 0x5C +#define FGDM_MASK 0x7 +#define ENPRIMALPHA BIT(3) +#define ENSECALPHA BIT(4) +#define FGINCTRLPANIC 0x60 +#define FGCCR 0x64 +#define CCALPHA(a) (((a) & 0x1) << 30) +#define CCRED(r) (((r) & 0x3FF) << 20) +#define CCGREEN(g) (((g) & 0x3FF) << 10) +#define CCBLUE(b) ((b) & 0x3FF) +#define FGENABLE 0x68 +#define FGEN BIT(0) +#define FGSLR 0x6C +#define FGENSTS 0x70 +#define ENSTS BIT(0) +#define FGTIMESTAMP 0x74 +#define LINEINDEX_MASK 0x3FFF +#define LINEINDEX_SHIFT 0 +#define FRAMEINDEX_MASK 0xFFFFC000 +#define FRAMEINDEX_SHIFT 14 +#define FGCHSTAT 0x78 +#define SECSYNCSTAT BIT(24) +#define SFIFOEMPTY BIT(16) +#define FGCHSTATCLR 0x7C +#define CLRSECSTAT BIT(16) +#define FGSKEWMON 0x80 +#define FGSFIFOMIN 0x84 +#define FGSFIFOMAX 0x88 +#define FGSFIFOFILLCLR 0x8C +#define FGSREPD 0x90 +#define FGSRFTD 0x94 + +#define KHZ 1000 +#define PLL_MIN_FREQ_HZ 648000000 + +struct dpu_framegen { + void __iomem *base; + struct clk *clk_pll; + struct clk *clk_bypass; + struct clk *clk_disp; + struct clk *clk_disp_lpcg; + struct mutex mutex; + int id; + unsigned int encoder_type; + bool inuse; + bool use_bypass_clk; + bool side_by_side; + struct dpu_soc *dpu; +}; + +static inline u32 dpu_fg_read(struct dpu_framegen *fg, unsigned int offset) +{ + return readl(fg->base + offset); +} + +static inline void dpu_fg_write(struct dpu_framegen *fg, + unsigned int offset, u32 value) +{ + writel(value, fg->base + offset); +} + +void framegen_enable(struct dpu_framegen *fg) +{ + dpu_fg_write(fg, FGENABLE, FGEN); +} +EXPORT_SYMBOL_GPL(framegen_enable); + +void framegen_disable(struct dpu_framegen *fg) +{ + dpu_fg_write(fg, FGENABLE, 0); +} +EXPORT_SYMBOL_GPL(framegen_disable); + +void framegen_enable_pixel_link(struct dpu_framegen *fg) +{ + struct dpu_soc *dpu = fg->dpu; + const struct dpu_data *data = dpu->data; + + if (!(data->has_dual_ldb && fg->encoder_type == DRM_MODE_ENCODER_LVDS)) + dpu_pxlink_set_mst_enable(fg->dpu, fg->id, true); + + if (fg->encoder_type == DRM_MODE_ENCODER_DPI) { + dpu_pxlink_set_mst_valid(fg->dpu, fg->id, true); + dpu_pxlink_set_sync_ctrl(fg->dpu, fg->id, true); + } +} +EXPORT_SYMBOL_GPL(framegen_enable_pixel_link); + +void framegen_disable_pixel_link(struct dpu_framegen *fg) +{ + struct dpu_soc *dpu = fg->dpu; + const struct dpu_data *data = dpu->data; + + if (fg->encoder_type == DRM_MODE_ENCODER_DPI) { + dpu_pxlink_set_mst_valid(fg->dpu, fg->id, false); + dpu_pxlink_set_sync_ctrl(fg->dpu, fg->id, false); + } + + if (!(data->has_dual_ldb && fg->encoder_type == DRM_MODE_ENCODER_LVDS)) + dpu_pxlink_set_mst_enable(fg->dpu, fg->id, false); +} +EXPORT_SYMBOL_GPL(framegen_disable_pixel_link); + +void framegen_shdtokgen(struct dpu_framegen *fg) +{ + dpu_fg_write(fg, FGSLR, SHDTOKGEN); +} +EXPORT_SYMBOL_GPL(framegen_shdtokgen); + +void framegen_syncmode(struct dpu_framegen *fg, fgsyncmode_t mode) +{ + u32 val; + + val = dpu_fg_read(fg, FGSTCTRL); + val &= ~FGSYNCMODE_MASK; + val |= mode; + dpu_fg_write(fg, FGSTCTRL, val); + + dpu_pxlink_set_dc_sync_mode(fg->dpu, mode != FGSYNCMODE__OFF); +} +EXPORT_SYMBOL_GPL(framegen_syncmode); + +void framegen_cfg_videomode(struct dpu_framegen *fg, struct drm_display_mode *m, + bool side_by_side, unsigned int encoder_type) +{ + struct dpu_soc *dpu = fg->dpu; + u32 hact, htotal, hsync, hsbp; + u32 vact, vtotal, vsync, vsbp; + u32 kick_row, kick_col; + u32 val; + unsigned long disp_clock_rate, pll_clock_rate = 0; + int div = 0; + + fg->side_by_side = side_by_side; + fg->encoder_type = encoder_type; + + hact = m->crtc_hdisplay; + htotal = m->crtc_htotal; + hsync = m->crtc_hsync_end - m->crtc_hsync_start; + hsbp = m->crtc_htotal - m->crtc_hsync_start; + + if (side_by_side) { + hact /= 2; + htotal /= 2; + hsync /= 2; + hsbp /= 2; + } + + vact = m->crtc_vdisplay; + vtotal = m->crtc_vtotal; + vsync = m->crtc_vsync_end - m->crtc_vsync_start; + vsbp = m->crtc_vtotal - m->crtc_vsync_start; + + /* video mode */ + dpu_fg_write(fg, HTCFG1, HACT(hact) | HTOTAL(htotal)); + dpu_fg_write(fg, HTCFG2, HSYNC(hsync) | HSBP(hsbp) | HSEN); + dpu_fg_write(fg, VTCFG1, VACT(vact) | VTOTAL(vtotal)); + dpu_fg_write(fg, VTCFG2, VSYNC(vsync) | VSBP(vsbp) | VSEN); + + kick_col = hact + 1; + kick_row = vact; + /* + * FrameGen as slave needs to be kicked later for + * one line comparing to the master. + */ + if (side_by_side && framegen_is_slave(fg)) + kick_row++; + + /* pkickconfig */ + dpu_fg_write(fg, PKICKCONFIG, COL(kick_col) | ROW(kick_row) | EN); + + /* skikconfig */ + dpu_fg_write(fg, SKICKCONFIG, COL(kick_col) | ROW(kick_row) | EN); + + /* primary and secondary area position config */ + dpu_fg_write(fg, PACFG, STARTX(0) | STARTY(0)); + dpu_fg_write(fg, SACFG, STARTX(0) | STARTY(0)); + + /* alpha */ + val = dpu_fg_read(fg, FGINCTRL); + val &= ~(ENPRIMALPHA | ENSECALPHA); + dpu_fg_write(fg, FGINCTRL, val); + + val = dpu_fg_read(fg, FGINCTRLPANIC); + val &= ~(ENPRIMALPHA | ENSECALPHA); + dpu_fg_write(fg, FGINCTRLPANIC, val); + + /* constant color */ + dpu_fg_write(fg, FGCCR, 0); + + disp_clock_rate = m->crtc_clock * 1000; + + if (encoder_type == DRM_MODE_ENCODER_TMDS) { + clk_set_parent(fg->clk_disp, fg->clk_bypass); + if (side_by_side) { + dpu_pxlink_set_mst_addr(dpu, fg->id, fg->id ? 2 : 1); + clk_set_rate(fg->clk_bypass, disp_clock_rate / 2); + clk_set_rate(fg->clk_disp, disp_clock_rate / 2); + } else { + dpu_pxlink_set_mst_addr(dpu, fg->id, 1); + clk_set_rate(fg->clk_bypass, disp_clock_rate); + clk_set_rate(fg->clk_disp, disp_clock_rate); + } + + fg->use_bypass_clk = true; + } else { + dpu_pxlink_set_mst_addr(dpu, fg->id, + encoder_type == DRM_MODE_ENCODER_DPI ? 1 : 0); + + clk_set_parent(fg->clk_disp, fg->clk_pll); + + /* find an even divisor for PLL */ + do { + div += 2; + pll_clock_rate = disp_clock_rate * div; + } while (pll_clock_rate < PLL_MIN_FREQ_HZ); + + clk_set_rate(fg->clk_pll, pll_clock_rate); + clk_set_rate(fg->clk_disp, disp_clock_rate); + + fg->use_bypass_clk = false; + } +} +EXPORT_SYMBOL_GPL(framegen_cfg_videomode); + +void framegen_pkickconfig(struct dpu_framegen *fg, bool enable) +{ + u32 val; + + val = dpu_fg_read(fg, PKICKCONFIG); + if (enable) + val |= EN; + else + val &= ~EN; + dpu_fg_write(fg, PKICKCONFIG, val); +} +EXPORT_SYMBOL_GPL(framegen_pkickconfig); + +void framegen_syncmode_fixup(struct dpu_framegen *fg, bool enable) +{ + u32 val; + + val = dpu_fg_read(fg, SECSTATCONFIG); + if (enable) + val |= BIT(7); + else + val &= ~BIT(7); + dpu_fg_write(fg, SECSTATCONFIG, val); +} +EXPORT_SYMBOL_GPL(framegen_syncmode_fixup); + +void framegen_displaymode(struct dpu_framegen *fg, fgdm_t mode) +{ + u32 val; + + val = dpu_fg_read(fg, FGINCTRL); + val &= ~FGDM_MASK; + val |= mode; + dpu_fg_write(fg, FGINCTRL, val); +} +EXPORT_SYMBOL_GPL(framegen_displaymode); + +void framegen_panic_displaymode(struct dpu_framegen *fg, fgdm_t mode) +{ + u32 val; + + val = dpu_fg_read(fg, FGINCTRLPANIC); + val &= ~FGDM_MASK; + val |= mode; + dpu_fg_write(fg, FGINCTRLPANIC, val); +} +EXPORT_SYMBOL_GPL(framegen_panic_displaymode); + +void framegen_wait_done(struct dpu_framegen *fg, struct drm_display_mode *m) +{ + unsigned long timeout, pending_framedur_jiffies; + int frame_size = m->crtc_htotal * m->crtc_vtotal; + int dotclock, pending_framedur_ns; + u32 val; + + dotclock = clk_get_rate(fg->clk_disp) / KHZ; + if (dotclock == 0) { + /* fall back to display mode's clock */ + dotclock = m->crtc_clock; + } + + /* + * The SoC designer indicates that there are two pending frames + * to complete in the worst case. + * So, three pending frames are enough for sure. + */ + pending_framedur_ns = div_u64((u64) 3 * frame_size * 1000000, dotclock); + pending_framedur_jiffies = nsecs_to_jiffies(pending_framedur_ns); + if (pending_framedur_jiffies > (3 * HZ)) { + pending_framedur_jiffies = 3 * HZ; + + dev_warn(fg->dpu->dev, + "truncate FrameGen%d pending frame duration to 3sec\n", + fg->id); + } + timeout = jiffies + pending_framedur_jiffies; + + do { + val = dpu_fg_read(fg, FGENSTS); + } while ((val & ENSTS) && time_before(jiffies, timeout)); + + dev_dbg(fg->dpu->dev, "FrameGen%d pending frame duration is %ums\n", + fg->id, jiffies_to_msecs(pending_framedur_jiffies)); + + if (val & ENSTS) + dev_err(fg->dpu->dev, "failed to wait for FrameGen%d done\n", + fg->id); +} +EXPORT_SYMBOL_GPL(framegen_wait_done); + +static inline u32 framegen_frame_index(u32 stamp) +{ + return (stamp & FRAMEINDEX_MASK) >> FRAMEINDEX_SHIFT; +} + +static inline u32 framegen_line_index(u32 stamp) +{ + return (stamp & LINEINDEX_MASK) >> LINEINDEX_SHIFT; +} + +void framegen_read_timestamp(struct dpu_framegen *fg, + u32 *frame_index, u32 *line_index) +{ + u32 stamp; + + stamp = dpu_fg_read(fg, FGTIMESTAMP); + *frame_index = framegen_frame_index(stamp); + *line_index = framegen_line_index(stamp); +} +EXPORT_SYMBOL_GPL(framegen_read_timestamp); + +void framegen_wait_for_frame_counter_moving(struct dpu_framegen *fg) +{ + u32 frame_index, line_index, last_frame_index; + unsigned long timeout = jiffies + msecs_to_jiffies(100); + + framegen_read_timestamp(fg, &frame_index, &line_index); + do { + last_frame_index = frame_index; + framegen_read_timestamp(fg, &frame_index, &line_index); + } while (last_frame_index == frame_index && + time_before(jiffies, timeout)); + + if (last_frame_index == frame_index) + dev_err(fg->dpu->dev, + "failed to wait for FrameGen%d frame counter moving\n", + fg->id); + else + dev_dbg(fg->dpu->dev, + "FrameGen%d frame counter moves - last %u, curr %d\n", + fg->id, last_frame_index, frame_index); +} +EXPORT_SYMBOL_GPL(framegen_wait_for_frame_counter_moving); + +bool framegen_secondary_requests_to_read_empty_fifo(struct dpu_framegen *fg) +{ + u32 val; + bool empty; + + val = dpu_fg_read(fg, FGCHSTAT); + + empty = !!(val & SFIFOEMPTY); + + if (empty) + dev_dbg(fg->dpu->dev, + "FrameGen%d secondary requests to read empty FIFO\n", + fg->id); + + return empty; +} +EXPORT_SYMBOL_GPL(framegen_secondary_requests_to_read_empty_fifo); + +void framegen_secondary_clear_channel_status(struct dpu_framegen *fg) +{ + dpu_fg_write(fg, FGCHSTATCLR, CLRSECSTAT); +} +EXPORT_SYMBOL_GPL(framegen_secondary_clear_channel_status); + +bool framegen_secondary_is_syncup(struct dpu_framegen *fg) +{ + u32 val = dpu_fg_read(fg, FGCHSTAT); + + return val & SECSYNCSTAT; +} +EXPORT_SYMBOL_GPL(framegen_secondary_is_syncup); + +void framegen_wait_for_secondary_syncup(struct dpu_framegen *fg) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(100); + bool syncup; + + do { + syncup = framegen_secondary_is_syncup(fg); + } while (!syncup && time_before(jiffies, timeout)); + + if (syncup) + dev_dbg(fg->dpu->dev, "FrameGen%d secondary syncup\n", fg->id); + else + dev_err(fg->dpu->dev, + "failed to wait for FrameGen%d secondary syncup\n", + fg->id); +} +EXPORT_SYMBOL_GPL(framegen_wait_for_secondary_syncup); + +void framegen_enable_clock(struct dpu_framegen *fg) +{ + if (!fg->use_bypass_clk) + clk_prepare_enable(fg->clk_pll); + clk_prepare_enable(fg->clk_disp); + clk_prepare_enable(fg->clk_disp_lpcg); +} +EXPORT_SYMBOL_GPL(framegen_enable_clock); + +void framegen_disable_clock(struct dpu_framegen *fg) +{ + if (!fg->use_bypass_clk) + clk_disable_unprepare(fg->clk_pll); + clk_disable_unprepare(fg->clk_disp); + clk_disable_unprepare(fg->clk_disp_lpcg); +} +EXPORT_SYMBOL_GPL(framegen_disable_clock); + +bool framegen_is_master(struct dpu_framegen *fg) +{ + const struct dpu_data *data = fg->dpu->data; + + return fg->id == data->master_stream_id; +} +EXPORT_SYMBOL_GPL(framegen_is_master); + +bool framegen_is_slave(struct dpu_framegen *fg) +{ + return !framegen_is_master(fg); +} +EXPORT_SYMBOL_GPL(framegen_is_slave); + +struct dpu_framegen *dpu_fg_get(struct dpu_soc *dpu, int id) +{ + struct dpu_framegen *fg; + int i; + + for (i = 0; i < ARRAY_SIZE(fg_ids); i++) + if (fg_ids[i] == id) + break; + + if (i == ARRAY_SIZE(fg_ids)) + return ERR_PTR(-EINVAL); + + fg = dpu->fg_priv[i]; + + mutex_lock(&fg->mutex); + + if (fg->inuse) { + mutex_unlock(&fg->mutex); + return ERR_PTR(-EBUSY); + } + + fg->inuse = true; + + mutex_unlock(&fg->mutex); + + return fg; +} +EXPORT_SYMBOL_GPL(dpu_fg_get); + +void dpu_fg_put(struct dpu_framegen *fg) +{ + mutex_lock(&fg->mutex); + + fg->inuse = false; + + mutex_unlock(&fg->mutex); +} +EXPORT_SYMBOL_GPL(dpu_fg_put); + +struct dpu_framegen *dpu_aux_fg_peek(struct dpu_framegen *fg) +{ + return fg->dpu->fg_priv[fg->id ^ 1]; +} +EXPORT_SYMBOL_GPL(dpu_aux_fg_peek); + +void _dpu_fg_init(struct dpu_soc *dpu, unsigned int id) +{ + struct dpu_framegen *fg; + int i; + + for (i = 0; i < ARRAY_SIZE(fg_ids); i++) + if (fg_ids[i] == id) + break; + + if (WARN_ON(i == ARRAY_SIZE(fg_ids))) + return; + + fg = dpu->fg_priv[i]; + + framegen_syncmode(fg, FGSYNCMODE__OFF); +} + +int dpu_fg_init(struct dpu_soc *dpu, unsigned int id, + unsigned long unused, unsigned long base) +{ + struct dpu_framegen *fg; + + fg = devm_kzalloc(dpu->dev, sizeof(*fg), GFP_KERNEL); + if (!fg) + return -ENOMEM; + + dpu->fg_priv[id] = fg; + + fg->base = devm_ioremap(dpu->dev, base, SZ_256); + if (!fg->base) + return -ENOMEM; + + fg->clk_pll = devm_clk_get(dpu->dev, id ? "pll1" : "pll0"); + if (IS_ERR(fg->clk_pll)) + return PTR_ERR(fg->clk_pll); + + fg->clk_bypass = devm_clk_get(dpu->dev, "bypass0"); + if (IS_ERR(fg->clk_bypass)) + return PTR_ERR(fg->clk_bypass); + + fg->clk_disp = devm_clk_get(dpu->dev, id ? "disp1" : "disp0"); + if (IS_ERR(fg->clk_disp)) + return PTR_ERR(fg->clk_disp); + + fg->clk_disp_lpcg = devm_clk_get(dpu->dev, id ? "disp1_lpcg" : "disp0_lpcg"); + if (IS_ERR(fg->clk_disp_lpcg)) + return PTR_ERR(fg->clk_disp_lpcg); + + fg->dpu = dpu; + fg->id = id; + mutex_init(&fg->mutex); + + _dpu_fg_init(dpu, id); + + return 0; +} diff --git a/drivers/gpu/imx/dpu/dpu-hscaler.c b/drivers/gpu/imx/dpu/dpu-hscaler.c new file mode 100644 index 000000000000..9e69c619bd3f --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-hscaler.c @@ -0,0 +1,386 @@ +/* + * Copyright 2017-2019 NXP + * + * 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 <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <video/dpu.h> +#include "dpu-prv.h" + +#define PIXENGCFG_DYNAMIC 0x8 +#define PIXENGCFG_DYNAMIC_SRC_SEL_MASK 0x3F + +#define SETUP1 0xC +#define SCALE_FACTOR_MASK 0xFFFFF +#define SCALE_FACTOR(n) ((n) & 0xFFFFF) +#define SETUP2 0x10 +#define PHASE_OFFSET_MASK 0x1FFFFF +#define PHASE_OFFSET(n) ((n) & 0x1FFFFF) +#define CONTROL 0x14 +#define OUTPUT_SIZE_MASK 0x3FFF0000 +#define OUTPUT_SIZE(n) ((((n) - 1) << 16) & OUTPUT_SIZE_MASK) +#define FILTER_MODE 0x100 +#define SCALE_MODE 0x10 +#define MODE 0x1 + +static const hs_src_sel_t src_sels[3][6] = { + { + HS_SRC_SEL__DISABLE, + HS_SRC_SEL__FETCHDECODE0, + HS_SRC_SEL__MATRIX4, + HS_SRC_SEL__VSCALER4, + }, { + HS_SRC_SEL__DISABLE, + HS_SRC_SEL__FETCHDECODE1, + HS_SRC_SEL__MATRIX5, + HS_SRC_SEL__VSCALER5, + }, { + HS_SRC_SEL__DISABLE, + HS_SRC_SEL__MATRIX9, + HS_SRC_SEL__VSCALER9, + HS_SRC_SEL__FILTER9, + }, +}; + +struct dpu_hscaler { + void __iomem *pec_base; + void __iomem *base; + struct mutex mutex; + int id; + bool inuse; + struct dpu_soc *dpu; + /* see DPU_PLANE_SRC_xxx */ + unsigned int stream_id; +}; + +static inline u32 dpu_pec_hs_read(struct dpu_hscaler *hs, + unsigned int offset) +{ + return readl(hs->pec_base + offset); +} + +static inline void dpu_pec_hs_write(struct dpu_hscaler *hs, + unsigned int offset, u32 value) +{ + writel(value, hs->pec_base + offset); +} + +static inline u32 dpu_hs_read(struct dpu_hscaler *hs, unsigned int offset) +{ + return readl(hs->base + offset); +} + +static inline void dpu_hs_write(struct dpu_hscaler *hs, + unsigned int offset, u32 value) +{ + writel(value, hs->base + offset); +} + +int hscaler_pixengcfg_dynamic_src_sel(struct dpu_hscaler *hs, hs_src_sel_t src) +{ + struct dpu_soc *dpu = hs->dpu; + const unsigned int hs_id_array[] = {4, 5, 9}; + int i, j; + u32 val; + + for (i = 0; i < ARRAY_SIZE(hs_id_array); i++) + if (hs_id_array[i] == hs->id) + break; + + if (WARN_ON(i == ARRAY_SIZE(hs_id_array))) + return -EINVAL; + + mutex_lock(&hs->mutex); + for (j = 0; j < ARRAY_SIZE(src_sels[0]); j++) { + if (src_sels[i][j] == src) { + val = dpu_pec_hs_read(hs, PIXENGCFG_DYNAMIC); + val &= ~PIXENGCFG_DYNAMIC_SRC_SEL_MASK; + val |= src; + dpu_pec_hs_write(hs, PIXENGCFG_DYNAMIC, val); + mutex_unlock(&hs->mutex); + return 0; + } + } + mutex_unlock(&hs->mutex); + + dev_err(dpu->dev, "Invalid source for HScaler%d\n", hs->id); + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(hscaler_pixengcfg_dynamic_src_sel); + +void hscaler_pixengcfg_clken(struct dpu_hscaler *hs, pixengcfg_clken_t clken) +{ + u32 val; + + mutex_lock(&hs->mutex); + val = dpu_pec_hs_read(hs, PIXENGCFG_DYNAMIC); + val &= ~CLKEN_MASK; + val |= clken << CLKEN_MASK_SHIFT; + dpu_pec_hs_write(hs, PIXENGCFG_DYNAMIC, val); + mutex_unlock(&hs->mutex); +} +EXPORT_SYMBOL_GPL(hscaler_pixengcfg_clken); + +void hscaler_shden(struct dpu_hscaler *hs, bool enable) +{ + u32 val; + + mutex_lock(&hs->mutex); + val = dpu_hs_read(hs, STATICCONTROL); + if (enable) + val |= SHDEN; + else + val &= ~SHDEN; + dpu_hs_write(hs, STATICCONTROL, val); + mutex_unlock(&hs->mutex); +} +EXPORT_SYMBOL_GPL(hscaler_shden); + +void hscaler_setup1(struct dpu_hscaler *hs, u32 src, u32 dst) +{ + struct dpu_soc *dpu = hs->dpu; + u32 scale_factor; + u64 tmp64; + + if (src == dst) { + scale_factor = 0x80000; + } else { + if (src > dst) { + tmp64 = (u64)((u64)dst * 0x80000); + do_div(tmp64, src); + + } else { + tmp64 = (u64)((u64)src * 0x80000); + do_div(tmp64, dst); + } + scale_factor = (u32)tmp64; + } + + WARN_ON(scale_factor > 0x80000); + + mutex_lock(&hs->mutex); + dpu_hs_write(hs, SETUP1, SCALE_FACTOR(scale_factor)); + mutex_unlock(&hs->mutex); + + dev_dbg(dpu->dev, "Hscaler%d scale factor 0x%08x\n", + hs->id, scale_factor); +} +EXPORT_SYMBOL_GPL(hscaler_setup1); + +void hscaler_setup2(struct dpu_hscaler *hs, u32 phase_offset) +{ + mutex_lock(&hs->mutex); + dpu_hs_write(hs, SETUP2, PHASE_OFFSET(phase_offset)); + mutex_unlock(&hs->mutex); +} +EXPORT_SYMBOL_GPL(hscaler_setup2); + +void hscaler_output_size(struct dpu_hscaler *hs, u32 line_num) +{ + u32 val; + + mutex_lock(&hs->mutex); + val = dpu_hs_read(hs, CONTROL); + val &= ~OUTPUT_SIZE_MASK; + val |= OUTPUT_SIZE(line_num); + dpu_hs_write(hs, CONTROL, val); + mutex_unlock(&hs->mutex); +} +EXPORT_SYMBOL_GPL(hscaler_output_size); + +void hscaler_filter_mode(struct dpu_hscaler *hs, scaler_filter_mode_t m) +{ + u32 val; + + mutex_lock(&hs->mutex); + val = dpu_hs_read(hs, CONTROL); + val &= ~FILTER_MODE; + val |= m; + dpu_hs_write(hs, CONTROL, val); + mutex_unlock(&hs->mutex); +} +EXPORT_SYMBOL_GPL(hscaler_filter_mode); + +void hscaler_scale_mode(struct dpu_hscaler *hs, scaler_scale_mode_t m) +{ + u32 val; + + mutex_lock(&hs->mutex); + val = dpu_hs_read(hs, CONTROL); + val &= ~SCALE_MODE; + val |= m; + dpu_hs_write(hs, CONTROL, val); + mutex_unlock(&hs->mutex); +} +EXPORT_SYMBOL_GPL(hscaler_scale_mode); + +void hscaler_mode(struct dpu_hscaler *hs, scaler_mode_t m) +{ + u32 val; + + mutex_lock(&hs->mutex); + val = dpu_hs_read(hs, CONTROL); + val &= ~MODE; + val |= m; + dpu_hs_write(hs, CONTROL, val); + mutex_unlock(&hs->mutex); +} +EXPORT_SYMBOL_GPL(hscaler_mode); + +bool hscaler_is_enabled(struct dpu_hscaler *hs) +{ + u32 val; + + mutex_lock(&hs->mutex); + val = dpu_hs_read(hs, CONTROL); + mutex_unlock(&hs->mutex); + + return (val & MODE) == SCALER_ACTIVE; +} +EXPORT_SYMBOL_GPL(hscaler_is_enabled); + +dpu_block_id_t hscaler_get_block_id(struct dpu_hscaler *hs) +{ + switch (hs->id) { + case 4: + return ID_HSCALER4; + case 5: + return ID_HSCALER5; + case 9: + return ID_HSCALER9; + default: + WARN_ON(1); + } + + return ID_NONE; +} +EXPORT_SYMBOL_GPL(hscaler_get_block_id); + +unsigned int hscaler_get_stream_id(struct dpu_hscaler *hs) +{ + return hs->stream_id; +} +EXPORT_SYMBOL_GPL(hscaler_get_stream_id); + +void hscaler_set_stream_id(struct dpu_hscaler *hs, unsigned int id) +{ + switch (id) { + case DPU_PLANE_SRC_TO_DISP_STREAM0: + case DPU_PLANE_SRC_TO_DISP_STREAM1: + case DPU_PLANE_SRC_DISABLED: + hs->stream_id = id; + break; + default: + WARN_ON(1); + } +} +EXPORT_SYMBOL_GPL(hscaler_set_stream_id); + +struct dpu_hscaler *dpu_hs_get(struct dpu_soc *dpu, int id) +{ + struct dpu_hscaler *hs; + int i; + + for (i = 0; i < ARRAY_SIZE(hs_ids); i++) + if (hs_ids[i] == id) + break; + + if (i == ARRAY_SIZE(hs_ids)) + return ERR_PTR(-EINVAL); + + hs = dpu->hs_priv[i]; + + mutex_lock(&hs->mutex); + + if (hs->inuse) { + mutex_unlock(&hs->mutex); + return ERR_PTR(-EBUSY); + } + + hs->inuse = true; + + mutex_unlock(&hs->mutex); + + return hs; +} +EXPORT_SYMBOL_GPL(dpu_hs_get); + +void dpu_hs_put(struct dpu_hscaler *hs) +{ + mutex_lock(&hs->mutex); + + hs->inuse = false; + + mutex_unlock(&hs->mutex); +} +EXPORT_SYMBOL_GPL(dpu_hs_put); + +void _dpu_hs_init(struct dpu_soc *dpu, unsigned int id) +{ + struct dpu_hscaler *hs; + int i; + + for (i = 0; i < ARRAY_SIZE(hs_ids); i++) + if (hs_ids[i] == id) + break; + + if (WARN_ON(i == ARRAY_SIZE(hs_ids))) + return; + + hs = dpu->hs_priv[i]; + + hscaler_shden(hs, true); + hscaler_setup2(hs, 0); + hscaler_pixengcfg_dynamic_src_sel(hs, HS_SRC_SEL__DISABLE); +} + +int dpu_hs_init(struct dpu_soc *dpu, unsigned int id, + unsigned long pec_base, unsigned long base) +{ + struct dpu_hscaler *hs; + int i; + + hs = devm_kzalloc(dpu->dev, sizeof(*hs), GFP_KERNEL); + if (!hs) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(hs_ids); i++) + if (hs_ids[i] == id) + break; + + if (i == ARRAY_SIZE(hs_ids)) + return -EINVAL; + + dpu->hs_priv[i] = hs; + + hs->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_8); + if (!hs->pec_base) + return -ENOMEM; + + hs->base = devm_ioremap(dpu->dev, base, SZ_1K); + if (!hs->base) + return -ENOMEM; + + hs->dpu = dpu; + hs->id = id; + + mutex_init(&hs->mutex); + + _dpu_hs_init(dpu, id); + + return 0; +} diff --git a/drivers/gpu/imx/dpu/dpu-layerblend.c b/drivers/gpu/imx/dpu/dpu-layerblend.c new file mode 100644 index 000000000000..c19fcbdb169b --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-layerblend.c @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2019 NXP + * + * 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 <drm/drm_blend.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <video/dpu.h> +#include "dpu-prv.h" + +#define PIXENGCFG_DYNAMIC 0x8 +#define PIXENGCFG_DYNAMIC_PRIM_SEL_MASK 0x3F +#define PIXENGCFG_DYNAMIC_SEC_SEL_MASK 0x3F00 +#define PIXENGCFG_DYNAMIC_SEC_SEL_SHIFT 8 + +static const lb_prim_sel_t prim_sels[] = { + LB_PRIM_SEL__DISABLE, + LB_PRIM_SEL__BLITBLEND9, + LB_PRIM_SEL__CONSTFRAME0, + LB_PRIM_SEL__CONSTFRAME1, + LB_PRIM_SEL__CONSTFRAME4, + LB_PRIM_SEL__CONSTFRAME5, + LB_PRIM_SEL__MATRIX4, + LB_PRIM_SEL__HSCALER4, + LB_PRIM_SEL__VSCALER4, + LB_PRIM_SEL__MATRIX5, + LB_PRIM_SEL__HSCALER5, + LB_PRIM_SEL__VSCALER5, + LB_PRIM_SEL__LAYERBLEND0, + LB_PRIM_SEL__LAYERBLEND1, + LB_PRIM_SEL__LAYERBLEND2, + LB_PRIM_SEL__LAYERBLEND3, +}; + +#define PIXENGCFG_STATUS 0xC +#define SHDTOKSEL (0x3 << 3) +#define SHDTOKSEL_SHIFT 3 +#define SHDLDSEL (0x3 << 1) +#define SHDLDSEL_SHIFT 1 +#define CONTROL 0xC +#define OPERATION_MODE_MASK BIT(0) +#define BLENDCONTROL 0x10 +#define ALPHA(a) (((a) & 0xFF) << 16) +#define PRIM_C_BLD_FUNC__ONE_MINUS_CONST_ALPHA 0x7 +#define PRIM_C_BLD_FUNC__ONE_MINUS_SEC_ALPHA 0x5 +#define PRIM_C_BLD_FUNC__ZERO 0x0 +#define SEC_C_BLD_FUNC__CONST_ALPHA (0x6 << 4) +#define SEC_C_BLD_FUNC__SEC_ALPHA (0x4 << 4) +#define PRIM_A_BLD_FUNC__ZERO (0x0 << 8) +#define SEC_A_BLD_FUNC__ZERO (0x0 << 12) +#define POSITION 0x14 +#define XPOS(x) ((x) & 0x7FFF) +#define YPOS(y) (((y) & 0x7FFF) << 16) +#define PRIMCONTROLWORD 0x18 +#define SECCONTROLWORD 0x1C + +struct dpu_layerblend { + void __iomem *pec_base; + void __iomem *base; + struct mutex mutex; + int id; + bool inuse; + struct dpu_soc *dpu; +}; + +static inline u32 dpu_pec_lb_read(struct dpu_layerblend *lb, + unsigned int offset) +{ + return readl(lb->pec_base + offset); +} + +static inline void dpu_pec_lb_write(struct dpu_layerblend *lb, + unsigned int offset, u32 value) +{ + writel(value, lb->pec_base + offset); +} + +static inline u32 dpu_lb_read(struct dpu_layerblend *lb, unsigned int offset) +{ + return readl(lb->base + offset); +} + +static inline void dpu_lb_write(struct dpu_layerblend *lb, + unsigned int offset, u32 value) +{ + writel(value, lb->base + offset); +} + +int layerblend_pixengcfg_dynamic_prim_sel(struct dpu_layerblend *lb, + lb_prim_sel_t prim) +{ + struct dpu_soc *dpu = lb->dpu; + int fixed_sels_num = ARRAY_SIZE(prim_sels) - 4; + int i; + u32 val; + + mutex_lock(&lb->mutex); + for (i = 0; i < fixed_sels_num + lb->id; i++) { + if (prim_sels[i] == prim) { + val = dpu_pec_lb_read(lb, PIXENGCFG_DYNAMIC); + val &= ~PIXENGCFG_DYNAMIC_PRIM_SEL_MASK; + val |= prim; + dpu_pec_lb_write(lb, PIXENGCFG_DYNAMIC, val); + mutex_unlock(&lb->mutex); + return 0; + } + } + mutex_unlock(&lb->mutex); + + dev_err(dpu->dev, "Invalid primary source for LayerBlend%d\n", lb->id); + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(layerblend_pixengcfg_dynamic_prim_sel); + +void layerblend_pixengcfg_dynamic_sec_sel(struct dpu_layerblend *lb, + lb_sec_sel_t sec) +{ + u32 val; + + mutex_lock(&lb->mutex); + val = dpu_pec_lb_read(lb, PIXENGCFG_DYNAMIC); + val &= ~PIXENGCFG_DYNAMIC_SEC_SEL_MASK; + val |= sec << PIXENGCFG_DYNAMIC_SEC_SEL_SHIFT; + dpu_pec_lb_write(lb, PIXENGCFG_DYNAMIC, val); + mutex_unlock(&lb->mutex); +} +EXPORT_SYMBOL_GPL(layerblend_pixengcfg_dynamic_sec_sel); + +void layerblend_pixengcfg_clken(struct dpu_layerblend *lb, + pixengcfg_clken_t clken) +{ + u32 val; + + mutex_lock(&lb->mutex); + val = dpu_pec_lb_read(lb, PIXENGCFG_DYNAMIC); + val &= ~CLKEN_MASK; + val |= clken << CLKEN_MASK_SHIFT; + dpu_pec_lb_write(lb, PIXENGCFG_DYNAMIC, val); + mutex_unlock(&lb->mutex); +} +EXPORT_SYMBOL_GPL(layerblend_pixengcfg_clken); + +void layerblend_shden(struct dpu_layerblend *lb, bool enable) +{ + u32 val; + + mutex_lock(&lb->mutex); + val = dpu_lb_read(lb, STATICCONTROL); + if (enable) + val |= SHDEN; + else + val &= ~SHDEN; + dpu_lb_write(lb, STATICCONTROL, val); + mutex_unlock(&lb->mutex); +} +EXPORT_SYMBOL_GPL(layerblend_shden); + +void layerblend_shdtoksel(struct dpu_layerblend *lb, lb_shadow_sel_t sel) +{ + u32 val; + + mutex_lock(&lb->mutex); + val = dpu_lb_read(lb, STATICCONTROL); + val &= ~SHDTOKSEL; + val |= (sel << SHDTOKSEL_SHIFT); + dpu_lb_write(lb, STATICCONTROL, val); + mutex_unlock(&lb->mutex); +} +EXPORT_SYMBOL_GPL(layerblend_shdtoksel); + +void layerblend_shdldsel(struct dpu_layerblend *lb, lb_shadow_sel_t sel) +{ + u32 val; + + mutex_lock(&lb->mutex); + val = dpu_lb_read(lb, STATICCONTROL); + val &= ~SHDLDSEL; + val |= (sel << SHDLDSEL_SHIFT); + dpu_lb_write(lb, STATICCONTROL, val); + mutex_unlock(&lb->mutex); +} +EXPORT_SYMBOL_GPL(layerblend_shdldsel); + +void layerblend_control(struct dpu_layerblend *lb, lb_mode_t mode) +{ + u32 val; + + mutex_lock(&lb->mutex); + val = dpu_lb_read(lb, CONTROL); + val &= ~OPERATION_MODE_MASK; + val |= mode; + dpu_lb_write(lb, CONTROL, val); + mutex_unlock(&lb->mutex); +} +EXPORT_SYMBOL_GPL(layerblend_control); + +void layerblend_blendcontrol(struct dpu_layerblend *lb, unsigned int zpos, + unsigned int pixel_blend_mode, u16 alpha) +{ + u32 val = PRIM_A_BLD_FUNC__ZERO | SEC_A_BLD_FUNC__ZERO; + + if (zpos == 0) { + val |= PRIM_C_BLD_FUNC__ZERO | SEC_C_BLD_FUNC__CONST_ALPHA; + alpha = DRM_BLEND_ALPHA_OPAQUE; + } else { + switch (pixel_blend_mode) { + case DRM_MODE_BLEND_PIXEL_NONE: + val |= PRIM_C_BLD_FUNC__ONE_MINUS_CONST_ALPHA | + SEC_C_BLD_FUNC__CONST_ALPHA; + break; + case DRM_MODE_BLEND_PREMULTI: + val |= PRIM_C_BLD_FUNC__ONE_MINUS_SEC_ALPHA | + SEC_C_BLD_FUNC__CONST_ALPHA; + break; + case DRM_MODE_BLEND_COVERAGE: + val |= PRIM_C_BLD_FUNC__ONE_MINUS_SEC_ALPHA | + SEC_C_BLD_FUNC__SEC_ALPHA; + break; + default: + break; + } + } + + val |= ALPHA(alpha >> 8); + + mutex_lock(&lb->mutex); + dpu_lb_write(lb, BLENDCONTROL, val); + mutex_unlock(&lb->mutex); +} +EXPORT_SYMBOL_GPL(layerblend_blendcontrol); + +void layerblend_position(struct dpu_layerblend *lb, int x, int y) +{ + mutex_lock(&lb->mutex); + dpu_lb_write(lb, POSITION, XPOS(x) | YPOS(y)); + mutex_unlock(&lb->mutex); +} +EXPORT_SYMBOL_GPL(layerblend_position); + +struct dpu_layerblend *dpu_lb_get(struct dpu_soc *dpu, int id) +{ + struct dpu_layerblend *lb; + int i; + + for (i = 0; i < ARRAY_SIZE(lb_ids); i++) + if (lb_ids[i] == id) + break; + + if (i == ARRAY_SIZE(lb_ids)) + return ERR_PTR(-EINVAL); + + lb = dpu->lb_priv[i]; + + mutex_lock(&lb->mutex); + + if (lb->inuse) { + mutex_unlock(&lb->mutex); + return ERR_PTR(-EBUSY); + } + + lb->inuse = true; + + mutex_unlock(&lb->mutex); + + return lb; +} +EXPORT_SYMBOL_GPL(dpu_lb_get); + +void dpu_lb_put(struct dpu_layerblend *lb) +{ + mutex_lock(&lb->mutex); + + lb->inuse = false; + + mutex_unlock(&lb->mutex); +} +EXPORT_SYMBOL_GPL(dpu_lb_put); + +void _dpu_lb_init(struct dpu_soc *dpu, unsigned int id) +{ + struct dpu_layerblend *lb; + int i; + + for (i = 0; i < ARRAY_SIZE(lb_ids); i++) + if (lb_ids[i] == id) + break; + + if (WARN_ON(i == ARRAY_SIZE(lb_ids))) + return; + + lb = dpu->lb_priv[i]; + + layerblend_pixengcfg_dynamic_prim_sel(lb, LB_PRIM_SEL__DISABLE); + layerblend_pixengcfg_dynamic_sec_sel(lb, LB_SEC_SEL__DISABLE); + layerblend_pixengcfg_clken(lb, CLKEN__AUTOMATIC); + layerblend_shdldsel(lb, BOTH); + layerblend_shdtoksel(lb, BOTH); + layerblend_shden(lb, true); +} + +int dpu_lb_init(struct dpu_soc *dpu, unsigned int id, + unsigned long pec_base, unsigned long base) +{ + struct dpu_layerblend *lb; + int ret; + + lb = devm_kzalloc(dpu->dev, sizeof(*lb), GFP_KERNEL); + if (!lb) + return -ENOMEM; + + dpu->lb_priv[id] = lb; + + lb->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_16); + if (!lb->pec_base) + return -ENOMEM; + + lb->base = devm_ioremap(dpu->dev, base, SZ_32); + if (!lb->base) + return -ENOMEM; + + lb->dpu = dpu; + lb->id = id; + mutex_init(&lb->mutex); + + ret = layerblend_pixengcfg_dynamic_prim_sel(lb, LB_PRIM_SEL__DISABLE); + if (ret < 0) + return ret; + + _dpu_lb_init(dpu, id); + + return 0; +} diff --git a/drivers/gpu/imx/dpu/dpu-prv.h b/drivers/gpu/imx/dpu/dpu-prv.h new file mode 100644 index 000000000000..19aac30a7fdc --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-prv.h @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2020,2022 NXP + * + * 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. + */ +#ifndef __DPU_PRV_H__ +#define __DPU_PRV_H__ + +#include <linux/firmware/imx/sci.h> +#include <drm/drm_fourcc.h> +#include <video/dpu.h> + +#define STATICCONTROL 0x8 +#define SHDLDREQSTICKY(lm) (((lm) & 0xFF) << 24) +#define SHDLDREQSTICKY_MASK (0xFF << 24) +#define BASEADDRESSAUTOUPDATE(lm) (((lm) & 0xFF) << 16) +#define BASEADDRESSAUTOUPDATE_MASK (0xFF << 16) +#define SHDEN BIT(0) +#define BURSTBUFFERMANAGEMENT 0xC +#define SETNUMBUFFERS(n) ((n) & 0xFF) +#define SETBURSTLENGTH(n) (((n) & 0x1F) << 8) +#define SETBURSTLENGTH_MASK 0x1F00 +#define LINEMODE_MASK 0x80000000U +#define LINEMODE_SHIFT 31U +enum linemode { + /* + * Mandatory setting for operation in the Display Controller. + * Works also for Blit Engine with marginal performance impact. + */ + LINEMODE__DISPLAY = 0, + /* Recommended setting for operation in the Blit Engine. */ + LINEMODE__BLIT = 1 << LINEMODE_SHIFT, +}; + +#define BITSPERPIXEL(bpp) (((bpp) & 0x3F) << 16) +#define STRIDE(n) (((n) - 1) & 0xFFFF) +#define LINEWIDTH(w) (((w) - 1) & 0x3FFF) +#define LINECOUNT(h) ((((h) - 1) & 0x3FFF) << 16) +#define ITUFORMAT BIT(31) +#define R_BITS(n) (((n) & 0xF) << 24) +#define G_BITS(n) (((n) & 0xF) << 16) +#define B_BITS(n) (((n) & 0xF) << 8) +#define A_BITS(n) ((n) & 0xF) +#define R_SHIFT(n) (((n) & 0x1F) << 24) +#define G_SHIFT(n) (((n) & 0x1F) << 16) +#define B_SHIFT(n) (((n) & 0x1F) << 8) +#define A_SHIFT(n) ((n) & 0x1F) +#define Y_BITS(n) R_BITS(n) +#define Y_BITS_MASK 0xF000000 +#define U_BITS(n) G_BITS(n) +#define U_BITS_MASK 0xF0000 +#define V_BITS(n) B_BITS(n) +#define V_BITS_MASK 0xF00 +#define Y_SHIFT(n) R_SHIFT(n) +#define Y_SHIFT_MASK 0x1F000000 +#define U_SHIFT(n) G_SHIFT(n) +#define U_SHIFT_MASK 0x1F0000 +#define V_SHIFT(n) B_SHIFT(n) +#define V_SHIFT_MASK 0x1F00 +#define LAYERXOFFSET(x) ((x) & 0x7FFF) +#define LAYERYOFFSET(y) (((y) & 0x7FFF) << 16) +#define CLIPWINDOWXOFFSET(x) ((x) & 0x7FFF) +#define CLIPWINDOWYOFFSET(y) (((y) & 0x7FFF) << 16) +#define CLIPWINDOWWIDTH(w) (((w) - 1) & 0x3FFF) +#define CLIPWINDOWHEIGHT(h) ((((h) - 1) & 0x3FFF) << 16) +#define CONSTANTALPHA_MASK 0xFF +#define CONSTANTALPHA(n) ((n) & CONSTANTALPHA_MASK) +#define PALETTEENABLE BIT(0) +typedef enum { + TILE_FILL_ZERO, + TILE_FILL_CONSTANT, + TILE_PAD, + TILE_PAD_ZERO, +} tilemode_t; +#define ALPHASRCENABLE BIT(8) +#define ALPHACONSTENABLE BIT(9) +#define ALPHAMASKENABLE BIT(10) +#define ALPHATRANSENABLE BIT(11) +#define ALPHA_ENABLE_MASK (ALPHASRCENABLE | ALPHACONSTENABLE | \ + ALPHAMASKENABLE | ALPHATRANSENABLE) +#define RGBALPHASRCENABLE BIT(12) +#define RGBALPHACONSTENABLE BIT(13) +#define RGBALPHAMASKENABLE BIT(14) +#define RGBALPHATRANSENABLE BIT(15) +#define RGB_ENABLE_MASK (RGBALPHASRCENABLE | \ + RGBALPHACONSTENABLE | \ + RGBALPHAMASKENABLE | \ + RGBALPHATRANSENABLE) +#define PREMULCONSTRGB BIT(16) +typedef enum { + YUVCONVERSIONMODE__OFF, + YUVCONVERSIONMODE__ITU601, + YUVCONVERSIONMODE__ITU601_FR, + YUVCONVERSIONMODE__ITU709, +} yuvconversionmode_t; +#define YUVCONVERSIONMODE_MASK 0x60000 +#define YUVCONVERSIONMODE(m) (((m) & 0x3) << 17) +#define GAMMAREMOVEENABLE BIT(20) +#define CLIPWINDOWENABLE BIT(30) +#define SOURCEBUFFERENABLE BIT(31) +#define EMPTYFRAME BIT(31) +#define FRAMEWIDTH(w) (((w) - 1) & 0x3FFF) +#define FRAMEHEIGHT(h) ((((h) - 1) & 0x3FFF) << 16) +#define DELTAX_MASK 0x3F000 +#define DELTAY_MASK 0xFC0000 +#define DELTAX(x) (((x) & 0x3F) << 12) +#define DELTAY(y) (((y) & 0x3F) << 18) +#define YUV422UPSAMPLINGMODE_MASK BIT(5) +#define YUV422UPSAMPLINGMODE(m) (((m) & 0x1) << 5) +typedef enum { + YUV422UPSAMPLINGMODE__REPLICATE, + YUV422UPSAMPLINGMODE__INTERPOLATE, +} yuv422upsamplingmode_t; +#define INPUTSELECT_MASK 0x18 +#define INPUTSELECT(s) (((s) & 0x3) << 3) +typedef enum { + INPUTSELECT__INACTIVE, + INPUTSELECT__COMPPACK, + INPUTSELECT__ALPHAMASK, + INPUTSELECT__COORDINATE, +} inputselect_t; +#define RASTERMODE_MASK 0x7 +#define RASTERMODE(m) ((m) & 0x7) +typedef enum { + RASTERMODE__NORMAL, + RASTERMODE__DECODE, + RASTERMODE__ARBITRARY, + RASTERMODE__PERSPECTIVE, + RASTERMODE__YUV422, + RASTERMODE__AFFINE, +} rastermode_t; +#define SHDTOKGEN BIT(0) +#define FETCHTYPE_MASK 0xF + +#define DPU_FRAC_PLANE_LAYER_NUM 8 + +#define DPU_VPROC_CAP_HSCALER4 BIT(0) +#define DPU_VPROC_CAP_VSCALER4 BIT(1) +#define DPU_VPROC_CAP_HSCALER5 BIT(2) +#define DPU_VPROC_CAP_VSCALER5 BIT(3) +#define DPU_VPROC_CAP_FETCHECO0 BIT(4) +#define DPU_VPROC_CAP_FETCHECO1 BIT(5) + +#define DPU_VPROC_CAP_HSCALE (DPU_VPROC_CAP_HSCALER4 | \ + DPU_VPROC_CAP_HSCALER5) +#define DPU_VPROC_CAP_VSCALE (DPU_VPROC_CAP_VSCALER4 | \ + DPU_VPROC_CAP_VSCALER5) +#define DPU_VPROC_CAP_FETCHECO (DPU_VPROC_CAP_FETCHECO0 | \ + DPU_VPROC_CAP_FETCHECO1) + +struct dpu_unit { + char *name; + unsigned int num; + const unsigned int *ids; + const unsigned long *pec_ofss; /* PixEngCFG */ + const unsigned long *ofss; + const unsigned int *dprc_ids; +}; + +struct cm_reg_ofs { + u32 ipidentifier; + u32 lockunlock; + u32 lockstatus; + u32 userinterruptmask; + u32 interruptenable; + u32 interruptpreset; + u32 interruptclear; + u32 interruptstatus; + u32 userinterruptenable; + u32 userinterruptpreset; + u32 userinterruptclear; + u32 userinterruptstatus; + u32 generalpurpose; +}; + +struct dpu_data { + unsigned long cm_ofs; /* common */ + const struct dpu_unit *cfs; + const struct dpu_unit *decs; + const struct dpu_unit *eds; + const struct dpu_unit *fds; + const struct dpu_unit *fes; + const struct dpu_unit *fgs; + const struct dpu_unit *fls; + const struct dpu_unit *fws; + const struct dpu_unit *hss; + const struct dpu_unit *lbs; + const struct dpu_unit *sigs; + const struct dpu_unit *sts; + const struct dpu_unit *tcons; + const struct dpu_unit *vss; + const struct cm_reg_ofs *cm_reg_ofs; + const unsigned long *unused_irq; + + unsigned int syncmode_min_prate; /* need pixel combiner, KHz */ + unsigned int singlemode_max_width; + unsigned int master_stream_id; + + u32 plane_src_mask; + + bool has_dual_ldb; +}; + +struct dpu_soc { + struct device *dev; + const struct dpu_data *data; + spinlock_t lock; + struct list_head list; + + struct device *pd_dc_dev; + struct device *pd_pll0_dev; + struct device *pd_pll1_dev; + struct device_link *pd_dc_link; + struct device_link *pd_pll0_link; + struct device_link *pd_pll1_link; + + void __iomem *cm_reg; + + int id; + int usecount; + + int irq_extdst0_shdload; + int irq_extdst4_shdload; + int irq_extdst1_shdload; + int irq_extdst5_shdload; + int irq_disengcfg_shdload0; + int irq_disengcfg_framecomplete0; + int irq_sig0_shdload; + int irq_sig0_valid; + int irq_disengcfg_shdload1; + int irq_disengcfg_framecomplete1; + int irq_sig1_shdload; + int irq_sig1_valid; + int irq_comctrl_sw0; + int irq_comctrl_sw1; + int irq_comctrl_sw2; + int irq_comctrl_sw3; + int irq_line_num; + + bool irq_chip_pm_get_extdst0_shdload; + bool irq_chip_pm_get_extdst4_shdload; + bool irq_chip_pm_get_extdst1_shdload; + bool irq_chip_pm_get_extdst5_shdload; + bool irq_chip_pm_get_disengcfg_shdload0; + bool irq_chip_pm_get_disengcfg_framecomplete0; + bool irq_chip_pm_get_sig0_shdload; + bool irq_chip_pm_get_sig0_valid; + bool irq_chip_pm_get_disengcfg_shdload1; + bool irq_chip_pm_get_disengcfg_framecomplete1; + bool irq_chip_pm_get_sig1_shdload; + bool irq_chip_pm_get_sig1_valid; + bool irq_chip_pm_get_comctrl_sw0; + bool irq_chip_pm_get_comctrl_sw1; + bool irq_chip_pm_get_comctrl_sw2; + bool irq_chip_pm_get_comctrl_sw3; + + struct irq_domain *domain; + + struct imx_sc_ipc *dpu_ipc_handle; + + struct dpu_constframe *cf_priv[4]; + struct dpu_disengcfg *dec_priv[2]; + struct dpu_extdst *ed_priv[4]; + struct dpu_fetchunit *fd_priv[2]; + struct dpu_fetchunit *fe_priv[4]; + struct dpu_framegen *fg_priv[2]; + struct dpu_fetchunit *fl_priv[1]; + struct dpu_fetchunit *fw_priv[1]; + struct dpu_hscaler *hs_priv[3]; + struct dpu_layerblend *lb_priv[4]; + struct dpu_signature *sig_priv[2]; + struct dpu_store *st_priv[1]; + struct dpu_tcon *tcon_priv[2]; + struct dpu_vscaler *vs_priv[3]; +}; + +int dpu_format_horz_chroma_subsampling(u32 format); +int dpu_format_vert_chroma_subsampling(u32 format); +int dpu_format_num_planes(u32 format); +int dpu_format_plane_width(int width, u32 format, int plane); +int dpu_format_plane_height(int height, u32 format, int plane); + +#define _DECLARE_DPU_UNIT_INIT_FUNC(block) \ +void _dpu_##block##_init(struct dpu_soc *dpu, unsigned int id) \ + +_DECLARE_DPU_UNIT_INIT_FUNC(cf); +_DECLARE_DPU_UNIT_INIT_FUNC(dec); +_DECLARE_DPU_UNIT_INIT_FUNC(ed); +_DECLARE_DPU_UNIT_INIT_FUNC(fd); +_DECLARE_DPU_UNIT_INIT_FUNC(fe); +_DECLARE_DPU_UNIT_INIT_FUNC(fg); +_DECLARE_DPU_UNIT_INIT_FUNC(fl); +_DECLARE_DPU_UNIT_INIT_FUNC(fw); +_DECLARE_DPU_UNIT_INIT_FUNC(hs); +_DECLARE_DPU_UNIT_INIT_FUNC(lb); +_DECLARE_DPU_UNIT_INIT_FUNC(sig); +_DECLARE_DPU_UNIT_INIT_FUNC(st); +_DECLARE_DPU_UNIT_INIT_FUNC(tcon); +_DECLARE_DPU_UNIT_INIT_FUNC(vs); + +#define DECLARE_DPU_UNIT_INIT_FUNC(block) \ +int dpu_##block##_init(struct dpu_soc *dpu, unsigned int id, \ + unsigned long pec_base, unsigned long base) + +DECLARE_DPU_UNIT_INIT_FUNC(cf); +DECLARE_DPU_UNIT_INIT_FUNC(dec); +DECLARE_DPU_UNIT_INIT_FUNC(ed); +DECLARE_DPU_UNIT_INIT_FUNC(fd); +DECLARE_DPU_UNIT_INIT_FUNC(fe); +DECLARE_DPU_UNIT_INIT_FUNC(fg); +DECLARE_DPU_UNIT_INIT_FUNC(fl); +DECLARE_DPU_UNIT_INIT_FUNC(fw); +DECLARE_DPU_UNIT_INIT_FUNC(hs); +DECLARE_DPU_UNIT_INIT_FUNC(lb); +DECLARE_DPU_UNIT_INIT_FUNC(sig); +DECLARE_DPU_UNIT_INIT_FUNC(st); +DECLARE_DPU_UNIT_INIT_FUNC(tcon); +DECLARE_DPU_UNIT_INIT_FUNC(vs); + +static inline u32 dpu_pec_fu_read(struct dpu_fetchunit *fu, unsigned int offset) +{ + return readl(fu->pec_base + offset); +} + +static inline void dpu_pec_fu_write(struct dpu_fetchunit *fu, + unsigned int offset, u32 value) +{ + writel(value, fu->pec_base + offset); +} + +static inline u32 dpu_fu_read(struct dpu_fetchunit *fu, unsigned int offset) +{ + return readl(fu->base + offset); +} + +static inline void dpu_fu_write(struct dpu_fetchunit *fu, + unsigned int offset, u32 value) +{ + writel(value, fu->base + offset); +} + +static inline u32 rgb_color(u8 r, u8 g, u8 b, u8 a) +{ + return (r << 24) | (g << 16) | (b << 8) | a; +} + +static inline u32 yuv_color(u8 y, u8 u, u8 v) +{ + return (y << 24) | (u << 16) | (v << 8); +} + +void tcon_get_pc(struct dpu_tcon *tcon, void *data); + +static const unsigned int cf_ids[] = {0, 1, 4, 5}; +static const unsigned int dec_ids[] = {0, 1}; +static const unsigned int ed_ids[] = {0, 1, 4, 5}; +static const unsigned int fd_ids[] = {0, 1}; +static const unsigned int fe_ids[] = {0, 1, 2, 9}; +static const unsigned int fg_ids[] = {0, 1}; +static const unsigned int fl_ids[] = {0}; +static const unsigned int fw_ids[] = {2}; +static const unsigned int hs_ids[] = {4, 5, 9}; +static const unsigned int lb_ids[] = {0, 1, 2, 3}; +static const unsigned int sig_ids[] = {0, 1}; +static const unsigned int st_ids[] = {9}; +static const unsigned int tcon_ids[] = {0, 1}; +static const unsigned int vs_ids[] = {4, 5, 9}; + +static const unsigned int fd_dprc_ids[] = {3, 4}; +static const unsigned int fl_dprc_ids[] = {2}; +static const unsigned int fw_dprc_ids[] = {5}; + +struct dpu_pixel_format { + u32 pixel_format; + u32 bits; + u32 shift; +}; + +static const struct dpu_pixel_format dpu_pixel_format_matrix[] = { + { + DRM_FORMAT_ARGB8888, + R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(8), + R_SHIFT(16) | G_SHIFT(8) | B_SHIFT(0) | A_SHIFT(24), + }, { + DRM_FORMAT_XRGB8888, + R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(0), + R_SHIFT(16) | G_SHIFT(8) | B_SHIFT(0) | A_SHIFT(0), + }, { + DRM_FORMAT_ABGR8888, + R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(8), + R_SHIFT(0) | G_SHIFT(8) | B_SHIFT(16) | A_SHIFT(24), + }, { + DRM_FORMAT_XBGR8888, + R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(0), + R_SHIFT(0) | G_SHIFT(8) | B_SHIFT(16) | A_SHIFT(0), + }, { + DRM_FORMAT_RGBA8888, + R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(8), + R_SHIFT(24) | G_SHIFT(16) | B_SHIFT(8) | A_SHIFT(0), + }, { + DRM_FORMAT_RGBX8888, + R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(0), + R_SHIFT(24) | G_SHIFT(16) | B_SHIFT(8) | A_SHIFT(0), + }, { + DRM_FORMAT_BGRA8888, + R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(8), + R_SHIFT(8) | G_SHIFT(16) | B_SHIFT(24) | A_SHIFT(0), + }, { + DRM_FORMAT_BGRX8888, + R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(0), + R_SHIFT(8) | G_SHIFT(16) | B_SHIFT(24) | A_SHIFT(0), + }, { + DRM_FORMAT_RGB888, + R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(0), + R_SHIFT(16) | G_SHIFT(8) | B_SHIFT(0) | A_SHIFT(0), + }, { + DRM_FORMAT_BGR888, + R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(0), + R_SHIFT(0) | G_SHIFT(8) | B_SHIFT(16) | A_SHIFT(0), + }, { + DRM_FORMAT_RGB565, + R_BITS(5) | G_BITS(6) | B_BITS(5) | A_BITS(0), + R_SHIFT(11) | G_SHIFT(5) | B_SHIFT(0) | A_SHIFT(0), + }, { + DRM_FORMAT_YUYV, + Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0), + Y_SHIFT(0) | U_SHIFT(8) | V_SHIFT(8) | A_SHIFT(0), + }, { + DRM_FORMAT_UYVY, + Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0), + Y_SHIFT(8) | U_SHIFT(0) | V_SHIFT(0) | A_SHIFT(0), + }, { + DRM_FORMAT_NV12, + Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0), + Y_SHIFT(0) | U_SHIFT(0) | V_SHIFT(8) | A_SHIFT(0), + }, { + DRM_FORMAT_NV21, + Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0), + Y_SHIFT(0) | U_SHIFT(8) | V_SHIFT(0) | A_SHIFT(0), + }, { + DRM_FORMAT_NV16, + Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0), + Y_SHIFT(0) | U_SHIFT(0) | V_SHIFT(8) | A_SHIFT(0), + }, { + DRM_FORMAT_NV61, + Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0), + Y_SHIFT(0) | U_SHIFT(8) | V_SHIFT(0) | A_SHIFT(0), + }, { + DRM_FORMAT_NV24, + Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0), + Y_SHIFT(0) | U_SHIFT(0) | V_SHIFT(8) | A_SHIFT(0), + }, { + DRM_FORMAT_NV42, + Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0), + Y_SHIFT(0) | U_SHIFT(8) | V_SHIFT(0) | A_SHIFT(0), + }, +}; + +int dpu_sc_misc_get_handle(struct dpu_soc *dpu); +int dpu_pxlink_set_mst_addr(struct dpu_soc *dpu, int disp_id, u32 val); +int dpu_pxlink_set_mst_enable(struct dpu_soc *dpu, int disp_id, bool enable); +int dpu_pxlink_set_mst_valid(struct dpu_soc *dpu, int disp_id, bool enable); +int dpu_pxlink_set_sync_ctrl(struct dpu_soc *dpu, int disp_id, bool enable); +int dpu_pxlink_set_dc_sync_mode(struct dpu_soc *dpu, bool enable); +int dpu_sc_misc_init(struct dpu_soc *dpu); +#endif /* __DPU_PRV_H__ */ diff --git a/drivers/gpu/imx/dpu/dpu-sc-misc.c b/drivers/gpu/imx/dpu/dpu-sc-misc.c new file mode 100644 index 000000000000..20f600cb5a68 --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-sc-misc.c @@ -0,0 +1,93 @@ +/* + * Copyright 2019 NXP + * + * 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 <dt-bindings/firmware/imx/rsrc.h> +#include "dpu-prv.h" + +static inline int +dpu_sc_misc_set_ctrl(struct dpu_soc *dpu, u32 rsc, u8 ctrl, u32 val) +{ + return imx_sc_misc_set_control(dpu->dpu_ipc_handle, rsc, ctrl, val); +} + +int dpu_sc_misc_get_handle(struct dpu_soc *dpu) +{ + return imx_scu_get_handle(&dpu->dpu_ipc_handle); +} + +int dpu_pxlink_set_mst_addr(struct dpu_soc *dpu, int disp_id, u32 val) +{ + u32 rsc = dpu->id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0; + u8 ctrl = disp_id ? + IMX_SC_C_PXL_LINK_MST2_ADDR : IMX_SC_C_PXL_LINK_MST1_ADDR; + + return dpu_sc_misc_set_ctrl(dpu, rsc, ctrl, val); +} + +int dpu_pxlink_set_mst_enable(struct dpu_soc *dpu, int disp_id, bool enable) +{ + u32 rsc = dpu->id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0; + u8 ctrl = disp_id ? + IMX_SC_C_PXL_LINK_MST2_ENB: IMX_SC_C_PXL_LINK_MST1_ENB; + + return dpu_sc_misc_set_ctrl(dpu, rsc, ctrl, enable); +} + +int dpu_pxlink_set_mst_valid(struct dpu_soc *dpu, int disp_id, bool enable) +{ + u32 rsc = dpu->id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0; + u8 ctrl = disp_id ? + IMX_SC_C_PXL_LINK_MST2_VLD : IMX_SC_C_PXL_LINK_MST1_VLD; + + return dpu_sc_misc_set_ctrl(dpu, rsc, ctrl, enable); +} + +int dpu_pxlink_set_sync_ctrl(struct dpu_soc *dpu, int disp_id, bool enable) +{ + u32 rsc = dpu->id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0; + u8 ctrl = disp_id ? IMX_SC_C_SYNC_CTRL1 : IMX_SC_C_SYNC_CTRL0; + + return dpu_sc_misc_set_ctrl(dpu, rsc, ctrl, enable); +} + +int dpu_pxlink_set_dc_sync_mode(struct dpu_soc *dpu, bool enable) +{ + u32 rsc = dpu->id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0; + + return dpu_sc_misc_set_ctrl(dpu, rsc, IMX_SC_C_MODE, enable); +} + +/* KACHUNK_CNT is needed for blit engine */ +int dpu_sc_misc_set_kachunk_cnt(struct dpu_soc *dpu, u32 cnt) +{ + u32 rsc = dpu->id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0; + + return dpu_sc_misc_set_ctrl(dpu, rsc, IMX_SC_C_KACHUNK_CNT, cnt); +} + +int dpu_sc_misc_init(struct dpu_soc *dpu) +{ + int disp_id, ret = 0; + + for (disp_id = 0; disp_id < 2; disp_id++) { + ret |= dpu_pxlink_set_mst_addr(dpu, disp_id, 0); + ret |= dpu_pxlink_set_mst_enable(dpu, disp_id, false); + ret |= dpu_pxlink_set_mst_valid(dpu, disp_id, false); + ret |= dpu_pxlink_set_sync_ctrl(dpu, disp_id, false); + } + + ret |= dpu_sc_misc_set_kachunk_cnt(dpu, 32); + + return ret; +} diff --git a/drivers/gpu/imx/dpu/dpu-signature.c b/drivers/gpu/imx/dpu/dpu-signature.c new file mode 100644 index 000000000000..5fca7a1c4343 --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-signature.c @@ -0,0 +1,392 @@ +/* + * Copyright 2019,2020 NXP + * + * 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 <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include "dpu-prv.h" + +#define STATICCONTROL 0x8 +#define SHDLDSEL BIT(4) +#define LOCAL 0 +#define GLOBAL BIT(4) +#define PANICCOLOR 0xC +#define EVALCONTRL(n) (0x10 + (n) * 0x24) +#define ENGLOBALPANIC BIT(17) +#define ENLOCALPANIC BIT(16) +#define ALPHAINV BIT(9) +#define ALPHAMASK BIT(8) +#define ENCRC BIT(1) +#define ENEVALWIN BIT(0) +#define EVALUPPERLEFT(n) (0x14 + (n) * 0x24) +#define EVALLOWERRIGHT(n) (0x18 + (n) * 0x24) +#define YEVAL(y) (((y) & 0x3FFF) << 16) +#define XEVAL(x) ((x) & 0x3FFF) +#define SIGCRCREDREF(n) (0x1C + (n) * 0x24) +#define SIGCRCGREENREF(n) (0x20 + (n) * 0x24) +#define SIGCRCBLUEREF(n) (0x24 + (n) * 0x24) +#define SIGCRCRED(n) (0x28 + (n) * 0x24) +#define SIGCRCGREEN(n) (0x2C + (n) * 0x24) +#define SIGCRCBLUE(n) (0x30 + (n) * 0x24) +#define SHADOWLOAD 0x130 +#define SHDLDREQ(n) BIT(n) +#define CONTINUOUSMODE 0x134 +#define ENCONT BIT(0) +#define SOFTWAREKICK 0x138 +#define KICK BIT(0) +#define STATUS 0x13C +#define STSSIGIDLE BIT(20) +#define STSSIGVALID BIT(16) +#define STSSIGERROR(n) BIT(n) +#define STSSIGERROR_MASK 0xFF + +struct dpu_signature { + void __iomem *base; + struct mutex mutex; + int id; + bool inuse; + struct dpu_soc *dpu; +}; + +static inline u32 dpu_sig_read(struct dpu_signature *sig, unsigned int offset) +{ + return readl(sig->base + offset); +} + +static inline void dpu_sig_write(struct dpu_signature *sig, + unsigned int offset, u32 value) +{ + writel(value, sig->base + offset); +} + +void signature_shden(struct dpu_signature *sig, bool enable) +{ + u32 val; + + mutex_lock(&sig->mutex); + val = dpu_sig_read(sig, STATICCONTROL); + if (enable) + val |= SHDEN; + else + val &= ~SHDEN; + dpu_sig_write(sig, STATICCONTROL, val); + mutex_unlock(&sig->mutex); +} +EXPORT_SYMBOL_GPL(signature_shden); + +void signature_shdldsel_local(struct dpu_signature *sig) +{ + u32 val; + + mutex_lock(&sig->mutex); + val = dpu_sig_read(sig, STATICCONTROL); + val &= ~GLOBAL; + dpu_sig_write(sig, STATICCONTROL, val); + mutex_unlock(&sig->mutex); +} +EXPORT_SYMBOL_GPL(signature_shdldsel_local); + +void signature_shdldsel_global(struct dpu_signature *sig) +{ + u32 val; + + mutex_lock(&sig->mutex); + val = dpu_sig_read(sig, STATICCONTROL); + dpu_sig_write(sig, STATICCONTROL, val | GLOBAL); + mutex_unlock(&sig->mutex); +} +EXPORT_SYMBOL_GPL(signature_shdldsel_global); + +void +signature_global_panic(struct dpu_signature *sig, unsigned int win, bool enable) +{ + u32 val; + + mutex_lock(&sig->mutex); + val = dpu_sig_read(sig, EVALCONTRL(win)); + if (enable) + val |= ENGLOBALPANIC; + else + val &= ~ENGLOBALPANIC; + dpu_sig_write(sig, EVALCONTRL(win), val); + mutex_unlock(&sig->mutex); +} +EXPORT_SYMBOL_GPL(signature_global_panic); + +void +signature_local_panic(struct dpu_signature *sig, unsigned int win, bool enable) +{ + u32 val; + + mutex_lock(&sig->mutex); + val = dpu_sig_read(sig, EVALCONTRL(win)); + if (enable) + val |= ENLOCALPANIC; + else + val &= ~ENLOCALPANIC; + dpu_sig_write(sig, EVALCONTRL(win), val); + mutex_unlock(&sig->mutex); +} +EXPORT_SYMBOL_GPL(signature_local_panic); + +void +signature_alpha_mask(struct dpu_signature *sig, unsigned int win, bool enable) +{ + u32 val; + + mutex_lock(&sig->mutex); + val = dpu_sig_read(sig, EVALCONTRL(win)); + if (enable) + val |= ALPHAMASK; + else + val &= ~ALPHAMASK; + dpu_sig_write(sig, EVALCONTRL(win), val); + mutex_unlock(&sig->mutex); +} +EXPORT_SYMBOL_GPL(signature_alpha_mask); + +void signature_crc(struct dpu_signature *sig, unsigned int win, bool enable) +{ + u32 val; + + mutex_lock(&sig->mutex); + val = dpu_sig_read(sig, EVALCONTRL(win)); + if (enable) + val |= ENCRC; + else + val &= ~ENCRC; + dpu_sig_write(sig, EVALCONTRL(win), val); + mutex_unlock(&sig->mutex); +} +EXPORT_SYMBOL_GPL(signature_crc); + +void +signature_eval_win(struct dpu_signature *sig, unsigned int win, bool enable) +{ + u32 val; + + mutex_lock(&sig->mutex); + val = dpu_sig_read(sig, EVALCONTRL(win)); + if (enable) + val |= ENEVALWIN; + else + val &= ~ENEVALWIN; + dpu_sig_write(sig, EVALCONTRL(win), val); + mutex_unlock(&sig->mutex); +} +EXPORT_SYMBOL_GPL(signature_eval_win); + +void signature_win(struct dpu_signature *sig, unsigned int win, + int xul, int yul, int xlr, int ylr) +{ + mutex_lock(&sig->mutex); + dpu_sig_write(sig, EVALUPPERLEFT(win), XEVAL(xul) | YEVAL(yul)); + dpu_sig_write(sig, EVALLOWERRIGHT(win), XEVAL(--xlr) | YEVAL(--ylr)); + mutex_unlock(&sig->mutex); +} +EXPORT_SYMBOL_GPL(signature_win); + +void signature_crc_value(struct dpu_signature *sig, unsigned int win, + u32 *red, u32 *green, u32 *blue) +{ + mutex_lock(&sig->mutex); + *red = dpu_sig_read(sig, SIGCRCRED(win)); + *green = dpu_sig_read(sig, SIGCRCGREEN(win)); + *blue = dpu_sig_read(sig, SIGCRCBLUE(win)); + mutex_unlock(&sig->mutex); +} +EXPORT_SYMBOL_GPL(signature_crc_value); + +void signature_shdldreq(struct dpu_signature *sig, u8 win_mask) +{ + mutex_lock(&sig->mutex); + dpu_sig_write(sig, SHADOWLOAD, win_mask); + mutex_unlock(&sig->mutex); +} +EXPORT_SYMBOL_GPL(signature_shdldreq); + +void signature_continuous_mode(struct dpu_signature *sig, bool enable) +{ + u32 val; + + mutex_lock(&sig->mutex); + val = dpu_sig_read(sig, CONTINUOUSMODE); + if (enable) + val |= ENCONT; + else + val &= ~ENCONT; + dpu_sig_write(sig, CONTINUOUSMODE, val); + mutex_unlock(&sig->mutex); +} +EXPORT_SYMBOL_GPL(signature_continuous_mode); + +void signature_kick(struct dpu_signature *sig) +{ + mutex_lock(&sig->mutex); + dpu_sig_write(sig, SOFTWAREKICK, KICK); + mutex_unlock(&sig->mutex); +} +EXPORT_SYMBOL_GPL(signature_kick); + +bool signature_is_idle(struct dpu_signature *sig) +{ + u32 val; + + mutex_lock(&sig->mutex); + val = dpu_sig_read(sig, STATUS); + mutex_unlock(&sig->mutex); + + return !!(val & STSSIGIDLE); +} +EXPORT_SYMBOL_GPL(signature_is_idle); + +void signature_wait_for_idle(struct dpu_signature *sig) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(100); + bool idle; + + do { + idle = signature_is_idle(sig); + } while (!idle && time_before(jiffies, timeout)); + + if (idle) + dev_dbg(sig->dpu->dev, "Signature%d is idle\n", sig->id); + else + dev_err(sig->dpu->dev, + "failed to wait for Signature%d idle\n", sig->id); +} +EXPORT_SYMBOL_GPL(signature_wait_for_idle); + +bool signature_is_valid(struct dpu_signature *sig) +{ + u32 val; + + mutex_lock(&sig->mutex); + val = dpu_sig_read(sig, STATUS); + mutex_unlock(&sig->mutex); + + return !!(val & STSSIGVALID); +} +EXPORT_SYMBOL_GPL(signature_is_valid); + +bool signature_is_error(struct dpu_signature *sig, u8 *err_win_mask) +{ + u32 val; + + mutex_lock(&sig->mutex); + val = dpu_sig_read(sig, STATUS); + mutex_unlock(&sig->mutex); + + *err_win_mask = val & STSSIGERROR_MASK; + + return !!(*err_win_mask); +} +EXPORT_SYMBOL_GPL(signature_is_error); + +struct dpu_signature *dpu_sig_get(struct dpu_soc *dpu, int id) +{ + struct dpu_signature *sig; + int i; + + for (i = 0; i < ARRAY_SIZE(sig_ids); i++) + if (sig_ids[i] == id) + break; + + if (i == ARRAY_SIZE(sig_ids)) + return ERR_PTR(-EINVAL); + + sig = dpu->sig_priv[i]; + + mutex_lock(&sig->mutex); + + if (sig->inuse) { + mutex_unlock(&sig->mutex); + return ERR_PTR(-EBUSY); + } + + sig->inuse = true; + + mutex_unlock(&sig->mutex); + + return sig; +} +EXPORT_SYMBOL_GPL(dpu_sig_get); + +void dpu_sig_put(struct dpu_signature *sig) +{ + mutex_lock(&sig->mutex); + + sig->inuse = false; + + mutex_unlock(&sig->mutex); +} +EXPORT_SYMBOL_GPL(dpu_sig_put); + +struct dpu_signature *dpu_aux_sig_peek(struct dpu_signature *sig) +{ + return sig->dpu->sig_priv[sig->id ^ 1]; +} +EXPORT_SYMBOL_GPL(dpu_aux_sig_peek); + +void _dpu_sig_init(struct dpu_soc *dpu, unsigned int id) +{ + struct dpu_signature *sig; + int i, j; + + for (i = 0; i < ARRAY_SIZE(sig_ids); i++) + if (sig_ids[i] == id) + break; + + if (WARN_ON(i == ARRAY_SIZE(sig_ids))) + return; + + sig = dpu->sig_priv[i]; + + signature_shden(sig, true); + signature_shdldsel_local(sig); + for (j = 0; j < MAX_DPU_SIGNATURE_WIN_NUM; j++) { + signature_global_panic(sig, j, false); + signature_local_panic(sig, j, false); + signature_alpha_mask(sig, j, false); + signature_crc(sig, j, false); + signature_eval_win(sig, j, false); + signature_continuous_mode(sig, false); + } +} + +int dpu_sig_init(struct dpu_soc *dpu, unsigned int id, + unsigned long unused, unsigned long base) +{ + struct dpu_signature *sig; + + sig = devm_kzalloc(dpu->dev, sizeof(*sig), GFP_KERNEL); + if (!sig) + return -ENOMEM; + + dpu->sig_priv[id] = sig; + + sig->base = devm_ioremap(dpu->dev, base, SZ_512); + if (!sig->base) + return -ENOMEM; + + sig->dpu = dpu; + sig->id = id; + mutex_init(&sig->mutex); + + _dpu_sig_init(dpu, id); + + return 0; +} diff --git a/drivers/gpu/imx/dpu/dpu-store.c b/drivers/gpu/imx/dpu/dpu-store.c new file mode 100644 index 000000000000..cbd06b83581b --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-store.c @@ -0,0 +1,157 @@ +/* + * Copyright 2018-2019 NXP + * + * 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 <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include "dpu-prv.h" + +#define PIXENGCFG_STATIC 0x8 +#define DIV(n) (((n) & 0xFF) << 16) +#define DIV_RESET 0x80 + +struct dpu_store { + void __iomem *pec_base; + void __iomem *base; + struct mutex mutex; + int id; + bool inuse; + struct dpu_soc *dpu; +}; + +static inline u32 dpu_pec_st_read(struct dpu_store *st, unsigned int offset) +{ + return readl(st->pec_base + offset); +} + +static inline void dpu_pec_st_write(struct dpu_store *st, + unsigned int offset, u32 value) +{ + writel(value, st->pec_base + offset); +} + +void store_pixengcfg_syncmode_fixup(struct dpu_store *st, bool enable) +{ + struct dpu_soc *dpu; + u32 val; + + if (!st) + return; + + dpu = st->dpu; + + mutex_lock(&st->mutex); + val = dpu_pec_st_read(st, PIXENGCFG_STATIC); + if (enable) + val |= BIT(16); + else + val &= ~BIT(16); + dpu_pec_st_write(st, PIXENGCFG_STATIC, val); + mutex_unlock(&st->mutex); +} +EXPORT_SYMBOL_GPL(store_pixengcfg_syncmode_fixup); + +struct dpu_store *dpu_st_get(struct dpu_soc *dpu, int id) +{ + struct dpu_store *st; + int i; + + for (i = 0; i < ARRAY_SIZE(st_ids); i++) + if (st_ids[i] == id) + break; + + if (i == ARRAY_SIZE(st_ids)) + return ERR_PTR(-EINVAL); + + st = dpu->st_priv[i]; + + mutex_lock(&st->mutex); + + if (st->inuse) { + mutex_unlock(&st->mutex); + return ERR_PTR(-EBUSY); + } + + st->inuse = true; + + mutex_unlock(&st->mutex); + + return st; +} +EXPORT_SYMBOL_GPL(dpu_st_get); + +void dpu_st_put(struct dpu_store *st) +{ + mutex_lock(&st->mutex); + + st->inuse = false; + + mutex_unlock(&st->mutex); +} +EXPORT_SYMBOL_GPL(dpu_st_put); + +void _dpu_st_init(struct dpu_soc *dpu, unsigned int id) +{ + struct dpu_store *st; + int i; + + for (i = 0; i < ARRAY_SIZE(st_ids); i++) + if (st_ids[i] == id) + break; + + if (WARN_ON(i == ARRAY_SIZE(st_ids))) + return; + + st = dpu->st_priv[i]; + + dpu_pec_st_write(st, PIXENGCFG_STATIC, SHDEN | DIV(DIV_RESET)); +} + +int dpu_st_init(struct dpu_soc *dpu, unsigned int id, + unsigned long pec_base, unsigned long base) +{ + struct dpu_store *st; + int i; + + st = devm_kzalloc(dpu->dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(st_ids); i++) + if (st_ids[i] == id) + break; + + if (i == ARRAY_SIZE(st_ids)) + return -EINVAL; + + dpu->st_priv[i] = st; + + st->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_32); + if (!st->pec_base) + return -ENOMEM; + + st->base = devm_ioremap(dpu->dev, base, SZ_256); + if (!st->base) + return -ENOMEM; + + st->dpu = dpu; + st->id = id; + mutex_init(&st->mutex); + + _dpu_st_init(dpu, id); + + return 0; +} diff --git a/drivers/gpu/imx/dpu/dpu-tcon.c b/drivers/gpu/imx/dpu/dpu-tcon.c new file mode 100644 index 000000000000..bbecc2c720ba --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-tcon.c @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2020 NXP + * + * 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 <linux/io.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <video/dpu.h> +#include <video/imx8-pc.h> +#include "dpu-prv.h" + +#define SSQCNTS 0 +#define SSQCYCLE 0x8 +#define SWRESET 0xC +#define TCON_CTRL 0x10 +#define BYPASS BIT(3) +#define RSDSINVCTRL 0x14 +#define MAPBIT3_0 0x18 +#define MAPBIT7_4 0x1C +#define MAPBIT11_8 0x20 +#define MAPBIT15_12 0x24 +#define MAPBIT19_16 0x28 +#define MAPBIT23_20 0x2C +#define MAPBIT27_24 0x30 +#define MAPBIT31_28 0x34 +#define MAPBIT34_32 0x38 +#define MAPBIT3_0_DUAL 0x3C +#define MAPBIT7_4_DUAL 0x40 +#define MAPBIT11_8_DUAL 0x44 +#define MAPBIT15_12_DUAL 0x48 +#define MAPBIT19_16_DUAL 0x4C +#define MAPBIT23_20_DUAL 0x50 +#define MAPBIT27_24_DUAL 0x54 +#define MAPBIT31_28_DUAL 0x58 +#define MAPBIT34_32_DUAL 0x5C +#define SPGPOSON(n) (0x60 + (n) * 16) +#define X(n) (((n) & 0x7FFF) << 16) +#define Y(n) ((n) & 0x7FFF) +#define SPGMASKON(n) (0x64 + (n) * 16) +#define SPGPOSOFF(n) (0x68 + (n) * 16) +#define SPGMASKOFF(n) (0x6C + (n) * 16) +#define SMXSIGS(n) (0x120 + (n) * 8) +#define SMXFCTTABLE(n) (0x124 + (n) * 8) +#define RESET_OVER_UNFERFLOW 0x180 +#define DUAL_DEBUG 0x184 + +struct dpu_tcon { + void __iomem *base; + struct mutex mutex; + int id; + bool inuse; + struct dpu_soc *dpu; + struct pc *pc; +}; + +static inline u32 dpu_tcon_read(struct dpu_tcon *tcon, unsigned int offset) +{ + return readl(tcon->base + offset); +} + +static inline void dpu_tcon_write(struct dpu_tcon *tcon, + unsigned int offset, u32 value) +{ + writel(value, tcon->base + offset); +} + +int tcon_set_fmt(struct dpu_tcon *tcon, u32 bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + dpu_tcon_write(tcon, MAPBIT3_0, 0x19181716); + dpu_tcon_write(tcon, MAPBIT7_4, 0x1d1c1b1a); + dpu_tcon_write(tcon, MAPBIT11_8, 0x0f0e0d0c); + dpu_tcon_write(tcon, MAPBIT15_12, 0x13121110); + dpu_tcon_write(tcon, MAPBIT19_16, 0x05040302); + dpu_tcon_write(tcon, MAPBIT23_20, 0x09080706); + break; + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_RGB888_1X30_PADLO: + case MEDIA_BUS_FMT_RGB666_1X30_PADLO: + case MEDIA_BUS_FMT_RGB565_1X30_PADLO: + dpu_tcon_write(tcon, MAPBIT3_0, 0x17161514); + dpu_tcon_write(tcon, MAPBIT7_4, 0x1b1a1918); + dpu_tcon_write(tcon, MAPBIT11_8, 0x0b0a1d1c); + dpu_tcon_write(tcon, MAPBIT15_12, 0x0f0e0d0c); + dpu_tcon_write(tcon, MAPBIT19_16, 0x13121110); + dpu_tcon_write(tcon, MAPBIT23_20, 0x03020100); + dpu_tcon_write(tcon, MAPBIT27_24, 0x07060504); + dpu_tcon_write(tcon, MAPBIT31_28, 0x00000908); + break; + default: + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(tcon_set_fmt); + +/* This function is used to workaround TKT320590 which is related to DPR/PRG. */ +void tcon_set_operation_mode(struct dpu_tcon *tcon) +{ + u32 val; + + val = dpu_tcon_read(tcon, TCON_CTRL); + val &= ~BYPASS; + dpu_tcon_write(tcon, TCON_CTRL, val); +} +EXPORT_SYMBOL_GPL(tcon_set_operation_mode); + +void tcon_cfg_videomode(struct dpu_tcon *tcon, + struct drm_display_mode *m, bool side_by_side) +{ + u32 val; + int hdisplay, hsync_start, hsync_end; + int vdisplay, vsync_start, vsync_end; + int y; + + hdisplay = m->hdisplay; + vdisplay = m->vdisplay; + hsync_start = m->hsync_start; + vsync_start = m->vsync_start; + hsync_end = m->hsync_end; + vsync_end = m->vsync_end; + + if (side_by_side) { + hdisplay /= 2; + hsync_start /= 2; + hsync_end /= 2; + } + + /* + * TKT320590: + * Turn TCON into operation mode later after the first dumb frame is + * generated by DPU. This makes DPR/PRG be able to evade the frame. + */ + val = dpu_tcon_read(tcon, TCON_CTRL); + val |= BYPASS; + dpu_tcon_write(tcon, TCON_CTRL, val); + + /* dsp_control[0]: hsync */ + dpu_tcon_write(tcon, SPGPOSON(0), X(hsync_start)); + dpu_tcon_write(tcon, SPGMASKON(0), 0xffff); + + dpu_tcon_write(tcon, SPGPOSOFF(0), X(hsync_end)); + dpu_tcon_write(tcon, SPGMASKOFF(0), 0xffff); + + dpu_tcon_write(tcon, SMXSIGS(0), 0x2); + dpu_tcon_write(tcon, SMXFCTTABLE(0), 0x1); + + /* dsp_control[1]: vsync */ + dpu_tcon_write(tcon, SPGPOSON(1), X(hsync_start) | Y(vsync_start - 1)); + dpu_tcon_write(tcon, SPGMASKON(1), 0x0); + + dpu_tcon_write(tcon, SPGPOSOFF(1), X(hsync_start) | Y(vsync_end - 1)); + dpu_tcon_write(tcon, SPGMASKOFF(1), 0x0); + + dpu_tcon_write(tcon, SMXSIGS(1), 0x3); + dpu_tcon_write(tcon, SMXFCTTABLE(1), 0x1); + + /* dsp_control[2]: data enable */ + /* horizontal */ + dpu_tcon_write(tcon, SPGPOSON(2), 0x0); + dpu_tcon_write(tcon, SPGMASKON(2), 0xffff); + + dpu_tcon_write(tcon, SPGPOSOFF(2), X(hdisplay)); + dpu_tcon_write(tcon, SPGMASKOFF(2), 0xffff); + + /* vertical */ + dpu_tcon_write(tcon, SPGPOSON(3), 0x0); + dpu_tcon_write(tcon, SPGMASKON(3), 0x7fff0000); + + dpu_tcon_write(tcon, SPGPOSOFF(3), Y(vdisplay)); + dpu_tcon_write(tcon, SPGMASKOFF(3), 0x7fff0000); + + dpu_tcon_write(tcon, SMXSIGS(2), 0x2c); + dpu_tcon_write(tcon, SMXFCTTABLE(2), 0x8); + + /* dsp_control[3]: kachuck */ + y = vdisplay + 1; + /* + * If sync mode fixup is present, the kachuck signal from slave tcon + * should be one line later than the one from master tcon. + */ + if (side_by_side && tcon_is_slave(tcon)) + y++; + + dpu_tcon_write(tcon, SPGPOSON(4), X(0x0) | Y(y)); + dpu_tcon_write(tcon, SPGMASKON(4), 0x0); + + dpu_tcon_write(tcon, SPGPOSOFF(4), X(0x20) | Y(y)); + dpu_tcon_write(tcon, SPGMASKOFF(4), 0x0); + + dpu_tcon_write(tcon, SMXSIGS(3), 0x6); + dpu_tcon_write(tcon, SMXFCTTABLE(3), 0x2); +} +EXPORT_SYMBOL_GPL(tcon_cfg_videomode); + +bool tcon_is_master(struct dpu_tcon *tcon) +{ + const struct dpu_data *data = tcon->dpu->data; + + return tcon->id == data->master_stream_id; +} +EXPORT_SYMBOL_GPL(tcon_is_master); + +bool tcon_is_slave(struct dpu_tcon *tcon) +{ + return !tcon_is_master(tcon); +} +EXPORT_SYMBOL_GPL(tcon_is_slave); + +void tcon_configure_pc(struct dpu_tcon *tcon, unsigned int di, + unsigned int frame_width, u32 mode, u32 format) +{ + if (WARN_ON(!tcon || !tcon->pc)) + return; + + pc_configure(tcon->pc, di, frame_width, mode, format); +} +EXPORT_SYMBOL_GPL(tcon_configure_pc); + +void tcon_enable_pc(struct dpu_tcon *tcon) +{ + if (WARN_ON(!tcon || !tcon->pc)) + return; + + pc_enable(tcon->pc); +} +EXPORT_SYMBOL_GPL(tcon_enable_pc); + +void tcon_disable_pc(struct dpu_tcon *tcon) +{ + if (WARN_ON(!tcon || !tcon->pc)) + return; + + pc_disable(tcon->pc); +} +EXPORT_SYMBOL_GPL(tcon_disable_pc); + +struct dpu_tcon *dpu_tcon_get(struct dpu_soc *dpu, int id) +{ + struct dpu_tcon *tcon; + int i; + + for (i = 0; i < ARRAY_SIZE(tcon_ids); i++) + if (tcon_ids[i] == id) + break; + + if (i == ARRAY_SIZE(tcon_ids)) + return ERR_PTR(-EINVAL); + + tcon = dpu->tcon_priv[i]; + + mutex_lock(&tcon->mutex); + + if (tcon->inuse) { + mutex_unlock(&tcon->mutex); + return ERR_PTR(-EBUSY); + } + + tcon->inuse = true; + + mutex_unlock(&tcon->mutex); + + return tcon; +} +EXPORT_SYMBOL_GPL(dpu_tcon_get); + +void dpu_tcon_put(struct dpu_tcon *tcon) +{ + mutex_lock(&tcon->mutex); + + tcon->inuse = false; + + mutex_unlock(&tcon->mutex); +} +EXPORT_SYMBOL_GPL(dpu_tcon_put); + +struct dpu_tcon *dpu_aux_tcon_peek(struct dpu_tcon *tcon) +{ + return tcon->dpu->tcon_priv[tcon->id ^ 1]; +} +EXPORT_SYMBOL_GPL(dpu_aux_tcon_peek); + +void _dpu_tcon_init(struct dpu_soc *dpu, unsigned int id) +{ +} + +int dpu_tcon_init(struct dpu_soc *dpu, unsigned int id, + unsigned long unused, unsigned long base) +{ + struct dpu_tcon *tcon; + + tcon = devm_kzalloc(dpu->dev, sizeof(*tcon), GFP_KERNEL); + if (!tcon) + return -ENOMEM; + + dpu->tcon_priv[id] = tcon; + + tcon->base = devm_ioremap(dpu->dev, base, SZ_512); + if (!tcon->base) + return -ENOMEM; + + tcon->dpu = dpu; + mutex_init(&tcon->mutex); + + return 0; +} + +void tcon_get_pc(struct dpu_tcon *tcon, void *data) +{ + if (WARN_ON(!tcon)) + return; + + tcon->pc = data; +} diff --git a/drivers/gpu/imx/dpu/dpu-vscaler.c b/drivers/gpu/imx/dpu/dpu-vscaler.c new file mode 100644 index 000000000000..b1bdcd596392 --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-vscaler.c @@ -0,0 +1,438 @@ +/* + * Copyright 2017-2019 NXP + * + * 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 <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <video/dpu.h> +#include "dpu-prv.h" + +#define PIXENGCFG_DYNAMIC 0x8 +#define PIXENGCFG_DYNAMIC_SRC_SEL_MASK 0x3F + +#define SETUP1 0xC +#define SCALE_FACTOR_MASK 0xFFFFF +#define SCALE_FACTOR(n) ((n) & 0xFFFFF) +#define SETUP2 0x10 +#define SETUP3 0x14 +#define SETUP4 0x18 +#define SETUP5 0x1C +#define PHASE_OFFSET_MASK 0x1FFFFF +#define PHASE_OFFSET(n) ((n) & 0x1FFFFF) +#define CONTROL 0x20 +#define OUTPUT_SIZE_MASK 0x3FFF0000 +#define OUTPUT_SIZE(n) ((((n) - 1) << 16) & OUTPUT_SIZE_MASK) +#define FIELD_MODE 0x3000 +#define FILTER_MODE 0x100 +#define SCALE_MODE 0x10 +#define MODE 0x1 + +static const vs_src_sel_t src_sels[3][6] = { + { + VS_SRC_SEL__DISABLE, + VS_SRC_SEL__FETCHDECODE0, + VS_SRC_SEL__MATRIX4, + VS_SRC_SEL__HSCALER4, + }, { + VS_SRC_SEL__DISABLE, + VS_SRC_SEL__FETCHDECODE1, + VS_SRC_SEL__MATRIX5, + VS_SRC_SEL__HSCALER5, + }, { + VS_SRC_SEL__DISABLE, + VS_SRC_SEL__MATRIX9, + VS_SRC_SEL__HSCALER9, + }, +}; + +struct dpu_vscaler { + void __iomem *pec_base; + void __iomem *base; + struct mutex mutex; + int id; + bool inuse; + struct dpu_soc *dpu; + /* see DPU_PLANE_SRC_xxx */ + unsigned int stream_id; +}; + +static inline u32 dpu_pec_vs_read(struct dpu_vscaler *vs, + unsigned int offset) +{ + return readl(vs->pec_base + offset); +} + +static inline void dpu_pec_vs_write(struct dpu_vscaler *vs, + unsigned int offset, u32 value) +{ + writel(value, vs->pec_base + offset); +} + +static inline u32 dpu_vs_read(struct dpu_vscaler *vs, unsigned int offset) +{ + return readl(vs->base + offset); +} + +static inline void dpu_vs_write(struct dpu_vscaler *vs, + unsigned int offset, u32 value) +{ + writel(value, vs->base + offset); +} + +int vscaler_pixengcfg_dynamic_src_sel(struct dpu_vscaler *vs, vs_src_sel_t src) +{ + struct dpu_soc *dpu = vs->dpu; + const unsigned int vs_id_array[] = {4, 5, 9}; + int i, j; + u32 val; + + for (i = 0; i < ARRAY_SIZE(vs_id_array); i++) + if (vs_id_array[i] == vs->id) + break; + + if (WARN_ON(i == ARRAY_SIZE(vs_id_array))) + return -EINVAL; + + mutex_lock(&vs->mutex); + for (j = 0; j < ARRAY_SIZE(src_sels[0]); j++) { + if (src_sels[i][j] == src) { + val = dpu_pec_vs_read(vs, PIXENGCFG_DYNAMIC); + val &= ~PIXENGCFG_DYNAMIC_SRC_SEL_MASK; + val |= src; + dpu_pec_vs_write(vs, PIXENGCFG_DYNAMIC, val); + mutex_unlock(&vs->mutex); + return 0; + } + } + mutex_unlock(&vs->mutex); + + dev_err(dpu->dev, "Invalid source for VScaler%d\n", vs->id); + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(vscaler_pixengcfg_dynamic_src_sel); + +void vscaler_pixengcfg_clken(struct dpu_vscaler *vs, pixengcfg_clken_t clken) +{ + u32 val; + + mutex_lock(&vs->mutex); + val = dpu_pec_vs_read(vs, PIXENGCFG_DYNAMIC); + val &= ~CLKEN_MASK; + val |= clken << CLKEN_MASK_SHIFT; + dpu_pec_vs_write(vs, PIXENGCFG_DYNAMIC, val); + mutex_unlock(&vs->mutex); +} +EXPORT_SYMBOL_GPL(vscaler_pixengcfg_clken); + +void vscaler_shden(struct dpu_vscaler *vs, bool enable) +{ + u32 val; + + mutex_lock(&vs->mutex); + val = dpu_vs_read(vs, STATICCONTROL); + if (enable) + val |= SHDEN; + else + val &= ~SHDEN; + dpu_vs_write(vs, STATICCONTROL, val); + mutex_unlock(&vs->mutex); +} +EXPORT_SYMBOL_GPL(vscaler_shden); + +void vscaler_setup1(struct dpu_vscaler *vs, u32 src, u32 dst, bool deinterlace) +{ + struct dpu_soc *dpu = vs->dpu; + u32 scale_factor; + u64 tmp64; + + if (deinterlace) + dst *= 2; + + if (src == dst) { + scale_factor = 0x80000; + } else { + if (src > dst) { + tmp64 = (u64)((u64)dst * 0x80000); + do_div(tmp64, src); + + } else { + tmp64 = (u64)((u64)src * 0x80000); + do_div(tmp64, dst); + } + scale_factor = (u32)tmp64; + } + + WARN_ON(scale_factor > 0x80000); + + mutex_lock(&vs->mutex); + dpu_vs_write(vs, SETUP1, SCALE_FACTOR(scale_factor)); + mutex_unlock(&vs->mutex); + + dev_dbg(dpu->dev, "Vscaler%d scale factor 0x%08x\n", + vs->id, scale_factor); +} +EXPORT_SYMBOL_GPL(vscaler_setup1); + +void vscaler_setup2(struct dpu_vscaler *vs, bool deinterlace) +{ + /* 0x20000: +0.25 phase offset for deinterlace */ + u32 phase_offset = deinterlace ? 0x20000 : 0; + + mutex_lock(&vs->mutex); + dpu_vs_write(vs, SETUP2, PHASE_OFFSET(phase_offset)); + mutex_unlock(&vs->mutex); +} +EXPORT_SYMBOL_GPL(vscaler_setup2); + +void vscaler_setup3(struct dpu_vscaler *vs, bool deinterlace) +{ + /* 0x1e0000: -0.25 phase offset for deinterlace */ + u32 phase_offset = deinterlace ? 0x1e0000 : 0; + + mutex_lock(&vs->mutex); + dpu_vs_write(vs, SETUP3, PHASE_OFFSET(phase_offset)); + mutex_unlock(&vs->mutex); +} +EXPORT_SYMBOL_GPL(vscaler_setup3); + +void vscaler_setup4(struct dpu_vscaler *vs, u32 phase_offset) +{ + mutex_lock(&vs->mutex); + dpu_vs_write(vs, SETUP4, PHASE_OFFSET(phase_offset)); + mutex_unlock(&vs->mutex); +} +EXPORT_SYMBOL_GPL(vscaler_setup4); + +void vscaler_setup5(struct dpu_vscaler *vs, u32 phase_offset) +{ + mutex_lock(&vs->mutex); + dpu_vs_write(vs, SETUP5, PHASE_OFFSET(phase_offset)); + mutex_unlock(&vs->mutex); +} +EXPORT_SYMBOL_GPL(vscaler_setup5); + +void vscaler_output_size(struct dpu_vscaler *vs, u32 line_num) +{ + u32 val; + + mutex_lock(&vs->mutex); + val = dpu_vs_read(vs, CONTROL); + val &= ~OUTPUT_SIZE_MASK; + val |= OUTPUT_SIZE(line_num); + dpu_vs_write(vs, CONTROL, val); + mutex_unlock(&vs->mutex); +} +EXPORT_SYMBOL_GPL(vscaler_output_size); + +void vscaler_field_mode(struct dpu_vscaler *vs, scaler_field_mode_t m) +{ + u32 val; + + mutex_lock(&vs->mutex); + val = dpu_vs_read(vs, CONTROL); + val &= ~FIELD_MODE; + val |= m; + dpu_vs_write(vs, CONTROL, val); + mutex_unlock(&vs->mutex); +} +EXPORT_SYMBOL_GPL(vscaler_field_mode); + +void vscaler_filter_mode(struct dpu_vscaler *vs, scaler_filter_mode_t m) +{ + u32 val; + + mutex_lock(&vs->mutex); + val = dpu_vs_read(vs, CONTROL); + val &= ~FILTER_MODE; + val |= m; + dpu_vs_write(vs, CONTROL, val); + mutex_unlock(&vs->mutex); +} +EXPORT_SYMBOL_GPL(vscaler_filter_mode); + +void vscaler_scale_mode(struct dpu_vscaler *vs, scaler_scale_mode_t m) +{ + u32 val; + + mutex_lock(&vs->mutex); + val = dpu_vs_read(vs, CONTROL); + val &= ~SCALE_MODE; + val |= m; + dpu_vs_write(vs, CONTROL, val); + mutex_unlock(&vs->mutex); +} +EXPORT_SYMBOL_GPL(vscaler_scale_mode); + +void vscaler_mode(struct dpu_vscaler *vs, scaler_mode_t m) +{ + u32 val; + + mutex_lock(&vs->mutex); + val = dpu_vs_read(vs, CONTROL); + val &= ~MODE; + val |= m; + dpu_vs_write(vs, CONTROL, val); + mutex_unlock(&vs->mutex); +} +EXPORT_SYMBOL_GPL(vscaler_mode); + +bool vscaler_is_enabled(struct dpu_vscaler *vs) +{ + u32 val; + + mutex_lock(&vs->mutex); + val = dpu_vs_read(vs, CONTROL); + mutex_unlock(&vs->mutex); + + return (val & MODE) == SCALER_ACTIVE; +} +EXPORT_SYMBOL_GPL(vscaler_is_enabled); + +dpu_block_id_t vscaler_get_block_id(struct dpu_vscaler *vs) +{ + switch (vs->id) { + case 4: + return ID_VSCALER4; + case 5: + return ID_VSCALER5; + case 9: + return ID_VSCALER9; + default: + WARN_ON(1); + } + + return ID_NONE; +} +EXPORT_SYMBOL_GPL(vscaler_get_block_id); + +unsigned int vscaler_get_stream_id(struct dpu_vscaler *vs) +{ + return vs->stream_id; +} +EXPORT_SYMBOL_GPL(vscaler_get_stream_id); + +void vscaler_set_stream_id(struct dpu_vscaler *vs, unsigned int id) +{ + switch (id) { + case DPU_PLANE_SRC_TO_DISP_STREAM0: + case DPU_PLANE_SRC_TO_DISP_STREAM1: + case DPU_PLANE_SRC_DISABLED: + vs->stream_id = id; + break; + default: + WARN_ON(1); + } +} +EXPORT_SYMBOL_GPL(vscaler_set_stream_id); + +struct dpu_vscaler *dpu_vs_get(struct dpu_soc *dpu, int id) +{ + struct dpu_vscaler *vs; + int i; + + for (i = 0; i < ARRAY_SIZE(vs_ids); i++) + if (vs_ids[i] == id) + break; + + if (i == ARRAY_SIZE(vs_ids)) + return ERR_PTR(-EINVAL); + + vs = dpu->vs_priv[i]; + + mutex_lock(&vs->mutex); + + if (vs->inuse) { + mutex_unlock(&vs->mutex); + return ERR_PTR(-EBUSY); + } + + vs->inuse = true; + + mutex_unlock(&vs->mutex); + + return vs; +} +EXPORT_SYMBOL_GPL(dpu_vs_get); + +void dpu_vs_put(struct dpu_vscaler *vs) +{ + mutex_lock(&vs->mutex); + + vs->inuse = false; + + mutex_unlock(&vs->mutex); +} +EXPORT_SYMBOL_GPL(dpu_vs_put); + +void _dpu_vs_init(struct dpu_soc *dpu, unsigned int id) +{ + struct dpu_vscaler *vs; + int i; + + for (i = 0; i < ARRAY_SIZE(vs_ids); i++) + if (vs_ids[i] == id) + break; + + if (WARN_ON(i == ARRAY_SIZE(vs_ids))) + return; + + vs = dpu->vs_priv[i]; + + vscaler_shden(vs, true); + vscaler_setup2(vs, false); + vscaler_setup3(vs, false); + vscaler_setup4(vs, 0); + vscaler_setup5(vs, 0); + vscaler_pixengcfg_dynamic_src_sel(vs, VS_SRC_SEL__DISABLE); +} + +int dpu_vs_init(struct dpu_soc *dpu, unsigned int id, + unsigned long pec_base, unsigned long base) +{ + struct dpu_vscaler *vs; + int i; + + vs = devm_kzalloc(dpu->dev, sizeof(*vs), GFP_KERNEL); + if (!vs) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(vs_ids); i++) + if (vs_ids[i] == id) + break; + + if (i == ARRAY_SIZE(vs_ids)) + return -EINVAL; + + dpu->vs_priv[i] = vs; + + vs->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_8); + if (!vs->pec_base) + return -ENOMEM; + + vs->base = devm_ioremap(dpu->dev, base, SZ_1K); + if (!vs->base) + return -ENOMEM; + + vs->dpu = dpu; + vs->id = id; + + mutex_init(&vs->mutex); + + _dpu_vs_init(dpu, id); + + return 0; +} diff --git a/drivers/gpu/imx/imx8_dprc.c b/drivers/gpu/imx/imx8_dprc.c new file mode 100644 index 000000000000..fc7508c528a7 --- /dev/null +++ b/drivers/gpu/imx/imx8_dprc.c @@ -0,0 +1,893 @@ +/* + * Copyright 2017-2021 NXP + * + * 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 <drm/drm_fourcc.h> +#include <dt-bindings/firmware/imx/rsrc.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/firmware/imx/sci.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <video/imx8-prefetch.h> + +#define SET 0x4 +#define CLR 0x8 +#define TOG 0xc + +#define SYSTEM_CTRL0 0x00 +#define BCMD2AXI_MASTR_ID_CTRL BIT(16) +#define SW_SHADOW_LOAD_SEL BIT(4) +#define SHADOW_LOAD_EN BIT(3) +#define REPEAT_EN BIT(2) +#define SOFT_RESET BIT(1) +#define RUN_EN BIT(0) /* self-clearing */ + +#define IRQ_MASK 0x20 +#define IRQ_MASK_STATUS 0x30 +#define IRQ_NONMASK_STATUS 0x40 +#define DPR2RTR_FIFO_LOAD_BUF_RDY_UV_ERROR BIT(7) +#define DPR2RTR_FIFO_LOAD_BUF_RDY_YRGB_ERROR BIT(6) +#define DPR2RTR_UV_FIFO_OVFL BIT(5) +#define DPR2RTR_YRGB_FIFO_OVFL BIT(4) +#define IRQ_AXI_READ_ERROR BIT(3) +#define IRQ_DPR_SHADOW_LOADED_MASK BIT(2) +#define IRQ_DPR_RUN BIT(1) +#define IRQ_DPR_CRTL_DONE BIT(0) +#define IRQ_ERROR_MASK 0xf8 +#define IRQ_CTRL_MASK 0x7 + +#define MODE_CTRL0 0x50 +#define PIX_COMP_SEL_MASK 0x3fc00 +#define A_COMP_SEL(byte) (((byte) & 0x3) << 16) +#define R_COMP_SEL(byte) (((byte) & 0x3) << 14) +#define G_COMP_SEL(byte) (((byte) & 0x3) << 12) +#define B_COMP_SEL(byte) (((byte) & 0x3) << 10) +#define PIX_UV_SWAP BIT(9) +#define VU BIT(9) +#define UV 0 +#define PIXEL_LUMA_UV_SWAP BIT(8) +#define UYVY BIT(8) +#define YUYV 0 +#define PIX_SIZE 0xc0 +enum { + PIX_SIZE_8BIT = (0 << 6), + PIX_SIZE_16BIT = (1 << 6), + PIX_SIZE_32BIT = (2 << 6), + PIX_SIZE_RESERVED = (3 << 6), +}; +#define COMP_2PLANE_EN BIT(5) +#define YUV_EN BIT(4) +#define TILE_TYPE 0xc +enum { + LINEAR_TILE = (0 << 2), + GPU_STANDARD_TILE = (1 << 2), + GPU_SUPER_TILE = (2 << 2), + VPU_TILE = (3 << 2), +}; +#define RTR_4LINE_BUF_EN BIT(1) +#define LINE4 BIT(1) +#define LINE8 0 +#define RTR_3BUF_EN BIT(0) +#define BUF3 BIT(0) +#define BUF2 0 + +#define FRAME_CTRL0 0x70 +#define PITCH(n) (((n) & 0xffff) << 16) +#define ROT_FLIP_ORDER_EN BIT(4) +#define ROT_FIRST BIT(4) +#define FLIP_FIRST 0 +#define ROT_ENC 0xc +#define DEGREE(n) ((((n) / 90) & 0x3) << 2) +#define VFLIP_EN BIT(1) +#define HFLIP_EN BIT(0) + +#define FRAME_1P_CTRL0 0x90 +#define FRAME_2P_CTRL0 0xe0 +#define MAX_BYTES_PREQ 0x7 +enum { + BYTE_64 = 0x0, + BYTE_128 = 0x1, + BYTE_256 = 0x2, + BYTE_512 = 0x3, + BYTE_1K = 0x4, + BYTE_2K = 0x5, + BYTE_4K = 0x6, +}; + +#define FRAME_1P_PIX_X_CTRL 0xa0 +#define FRAME_2P_PIX_X_CTRL 0xf0 +#define NUM_X_PIX_WIDE(n) ((n) & 0xffff) +#define FRAME_PIX_X_ULC_CTRL 0xf0 +#define CROP_ULC_X(n) ((n) & 0xffff) + +#define FRAME_1P_PIX_Y_CTRL 0xb0 +#define FRAME_2P_PIX_Y_CTRL 0x100 +#define NUM_Y_PIX_HIGH(n) ((n) & 0xffff) +#define FRAME_PIX_Y_ULC_CTRL 0x100 +#define CROP_ULC_Y(n) ((n) & 0xffff) + +#define FRAME_1P_BASE_ADDR_CTRL0 0xc0 +#define FRAME_2P_BASE_ADDR_CTRL0 0x110 + +#define STATUS_CTRL0 0x130 +#define STATUS_SRC_SEL 0x70000 +enum { + DPR_CTRL = 0x0, + PREFETCH_1PLANE = 0x1, + RESPONSE_1PLANE = 0x2, + PREFETCH_2PLANE = 0x3, + RESPONSE_2PLANE = 0x4, +}; +#define STATUS_MUX_SEL 0x7 + +#define STATUS_CTRL1 0x140 + +#define RTRAM_CTRL0 0x200 +#define ABORT_SEL BIT(7) +#define ABORT BIT(7) +#define STALL 0 +#define THRES_LOW_MASK 0x70 +#define THRES_LOW(n) (((n) & 0x7) << 4) +#define THRES_HIGH_MASK 0xe +#define THRES_HIGH(n) (((n) & 0x7) << 1) +#define NUM_ROWS_ACTIVE BIT(0) +#define ROWS_0_6 BIT(0) +#define ROWS_0_4 0 + +struct dprc { + struct device *dev; + void __iomem *base; + struct list_head list; + struct clk *clk_apb; + struct clk *clk_b; + struct clk *clk_rtram; + struct imx_sc_ipc *ipc_handle; + spinlock_t spin_lock; + u32 sc_resource; + bool is_blit_chan; + + /* The second one, if non-NULL, is auxiliary for UV buffer. */ + struct prg *prgs[2]; + bool has_aux_prg; + bool use_aux_prg; +}; + +struct dprc_format_info { + u32 format; + u8 depth; + u8 num_planes; + u8 cpp[3]; + u8 hsub; + u8 vsub; +}; + +static const struct dprc_format_info formats[] = { + { + .format = DRM_FORMAT_RGB565, + .depth = 16, .num_planes = 1, .cpp = { 2, 0, 0 }, + .hsub = 1, .vsub = 1, + }, { + .format = DRM_FORMAT_ARGB8888, + .depth = 32, .num_planes = 1, .cpp = { 4, 0, 0 }, + .hsub = 1, .vsub = 1, + }, { + .format = DRM_FORMAT_XRGB8888, + .depth = 24, .num_planes = 1, .cpp = { 4, 0, 0 }, + .hsub = 1, .vsub = 1, + }, { + .format = DRM_FORMAT_ABGR8888, + .depth = 32, .num_planes = 1, .cpp = { 4, 0, 0 }, + .hsub = 1, .vsub = 1, + }, { + .format = DRM_FORMAT_XBGR8888, + .depth = 24, .num_planes = 1, .cpp = { 4, 0, 0 }, + .hsub = 1, .vsub = 1, + }, { + .format = DRM_FORMAT_RGBA8888, + .depth = 32, .num_planes = 1, .cpp = { 4, 0, 0 }, + .hsub = 1, .vsub = 1, + }, { + .format = DRM_FORMAT_RGBX8888, + .depth = 24, .num_planes = 1, .cpp = { 4, 0, 0 }, + .hsub = 1, .vsub = 1, + }, { + .format = DRM_FORMAT_BGRA8888, + .depth = 32, .num_planes = 1, .cpp = { 4, 0, 0 }, + .hsub = 1, .vsub = 1, + }, { + .format = DRM_FORMAT_BGRX8888, + .depth = 24, .num_planes = 1, .cpp = { 4, 0, 0 }, + .hsub = 1, .vsub = 1, + }, { + .format = DRM_FORMAT_NV12, + .depth = 0, .num_planes = 2, .cpp = { 1, 2, 0 }, + .hsub = 2, .vsub = 2, + }, { + .format = DRM_FORMAT_NV21, + .depth = 0, .num_planes = 2, .cpp = { 1, 2, 0 }, + .hsub = 2, .vsub = 2, + }, { + .format = DRM_FORMAT_YUYV, + .depth = 0, .num_planes = 1, .cpp = { 2, 0, 0 }, + .hsub = 2, .vsub = 1, + }, { + .format = DRM_FORMAT_UYVY, + .depth = 0, .num_planes = 1, .cpp = { 2, 0, 0 }, + .hsub = 2, .vsub = 1, + } +}; + +static const struct dprc_format_info *dprc_format_info(u32 format) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); ++i) { + if (formats[i].format == format) + return &formats[i]; + } + + return NULL; +} + +static DEFINE_MUTEX(dprc_list_mutex); +static LIST_HEAD(dprc_list); + +static inline u32 dprc_read(struct dprc *dprc, unsigned int offset) +{ + return readl(dprc->base + offset); +} + +static inline void dprc_write(struct dprc *dprc, u32 value, unsigned int offset) +{ + writel(value, dprc->base + offset); +} + +static void dprc_reset(struct dprc *dprc) +{ + dprc_write(dprc, SOFT_RESET, SYSTEM_CTRL0 + SET); + + if (dprc->is_blit_chan) + usleep_range(10, 20); + else + usleep_range(1000, 2000); + + dprc_write(dprc, SOFT_RESET, SYSTEM_CTRL0 + CLR); +} + +void dprc_enable(struct dprc *dprc) +{ + if (WARN_ON(!dprc)) + return; + + prg_enable(dprc->prgs[0]); + if (dprc->use_aux_prg) + prg_enable(dprc->prgs[1]); +} +EXPORT_SYMBOL_GPL(dprc_enable); + +void dprc_disable(struct dprc *dprc) +{ + if (WARN_ON(!dprc)) + return; + + dprc_write(dprc, SHADOW_LOAD_EN | SW_SHADOW_LOAD_SEL, SYSTEM_CTRL0); + + prg_disable(dprc->prgs[0]); + if (dprc->has_aux_prg) + prg_disable(dprc->prgs[1]); + + prg_reg_update(dprc->prgs[0]); + if (dprc->has_aux_prg) + prg_reg_update(dprc->prgs[1]); +} +EXPORT_SYMBOL_GPL(dprc_disable); + +static inline void +dprc_dpu_gpr_configure(struct dprc *dprc, unsigned int stream_id) +{ + int ret; + + ret = imx_sc_misc_set_control(dprc->ipc_handle, + dprc->sc_resource, IMX_SC_C_KACHUNK_SEL, stream_id); + if (ret) + dev_warn(dprc->dev, "failed to set KACHUNK_SEL: %d\n", ret); +} + +static inline void +dprc_prg_sel_configure(struct dprc *dprc, u32 resource, bool enable) +{ + int ret; + + ret = imx_sc_misc_set_control(dprc->ipc_handle, + resource, IMX_SC_C_SEL0, enable); + if (ret) + dev_warn(dprc->dev, "failed to set SEL0: %d\n", ret); +} + +void dprc_configure(struct dprc *dprc, unsigned int stream_id, + unsigned int width, unsigned int height, + unsigned int x_offset, unsigned int y_offset, + unsigned int stride, u32 format, u64 modifier, + unsigned long baddr, unsigned long uv_baddr, + bool start, bool aux_start, bool interlace_frame) +{ + const struct dprc_format_info *info = dprc_format_info(format); + unsigned int dprc_width = width + x_offset; + unsigned int dprc_height; + unsigned int p1_w, p1_h, p2_w, p2_h; + unsigned int prg_stride = width * info->cpp[0]; + unsigned int bpp = 8 * info->cpp[0]; + unsigned int preq; + unsigned int mt_w = 0, mt_h = 0; /* w/h in a micro-tile */ + u32 val; + + if (WARN_ON(!dprc)) + return; + + dprc->use_aux_prg = false; + + if (start) { + dprc_reset(dprc); + + if (!dprc->is_blit_chan) + dprc_dpu_gpr_configure(dprc, stream_id); + } + + if (interlace_frame) { + height /= 2; + y_offset /= 2; + } + + dprc_height = height + y_offset; + + /* disable all control irqs and enable all error irqs */ + dprc_write(dprc, IRQ_CTRL_MASK, IRQ_MASK); + + if (info->num_planes > 1) { + p1_w = round_up(dprc_width, modifier ? 8 : 64); + p1_h = round_up(dprc_height, 8); + + p2_w = p1_w; + if (modifier) + p2_h = dprc_height / info->vsub; + else + p2_h = round_up((dprc_height / info->vsub), 8); + + preq = modifier ? BYTE_64 : BYTE_1K; + + dprc_write(dprc, preq, FRAME_2P_CTRL0); + if (dprc->sc_resource == IMX_SC_R_DC_0_BLIT1 || + dprc->sc_resource == IMX_SC_R_DC_1_BLIT1) { + dprc_prg_sel_configure(dprc, + dprc->sc_resource == IMX_SC_R_DC_0_BLIT1 ? + IMX_SC_R_DC_0_BLIT0 : IMX_SC_R_DC_1_BLIT0, + true); + prg_set_auxiliary(dprc->prgs[1]); + dprc->has_aux_prg = true; + } + dprc_write(dprc, uv_baddr, FRAME_2P_BASE_ADDR_CTRL0); + } else { + switch (dprc->sc_resource) { + case IMX_SC_R_DC_0_BLIT0: + case IMX_SC_R_DC_1_BLIT0: + dprc_prg_sel_configure(dprc, dprc->sc_resource, false); + prg_set_primary(dprc->prgs[0]); + break; + case IMX_SC_R_DC_0_BLIT1: + case IMX_SC_R_DC_1_BLIT1: + dprc->has_aux_prg = false; + break; + default: + break; + } + + switch (modifier) { + case DRM_FORMAT_MOD_VIVANTE_TILED: + p1_w = round_up(dprc_width, info->cpp[0] == 2 ? 8 : 4); + break; + case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED: + if (dprc->is_blit_chan) + p1_w = round_up(dprc_width, + info->cpp[0] == 2 ? 8 : 4); + else + p1_w = round_up(dprc_width, 64); + break; + default: + p1_w = round_up(dprc_width, + info->cpp[0] == 2 ? 32 : 16); + break; + } + p1_h = round_up(dprc_height, 4); + } + + dprc_write(dprc, PITCH(stride), FRAME_CTRL0); + switch (modifier) { + case DRM_FORMAT_MOD_AMPHION_TILED: + preq = BYTE_64; + mt_w = 8; + mt_h = 8; + break; + case DRM_FORMAT_MOD_VIVANTE_TILED: + preq = BYTE_256; + mt_w = bpp == 16 ? 8 : 4; + mt_h = 4; + break; + case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED: + if (bpp == 16) { + preq = BYTE_64; + mt_w = 8; + } else { + preq = (x_offset % 8) ? BYTE_64 : BYTE_128; + mt_w = 4; + } + mt_h = 4; + break; + default: + preq = BYTE_1K; + break; + } + dprc_write(dprc, preq, FRAME_1P_CTRL0); + dprc_write(dprc, NUM_X_PIX_WIDE(p1_w), FRAME_1P_PIX_X_CTRL); + dprc_write(dprc, NUM_Y_PIX_HIGH(p1_h), FRAME_1P_PIX_Y_CTRL); + dprc_write(dprc, baddr, FRAME_1P_BASE_ADDR_CTRL0); + if (modifier) { + dprc_write(dprc, CROP_ULC_X(round_down(x_offset, mt_w)), + FRAME_PIX_X_ULC_CTRL); + dprc_write(dprc, CROP_ULC_Y(round_down(y_offset, mt_h)), + FRAME_PIX_Y_ULC_CTRL); + } else { + dprc_write(dprc, CROP_ULC_X(0), FRAME_PIX_X_ULC_CTRL); + dprc_write(dprc, CROP_ULC_Y(0), FRAME_PIX_Y_ULC_CTRL); + } + + val = dprc_read(dprc, RTRAM_CTRL0); + val &= ~THRES_LOW_MASK; + val |= THRES_LOW(3); + val &= ~THRES_HIGH_MASK; + val |= THRES_HIGH(7); + dprc_write(dprc, val, RTRAM_CTRL0); + + val = dprc_read(dprc, MODE_CTRL0); + val &= ~PIX_UV_SWAP; + val &= ~PIXEL_LUMA_UV_SWAP; + val &= ~COMP_2PLANE_EN; + val &= ~YUV_EN; + val &= ~TILE_TYPE; + switch (modifier) { + case DRM_FORMAT_MOD_NONE: + break; + case DRM_FORMAT_MOD_AMPHION_TILED: + val |= VPU_TILE; + break; + case DRM_FORMAT_MOD_VIVANTE_TILED: + val |= GPU_STANDARD_TILE; + break; + case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED: + val |= GPU_SUPER_TILE; + break; + default: + dev_err(dprc->dev, "unsupported modifier 0x%016llx\n", + modifier); + return; + } + val &= ~RTR_4LINE_BUF_EN; + val |= info->num_planes > 1 ? LINE8 : LINE4; + val &= ~RTR_3BUF_EN; + val |= BUF2; + val &= ~(PIX_COMP_SEL_MASK | PIX_SIZE); + switch (format) { + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_BGRX8888: + /* + * It turns out pixel components are mapped directly + * without position change via DPR processing with + * the following color component configurations. + * Leave the pixel format to be handled by the + * display controllers. + */ + val |= A_COMP_SEL(3) | R_COMP_SEL(2) | + G_COMP_SEL(1) | B_COMP_SEL(0); + val |= PIX_SIZE_32BIT; + break; + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + val |= YUV_EN; + fallthrough; + case DRM_FORMAT_RGB565: + val |= PIX_SIZE_16BIT; + break; + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + dprc->use_aux_prg = true; + + val |= COMP_2PLANE_EN; + val |= YUV_EN; + val |= PIX_SIZE_8BIT; + break; + default: + dev_err(dprc->dev, "unsupported format 0x%08x\n", format); + return; + } + dprc_write(dprc, val, MODE_CTRL0); + + if (dprc->is_blit_chan) { + val = SW_SHADOW_LOAD_SEL | RUN_EN | SHADOW_LOAD_EN; + dprc_write(dprc, val, SYSTEM_CTRL0); + } else if (start) { + /* software shadow load for the first frame */ + val = SW_SHADOW_LOAD_SEL | SHADOW_LOAD_EN; + dprc_write(dprc, val, SYSTEM_CTRL0); + + /* and then, run... */ + val |= RUN_EN | REPEAT_EN; + dprc_write(dprc, val, SYSTEM_CTRL0); + } + + prg_configure(dprc->prgs[0], width, height, x_offset, y_offset, + prg_stride, bpp, baddr, format, modifier, start); + if (dprc->use_aux_prg) + prg_configure(dprc->prgs[1], width, height, x_offset, y_offset, + prg_stride, 8, uv_baddr, format, modifier, aux_start); + + dev_dbg(dprc->dev, "w-%u, h-%u, s-%u, fmt-0x%08x, mod-0x%016llx\n", + width, height, stride, format, modifier); +} +EXPORT_SYMBOL_GPL(dprc_configure); + +void dprc_disable_repeat_en(struct dprc *dprc) +{ + if (WARN_ON(!dprc)) + return; + + dprc_write(dprc, REPEAT_EN, SYSTEM_CTRL0 + CLR); +} +EXPORT_SYMBOL_GPL(dprc_disable_repeat_en); + +void dprc_reg_update(struct dprc *dprc) +{ + if (WARN_ON(!dprc)) + return; + + prg_reg_update(dprc->prgs[0]); + if (dprc->use_aux_prg) + prg_reg_update(dprc->prgs[1]); +} +EXPORT_SYMBOL_GPL(dprc_reg_update); + +void dprc_first_frame_handle(struct dprc *dprc) +{ + if (WARN_ON(!dprc)) + return; + + if (dprc->is_blit_chan) + return; + + dprc_write(dprc, REPEAT_EN, SYSTEM_CTRL0); + + prg_shadow_enable(dprc->prgs[0]); + if (dprc->use_aux_prg) + prg_shadow_enable(dprc->prgs[1]); +} +EXPORT_SYMBOL_GPL(dprc_first_frame_handle); + +void dprc_irq_handle(struct dprc *dprc) +{ + u32 mask, status; + + if (WARN_ON(!dprc)) + return; + + spin_lock(&dprc->spin_lock); + + mask = dprc_read(dprc, IRQ_MASK); + mask = ~mask; + status = dprc_read(dprc, IRQ_MASK_STATUS); + status &= mask; + + /* disable irqs to be handled */ + dprc_write(dprc, status, IRQ_MASK + SET); + + /* clear status */ + dprc_write(dprc, status, IRQ_MASK_STATUS); + + if (status & DPR2RTR_FIFO_LOAD_BUF_RDY_UV_ERROR) + dev_err(dprc->dev, + "DPR to RTRAM FIFO load UV buffer ready error\n"); + + if (status & DPR2RTR_FIFO_LOAD_BUF_RDY_YRGB_ERROR) + dev_err(dprc->dev, + "DPR to RTRAM FIFO load YRGB buffer ready error\n"); + + if (status & DPR2RTR_UV_FIFO_OVFL) + dev_err(dprc->dev, "DPR to RTRAM FIFO UV FIFO overflow\n"); + + if (status & DPR2RTR_YRGB_FIFO_OVFL) + dev_err(dprc->dev, "DPR to RTRAM FIFO YRGB FIFO overflow\n"); + + if (status & IRQ_AXI_READ_ERROR) + dev_err(dprc->dev, "AXI read error\n"); + + if (status & IRQ_DPR_CRTL_DONE) + dprc_first_frame_handle(dprc); + + spin_unlock(&dprc->spin_lock); +} +EXPORT_SYMBOL_GPL(dprc_irq_handle); + +void dprc_enable_ctrl_done_irq(struct dprc *dprc) +{ + unsigned long lock_flags; + + if (WARN_ON(!dprc)) + return; + + spin_lock_irqsave(&dprc->spin_lock, lock_flags); + dprc_write(dprc, IRQ_DPR_CRTL_DONE, IRQ_MASK + CLR); + spin_unlock_irqrestore(&dprc->spin_lock, lock_flags); +} +EXPORT_SYMBOL_GPL(dprc_enable_ctrl_done_irq); + +bool dprc_format_supported(struct dprc *dprc, u32 format, u64 modifier) +{ + if (WARN_ON(!dprc)) + return false; + + switch (format) { + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_RGB565: + return (modifier == DRM_FORMAT_MOD_NONE || + modifier == DRM_FORMAT_MOD_VIVANTE_TILED || + modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED); + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + switch (dprc->sc_resource) { + case IMX_SC_R_DC_0_FRAC0: + case IMX_SC_R_DC_1_FRAC0: + case IMX_SC_R_DC_0_WARP: + case IMX_SC_R_DC_1_WARP: + return false; + } + return modifier == DRM_FORMAT_MOD_NONE; + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + switch (dprc->sc_resource) { + case IMX_SC_R_DC_0_FRAC0: + case IMX_SC_R_DC_1_FRAC0: + case IMX_SC_R_DC_0_WARP: + case IMX_SC_R_DC_1_WARP: + return false; + case IMX_SC_R_DC_0_BLIT1: + case IMX_SC_R_DC_1_BLIT1: + return (modifier == DRM_FORMAT_MOD_NONE || + modifier == DRM_FORMAT_MOD_AMPHION_TILED); + } + return (dprc->has_aux_prg && + (modifier == DRM_FORMAT_MOD_NONE || + modifier == DRM_FORMAT_MOD_AMPHION_TILED)); + } + + return false; +} +EXPORT_SYMBOL_GPL(dprc_format_supported); + +bool dprc_stride_supported(struct dprc *dprc, + unsigned int stride, unsigned int uv_stride, + unsigned int width, u32 format) +{ + const struct dprc_format_info *info = dprc_format_info(format); + unsigned int prg_stride = width * info->cpp[0]; + + if (WARN_ON(!dprc)) + return false; + + if (stride > 0xffff) + return false; + + if (info->num_planes > 1 && stride != uv_stride) + return false; + + return prg_stride_supported(dprc->prgs[0], prg_stride); +} +EXPORT_SYMBOL_GPL(dprc_stride_supported); + +bool dprc_stride_double_check(struct dprc *dprc, + unsigned int width, unsigned int x_offset, + u32 format, u64 modifier, + dma_addr_t baddr, dma_addr_t uv_baddr) +{ + const struct dprc_format_info *info = dprc_format_info(format); + unsigned int bpp = 8 * info->cpp[0]; + unsigned int prg_stride = width * info->cpp[0]; + + if (WARN_ON(!dprc)) + return false; + + if (!prg_stride_double_check(dprc->prgs[0], width, x_offset, + bpp, modifier, prg_stride, baddr)) + return false; + + if (info->num_planes > 1 && + !prg_stride_double_check(dprc->prgs[1], width, x_offset, + bpp, modifier, prg_stride, uv_baddr)) + return false; + + return true; +} +EXPORT_SYMBOL_GPL(dprc_stride_double_check); + +struct dprc * +dprc_lookup_by_phandle(struct device *dev, const char *name, int index) +{ + struct device_node *dprc_node = of_parse_phandle(dev->of_node, + name, index); + struct dprc *dprc; + + mutex_lock(&dprc_list_mutex); + list_for_each_entry(dprc, &dprc_list, list) { + if (dprc_node == dprc->dev->of_node) { + mutex_unlock(&dprc_list_mutex); + device_link_add(dev, dprc->dev, + DL_FLAG_AUTOREMOVE_CONSUMER); + return dprc; + } + } + mutex_unlock(&dprc_list_mutex); + + return NULL; +} +EXPORT_SYMBOL_GPL(dprc_lookup_by_phandle); + +static const struct of_device_id dprc_dt_ids[] = { + { .compatible = "fsl,imx8qm-dpr-channel", }, + { .compatible = "fsl,imx8qxp-dpr-channel", }, + { /* sentinel */ }, +}; + +static int dprc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct dprc *dprc; + int ret, i; + + dprc = devm_kzalloc(dev, sizeof(*dprc), GFP_KERNEL); + if (!dprc) + return -ENOMEM; + + ret = imx_scu_get_handle(&dprc->ipc_handle); + if (ret) + return ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dprc->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dprc->base)) + return PTR_ERR(dprc->base); + + dprc->clk_apb = devm_clk_get(dev, "apb"); + if (IS_ERR(dprc->clk_apb)) + return PTR_ERR(dprc->clk_apb); + clk_prepare_enable(dprc->clk_apb); + + dprc->clk_b = devm_clk_get(dev, "b"); + if (IS_ERR(dprc->clk_b)) + return PTR_ERR(dprc->clk_b); + clk_prepare_enable(dprc->clk_b); + + dprc->clk_rtram = devm_clk_get(dev, "rtram"); + if (IS_ERR(dprc->clk_rtram)) + return PTR_ERR(dprc->clk_rtram); + clk_prepare_enable(dprc->clk_rtram); + + ret = of_property_read_u32(pdev->dev.of_node, + "fsl,sc-resource", &dprc->sc_resource); + if (ret) { + dev_err(dev, "cannot get SC resource %d\n", ret); + return ret; + } + + switch (dprc->sc_resource) { + case IMX_SC_R_DC_0_BLIT1: + case IMX_SC_R_DC_1_BLIT1: + dprc->has_aux_prg = true; + fallthrough; + case IMX_SC_R_DC_0_BLIT0: + case IMX_SC_R_DC_1_BLIT0: + dprc->is_blit_chan = true; + fallthrough; + case IMX_SC_R_DC_0_FRAC0: + case IMX_SC_R_DC_1_FRAC0: + break; + case IMX_SC_R_DC_0_VIDEO0: + case IMX_SC_R_DC_0_VIDEO1: + case IMX_SC_R_DC_1_VIDEO0: + case IMX_SC_R_DC_1_VIDEO1: + case IMX_SC_R_DC_0_WARP: + case IMX_SC_R_DC_1_WARP: + dprc->has_aux_prg = true; + break; + default: + dev_err(dev, "wrong SC resource %u\n", dprc->sc_resource); + return -EINVAL; + } + + for (i = 0; i < 2; i++) { + if (i == 1 && !dprc->has_aux_prg) + break; + + dprc->prgs[i] = prg_lookup_by_phandle(dev, "fsl,prgs", i); + if (!dprc->prgs[i]) + return -EPROBE_DEFER; + + if (i == 1) + prg_set_auxiliary(dprc->prgs[i]); + + if (dprc->is_blit_chan) + prg_set_blit(dprc->prgs[i]); + } + + dprc->dev = dev; + spin_lock_init(&dprc->spin_lock); + platform_set_drvdata(pdev, dprc); + mutex_lock(&dprc_list_mutex); + list_add(&dprc->list, &dprc_list); + mutex_unlock(&dprc_list_mutex); + + dprc_reset(dprc); + + return 0; +} + +static int dprc_remove(struct platform_device *pdev) +{ + struct dprc *dprc = platform_get_drvdata(pdev); + + mutex_lock(&dprc_list_mutex); + list_del(&dprc->list); + mutex_unlock(&dprc_list_mutex); + + clk_disable_unprepare(dprc->clk_rtram); + clk_disable_unprepare(dprc->clk_b); + clk_disable_unprepare(dprc->clk_apb); + + return 0; +} + +struct platform_driver dprc_drv = { + .probe = dprc_probe, + .remove = dprc_remove, + .driver = { + .name = "imx8-dpr-channel", + .of_match_table = dprc_dt_ids, + }, +}; +module_platform_driver(dprc_drv); + +MODULE_DESCRIPTION("i.MX8 DPRC driver"); +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/imx/imx8_pc.c b/drivers/gpu/imx/imx8_pc.c new file mode 100644 index 000000000000..f5386b9f6193 --- /dev/null +++ b/drivers/gpu/imx/imx8_pc.c @@ -0,0 +1,218 @@ +/* + * Copyright 2018,2019 NXP + * + * 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 <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <video/imx8-pc.h> + +#define REG0 0x0 +#define PIX_COMBINE_ENABLE BIT(0) +#define DISP_PIX_COMBINE_BYPASS(n) BIT(1 + 21 * (n)) +#define DISP_HSYNC_POLARITY(n) BIT(2 + 11 * (n)) +#define DISP_HSYNC_POLARITY_POS(n) DISP_HSYNC_POLARITY(n) +#define DISP_VSYNC_POLARITY(n) BIT(3 + 11 * (n)) +#define DISP_VSYNC_POLARITY_POS(n) DISP_VSYNC_POLARITY(n) +#define DISP_DVALID_POLARITY(n) BIT(4 + 11 * (n)) +#define DISP_DVALID_POLARITY_POS(n) DISP_DVALID_POLARITY(n) +#define VSYNC_MASK_ENABLE BIT(5) +#define SKIP_MODE BIT(6) +#define SKIP_NUMBER(n) (((n) & 0x3F) << 7) +#define DISP_PIX_DATA_FORMAT_MASK(n) (0x7 << (16 + (n) * 3)) +#define DISP_PIX_DATA_FORMAT_SHIFT(n) (16 + (n) * 3) +enum { + RGB = 0, + YUV444, + YUV422, + SPLIT_RGB, +}; + +#define REG1 0x10 +#define BUF_ACTIVE_DEPTH(n) ((n) & 0x7FF) + +#define REG2 0x20 +#define PC_SW_RESET_N BIT(0) +#define DISP_SW_RESET_N(n) BIT(1 + (n)) +#define PC_FULL_RESET_N (PC_SW_RESET_N | \ + DISP_SW_RESET_N(0) | \ + DISP_SW_RESET_N(1)) + +struct pc { + struct device *dev; + void __iomem *base; + struct list_head list; +}; + +static DEFINE_MUTEX(pc_list_mutex); +static LIST_HEAD(pc_list); + +static inline u32 pc_read(struct pc *pc, unsigned int offset) +{ + return readl(pc->base + offset); +} + +static inline void pc_write(struct pc *pc, unsigned int offset, u32 value) +{ + writel(value, pc->base + offset); +} + +static void pc_reset(struct pc *pc) +{ + pc_write(pc, REG2, 0); + usleep_range(1000, 2000); + pc_write(pc, REG2, PC_FULL_RESET_N); +} + +void pc_enable(struct pc *pc) +{ + u32 val; + + if (WARN_ON(!pc)) + return; + + val = pc_read(pc, REG0); + val |= PIX_COMBINE_ENABLE; + pc_write(pc, REG0, val); + + dev_dbg(pc->dev, "enable\n"); +} +EXPORT_SYMBOL_GPL(pc_enable); + +void pc_disable(struct pc *pc) +{ + if (WARN_ON(!pc)) + return; + + pc_reset(pc); + + dev_dbg(pc->dev, "disable\n"); +} +EXPORT_SYMBOL_GPL(pc_disable); + +void pc_configure(struct pc *pc, unsigned int di, unsigned int frame_width, + u32 mode, u32 format) +{ + u32 val; + + if (WARN_ON(!pc)) + return; + + if (WARN_ON(di != 0 && di != 1)) + return; + + dev_dbg(pc->dev, "configure mode-0x%08x frame_width-%u\n", + mode, frame_width); + + val = pc_read(pc, REG0); + if (mode == PC_BYPASS) { + val |= DISP_PIX_COMBINE_BYPASS(di); + } else if (mode == PC_COMBINE) { + val &= ~DISP_PIX_COMBINE_BYPASS(di); + frame_width /= 4; + } + + pc_write(pc, REG0, val); + pc_write(pc, REG1, BUF_ACTIVE_DEPTH(frame_width)); +} +EXPORT_SYMBOL_GPL(pc_configure); + +struct pc *pc_lookup_by_phandle(struct device *dev, const char *name) +{ + struct device_node *pc_node = of_parse_phandle(dev->of_node, + name, 0); + struct pc *pc; + + mutex_lock(&pc_list_mutex); + list_for_each_entry(pc, &pc_list, list) { + if (pc_node == pc->dev->of_node) { + mutex_unlock(&pc_list_mutex); + device_link_add(dev, pc->dev, + DL_FLAG_AUTOREMOVE_CONSUMER); + return pc; + } + } + mutex_unlock(&pc_list_mutex); + + return NULL; +} +EXPORT_SYMBOL_GPL(pc_lookup_by_phandle); + +static int pc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct pc *pc; + u32 val; + + pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pc->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pc->base)) + return PTR_ERR(pc->base); + + pc->dev = dev; + platform_set_drvdata(pdev, pc); + mutex_lock(&pc_list_mutex); + list_add(&pc->list, &pc_list); + mutex_unlock(&pc_list_mutex); + + pc_reset(pc); + + /* + * assume data enable is active high and HSYNC/VSYNC are active low + * also, bypass combine at startup + */ + val = DISP_DVALID_POLARITY_POS(0) | DISP_DVALID_POLARITY_POS(1) | + DISP_PIX_COMBINE_BYPASS(0) | DISP_PIX_COMBINE_BYPASS(1) | + VSYNC_MASK_ENABLE; + + pc_write(pc, REG0, val); + + return 0; +} + +static int pc_remove(struct platform_device *pdev) +{ + struct pc *pc = platform_get_drvdata(pdev); + + mutex_lock(&pc_list_mutex); + list_del(&pc->list); + mutex_unlock(&pc_list_mutex); + + return 0; +} + +static const struct of_device_id pc_dt_ids[] = { + { .compatible = "fsl,imx8qm-pixel-combiner", }, + { .compatible = "fsl,imx8qxp-pixel-combiner", }, + { /* sentinel */ }, +}; + +struct platform_driver pc_drv = { + .probe = pc_probe, + .remove = pc_remove, + .driver = { + .name = "imx8-pixel-combiner", + .of_match_table = pc_dt_ids, + }, +}; +module_platform_driver(pc_drv); + +MODULE_DESCRIPTION("i.MX8 Pixel Combiner driver"); +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/imx/imx8_prg.c b/drivers/gpu/imx/imx8_prg.c new file mode 100644 index 000000000000..4dbcb1cb909a --- /dev/null +++ b/drivers/gpu/imx/imx8_prg.c @@ -0,0 +1,452 @@ +/* + * Copyright 2017-2019 NXP + * + * 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 <drm/drm_fourcc.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <video/imx8-prefetch.h> + +#define SET 0x4 +#define CLR 0x8 +#define TOG 0xc + +#define PRG_CTRL 0x00 +#define BYPASS BIT(0) +#define SC_DATA_TYPE BIT(2) +#define SC_DATA_TYPE_8BIT 0 +#define SC_DATA_TYPE_10BIT BIT(2) +#define UV_EN BIT(3) +#define HANDSHAKE_MODE BIT(4) +#define HANDSHAKE_MODE_4LINES 0 +#define HANDSHAKE_MODE_8LINES BIT(4) +#define SHADOW_LOAD_MODE BIT(5) +#define DES_DATA_TYPE 0x30000 +enum { + DES_DATA_TYPE_32BPP = (0 << 16), + DES_DATA_TYPE_24BPP = (1 << 16), + DES_DATA_TYPE_16BPP = (2 << 16), + DES_DATA_TYPE_8BPP = (3 << 16), +}; +#define SOFTRST BIT(30) +#define SHADOW_EN BIT(31) + +#define PRG_STATUS 0x10 +#define BUFFER_VALID_B BIT(1) +#define BUFFER_VALID_A BIT(0) + +#define PRG_REG_UPDATE 0x20 +#define REG_UPDATE BIT(0) + +#define PRG_STRIDE 0x30 +#define STRIDE(n) (((n) - 1) & 0xffff) + +#define PRG_HEIGHT 0x40 +#define HEIGHT(n) (((n) - 1) & 0xffff) + +#define PRG_BADDR 0x50 + +#define PRG_OFFSET 0x60 +#define Y(n) (((n) & 0x7) << 16) +#define X(n) ((n) & 0xffff) + +#define PRG_WIDTH 0x70 +#define WIDTH(n) (((n) - 1) & 0xffff) + +struct prg { + struct device *dev; + void __iomem *base; + struct list_head list; + struct clk *clk_apb; + struct clk *clk_rtram; + bool is_auxiliary; + bool is_blit; +}; + +static DEFINE_MUTEX(prg_list_mutex); +static LIST_HEAD(prg_list); + +static inline u32 prg_read(struct prg *prg, unsigned int offset) +{ + return readl(prg->base + offset); +} + +static inline void prg_write(struct prg *prg, u32 value, unsigned int offset) +{ + writel(value, prg->base + offset); +} + +static void prg_reset(struct prg *prg) +{ + if (prg->is_blit) + usleep_range(10, 20); + + prg_write(prg, SOFTRST, PRG_CTRL + SET); + + if (prg->is_blit) + usleep_range(10, 20); + else + usleep_range(1000, 2000); + + prg_write(prg, SOFTRST, PRG_CTRL + CLR); +} + +void prg_enable(struct prg *prg) +{ + if (WARN_ON(!prg)) + return; + + prg_write(prg, BYPASS, PRG_CTRL + CLR); +} +EXPORT_SYMBOL_GPL(prg_enable); + +void prg_disable(struct prg *prg) +{ + if (WARN_ON(!prg)) + return; + + prg_write(prg, BYPASS, PRG_CTRL); +} +EXPORT_SYMBOL_GPL(prg_disable); + +void prg_configure(struct prg *prg, unsigned int width, unsigned int height, + unsigned int x_offset, unsigned int y_offset, + unsigned int stride, unsigned int bits_per_pixel, + unsigned long baddr, u32 format, u64 modifier, + bool start) +{ + unsigned int burst_size; + unsigned int mt_w = 0, mt_h = 0; /* w/h in a micro-tile */ + unsigned long _baddr; + u32 val; + + if (WARN_ON(!prg)) + return; + + if (start) + prg_reset(prg); + + /* prg finer cropping into micro-tile block - top/left start point */ + switch (modifier) { + case DRM_FORMAT_MOD_NONE: + break; + case DRM_FORMAT_MOD_AMPHION_TILED: + mt_w = 8; + mt_h = 8; + break; + case DRM_FORMAT_MOD_VIVANTE_TILED: + case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED: + mt_w = (bits_per_pixel == 16) ? 8 : 4; + mt_h = 4; + break; + default: + dev_err(prg->dev, "unsupported modifier 0x%016llx\n", modifier); + return; + } + + if (modifier) { + x_offset %= mt_w; + y_offset %= mt_h; + + /* consider x offset to calculate stride */ + _baddr = baddr + (x_offset * (bits_per_pixel / 8)); + } else { + x_offset = 0; + y_offset = 0; + _baddr = baddr; + } + + /* + * address TKT343664: + * fetch unit base address has to align to burst_size + */ + burst_size = 1 << (ffs(_baddr) - 1); + burst_size = round_up(burst_size, 8); + burst_size = min(burst_size, 128U); + + /* + * address TKT339017: + * fixup for burst size vs stride mismatch + */ + if (modifier) + stride = round_up(stride + round_up(_baddr % 8, 8), burst_size); + else + stride = round_up(stride, burst_size); + + /* + * address TKT342628(part 1): + * when prg stride is less or equals to burst size, + * the auxiliary prg height needs to be a half + */ + if (prg->is_auxiliary && stride <= burst_size) { + height /= 2; + if (modifier) + y_offset /= 2; + } + + prg_write(prg, STRIDE(stride), PRG_STRIDE); + prg_write(prg, WIDTH(width), PRG_WIDTH); + prg_write(prg, HEIGHT(height), PRG_HEIGHT); + prg_write(prg, X(x_offset) | Y(y_offset), PRG_OFFSET); + prg_write(prg, baddr, PRG_BADDR); + + val = prg_read(prg, PRG_CTRL); + val &= ~SC_DATA_TYPE; + val |= SC_DATA_TYPE_8BIT; + val &= ~HANDSHAKE_MODE; + if (format == DRM_FORMAT_NV21 || format == DRM_FORMAT_NV12) { + val |= HANDSHAKE_MODE_8LINES; + /* + * address TKT342628(part 2): + * when prg stride is less or equals to burst size, + * we disable UV_EN bit for the auxiliary prg + */ + if (prg->is_auxiliary && stride > burst_size) + val |= UV_EN; + else + val &= ~UV_EN; + } else { + val |= HANDSHAKE_MODE_4LINES; + val &= ~UV_EN; + } + val |= SHADOW_LOAD_MODE; + val &= ~DES_DATA_TYPE; + switch (bits_per_pixel) { + case 32: + val |= DES_DATA_TYPE_32BPP; + break; + case 24: + val |= DES_DATA_TYPE_24BPP; + break; + case 16: + val |= DES_DATA_TYPE_16BPP; + break; + case 8: + val |= DES_DATA_TYPE_8BPP; + break; + } + if (start) + /* no shadow for the first frame */ + val &= ~SHADOW_EN; + else + val |= SHADOW_EN; + prg_write(prg, val, PRG_CTRL); + + dev_dbg(prg->dev, "bits per pixel %u\n", bits_per_pixel); +} +EXPORT_SYMBOL_GPL(prg_configure); + +void prg_reg_update(struct prg *prg) +{ + if (WARN_ON(!prg)) + return; + + prg_write(prg, REG_UPDATE, PRG_REG_UPDATE); +} +EXPORT_SYMBOL_GPL(prg_reg_update); + +void prg_shadow_enable(struct prg *prg) +{ + if (WARN_ON(!prg)) + return; + + prg_write(prg, SHADOW_EN, PRG_CTRL + SET); +} +EXPORT_SYMBOL_GPL(prg_shadow_enable); + +bool prg_stride_supported(struct prg *prg, unsigned int stride) +{ + return stride < 0x10000; +} +EXPORT_SYMBOL_GPL(prg_stride_supported); + +bool prg_stride_double_check(struct prg *prg, + unsigned int width, unsigned int x_offset, + unsigned int bits_per_pixel, u64 modifier, + unsigned int stride, dma_addr_t baddr) +{ + unsigned int burst_size; + unsigned int mt_w = 0; /* w in a micro-tile */ + dma_addr_t _baddr; + + if (WARN_ON(!prg)) + return false; + + /* prg finer cropping into micro-tile block - top/left start point */ + switch (modifier) { + case DRM_FORMAT_MOD_NONE: + break; + case DRM_FORMAT_MOD_AMPHION_TILED: + mt_w = 8; + break; + case DRM_FORMAT_MOD_VIVANTE_TILED: + case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED: + mt_w = (bits_per_pixel == 16) ? 8 : 4; + break; + default: + dev_err(prg->dev, "unsupported modifier 0x%016llx\n", modifier); + return false; + } + + if (modifier) { + x_offset %= mt_w; + + /* consider x offset to calculate stride */ + _baddr = baddr + (x_offset * (bits_per_pixel / 8)); + } else { + _baddr = baddr; + } + + /* + * address TKT343664: + * fetch unit base address has to align to burst size + */ + burst_size = 1 << (ffs(_baddr) - 1); + burst_size = round_up(burst_size, 8); + burst_size = min(burst_size, 128U); + + /* + * address TKT339017: + * fixup for burst size vs stride mismatch + */ + if (modifier) + stride = round_up(stride + round_up(_baddr % 8, 8), burst_size); + else + stride = round_up(stride, burst_size); + + return stride < 0x10000; +} +EXPORT_SYMBOL_GPL(prg_stride_double_check); + +void prg_set_auxiliary(struct prg *prg) +{ + if (WARN_ON(!prg)) + return; + + prg->is_auxiliary = true; +} +EXPORT_SYMBOL_GPL(prg_set_auxiliary); + +void prg_set_primary(struct prg *prg) +{ + if (WARN_ON(!prg)) + return; + + prg->is_auxiliary = false; +} +EXPORT_SYMBOL_GPL(prg_set_primary); + +void prg_set_blit(struct prg *prg) +{ + if (WARN_ON(!prg)) + return; + + prg->is_blit = true; +} +EXPORT_SYMBOL_GPL(prg_set_blit); + +struct prg * +prg_lookup_by_phandle(struct device *dev, const char *name, int index) +{ + struct device_node *prg_node = of_parse_phandle(dev->of_node, + name, index); + struct prg *prg; + + mutex_lock(&prg_list_mutex); + list_for_each_entry(prg, &prg_list, list) { + if (prg_node == prg->dev->of_node) { + mutex_unlock(&prg_list_mutex); + device_link_add(dev, prg->dev, + DL_FLAG_AUTOREMOVE_CONSUMER); + return prg; + } + } + mutex_unlock(&prg_list_mutex); + + return NULL; +} +EXPORT_SYMBOL_GPL(prg_lookup_by_phandle); + +static const struct of_device_id prg_dt_ids[] = { + { .compatible = "fsl,imx8qm-prg", }, + { .compatible = "fsl,imx8qxp-prg", }, + { /* sentinel */ }, +}; + +static int prg_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct prg *prg; + + prg = devm_kzalloc(dev, sizeof(*prg), GFP_KERNEL); + if (!prg) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + prg->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(prg->base)) + return PTR_ERR(prg->base); + + prg->clk_apb = devm_clk_get(dev, "apb"); + if (IS_ERR(prg->clk_apb)) + return PTR_ERR(prg->clk_apb); + clk_prepare_enable(prg->clk_apb); + + prg->clk_rtram = devm_clk_get(dev, "rtram"); + if (IS_ERR(prg->clk_rtram)) + return PTR_ERR(prg->clk_rtram); + clk_prepare_enable(prg->clk_rtram); + + prg->dev = dev; + platform_set_drvdata(pdev, prg); + mutex_lock(&prg_list_mutex); + list_add(&prg->list, &prg_list); + mutex_unlock(&prg_list_mutex); + + prg_reset(prg); + + return 0; +} + +static int prg_remove(struct platform_device *pdev) +{ + struct prg *prg = platform_get_drvdata(pdev); + + mutex_lock(&prg_list_mutex); + list_del(&prg->list); + mutex_unlock(&prg_list_mutex); + + clk_disable_unprepare(prg->clk_rtram); + clk_disable_unprepare(prg->clk_apb); + + return 0; +} + +struct platform_driver prg_drv = { + .probe = prg_probe, + .remove = prg_remove, + .driver = { + .name = "imx8-prg", + .of_match_table = prg_dt_ids, + }, +}; +module_platform_driver(prg_drv); + +MODULE_DESCRIPTION("i.MX8 PRG driver"); +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/ipu-v3/Kconfig b/drivers/gpu/imx/ipu-v3/Kconfig index 061fb990c120..061fb990c120 100644 --- a/drivers/gpu/ipu-v3/Kconfig +++ b/drivers/gpu/imx/ipu-v3/Kconfig diff --git a/drivers/gpu/ipu-v3/Makefile b/drivers/gpu/imx/ipu-v3/Makefile index 5fe5ef20701a..5fe5ef20701a 100644 --- a/drivers/gpu/ipu-v3/Makefile +++ b/drivers/gpu/imx/ipu-v3/Makefile diff --git a/drivers/gpu/ipu-v3/ipu-common.c b/drivers/gpu/imx/ipu-v3/ipu-common.c index c35eac1116f5..c35eac1116f5 100644 --- a/drivers/gpu/ipu-v3/ipu-common.c +++ b/drivers/gpu/imx/ipu-v3/ipu-common.c diff --git a/drivers/gpu/ipu-v3/ipu-cpmem.c b/drivers/gpu/imx/ipu-v3/ipu-cpmem.c index 82b244cb313e..82b244cb313e 100644 --- a/drivers/gpu/ipu-v3/ipu-cpmem.c +++ b/drivers/gpu/imx/ipu-v3/ipu-cpmem.c diff --git a/drivers/gpu/ipu-v3/ipu-csi.c b/drivers/gpu/imx/ipu-v3/ipu-csi.c index 8ae301eef643..8ae301eef643 100644 --- a/drivers/gpu/ipu-v3/ipu-csi.c +++ b/drivers/gpu/imx/ipu-v3/ipu-csi.c diff --git a/drivers/gpu/ipu-v3/ipu-dc.c b/drivers/gpu/imx/ipu-v3/ipu-dc.c index ca96b235491a..ca96b235491a 100644 --- a/drivers/gpu/ipu-v3/ipu-dc.c +++ b/drivers/gpu/imx/ipu-v3/ipu-dc.c diff --git a/drivers/gpu/ipu-v3/ipu-di.c b/drivers/gpu/imx/ipu-v3/ipu-di.c index 0a34e0ab4fe6..0a34e0ab4fe6 100644 --- a/drivers/gpu/ipu-v3/ipu-di.c +++ b/drivers/gpu/imx/ipu-v3/ipu-di.c diff --git a/drivers/gpu/ipu-v3/ipu-dmfc.c b/drivers/gpu/imx/ipu-v3/ipu-dmfc.c index ae682084a10a..ae682084a10a 100644 --- a/drivers/gpu/ipu-v3/ipu-dmfc.c +++ b/drivers/gpu/imx/ipu-v3/ipu-dmfc.c diff --git a/drivers/gpu/ipu-v3/ipu-dp.c b/drivers/gpu/imx/ipu-v3/ipu-dp.c index 6a558205db96..6a558205db96 100644 --- a/drivers/gpu/ipu-v3/ipu-dp.c +++ b/drivers/gpu/imx/ipu-v3/ipu-dp.c diff --git a/drivers/gpu/ipu-v3/ipu-ic-csc.c b/drivers/gpu/imx/ipu-v3/ipu-ic-csc.c index d1ca7ba94a12..d1ca7ba94a12 100644 --- a/drivers/gpu/ipu-v3/ipu-ic-csc.c +++ b/drivers/gpu/imx/ipu-v3/ipu-ic-csc.c diff --git a/drivers/gpu/ipu-v3/ipu-ic.c b/drivers/gpu/imx/ipu-v3/ipu-ic.c index 846461bac70d..846461bac70d 100644 --- a/drivers/gpu/ipu-v3/ipu-ic.c +++ b/drivers/gpu/imx/ipu-v3/ipu-ic.c diff --git a/drivers/gpu/ipu-v3/ipu-image-convert.c b/drivers/gpu/imx/ipu-v3/ipu-image-convert.c index aa1d4b6d278f..aa1d4b6d278f 100644 --- a/drivers/gpu/ipu-v3/ipu-image-convert.c +++ b/drivers/gpu/imx/ipu-v3/ipu-image-convert.c diff --git a/drivers/gpu/ipu-v3/ipu-pre.c b/drivers/gpu/imx/ipu-v3/ipu-pre.c index ad82c9e0252f..ad82c9e0252f 100644 --- a/drivers/gpu/ipu-v3/ipu-pre.c +++ b/drivers/gpu/imx/ipu-v3/ipu-pre.c diff --git a/drivers/gpu/ipu-v3/ipu-prg.c b/drivers/gpu/imx/ipu-v3/ipu-prg.c index 196797c1b4b3..196797c1b4b3 100644 --- a/drivers/gpu/ipu-v3/ipu-prg.c +++ b/drivers/gpu/imx/ipu-v3/ipu-prg.c diff --git a/drivers/gpu/ipu-v3/ipu-prv.h b/drivers/gpu/imx/ipu-v3/ipu-prv.h index 291ac1bab66d..291ac1bab66d 100644 --- a/drivers/gpu/ipu-v3/ipu-prv.h +++ b/drivers/gpu/imx/ipu-v3/ipu-prv.h diff --git a/drivers/gpu/ipu-v3/ipu-smfc.c b/drivers/gpu/imx/ipu-v3/ipu-smfc.c index 46ffc0a5906d..46ffc0a5906d 100644 --- a/drivers/gpu/ipu-v3/ipu-smfc.c +++ b/drivers/gpu/imx/ipu-v3/ipu-smfc.c diff --git a/drivers/gpu/ipu-v3/ipu-vdi.c b/drivers/gpu/imx/ipu-v3/ipu-vdi.c index a593b232b6d3..a593b232b6d3 100644 --- a/drivers/gpu/ipu-v3/ipu-vdi.c +++ b/drivers/gpu/imx/ipu-v3/ipu-vdi.c diff --git a/drivers/gpu/imx/lcdif/Kconfig b/drivers/gpu/imx/lcdif/Kconfig new file mode 100644 index 000000000000..dfaea1207940 --- /dev/null +++ b/drivers/gpu/imx/lcdif/Kconfig @@ -0,0 +1,9 @@ +config IMX_LCDIF_CORE + tristate "i.MX LCDIF core support" + depends on ARCH_MXC + depends on DRM && OF + select RESET_CONTROLLER + help + Choose this if you have a NXP i.MX8MM platform and want to use the + LCDIF display controller. This option only enables LCDIF base support. + diff --git a/drivers/gpu/imx/lcdif/Makefile b/drivers/gpu/imx/lcdif/Makefile new file mode 100644 index 000000000000..8c7ce5ccce95 --- /dev/null +++ b/drivers/gpu/imx/lcdif/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_IMX_LCDIF_CORE) += imx-lcdif-core.o + +imx-lcdif-core-objs := lcdif-common.o diff --git a/drivers/gpu/imx/lcdif/lcdif-common.c b/drivers/gpu/imx/lcdif/lcdif-common.c new file mode 100644 index 000000000000..09b07758b9ea --- /dev/null +++ b/drivers/gpu/imx/lcdif/lcdif-common.c @@ -0,0 +1,854 @@ +/* + * Copyright 2018,2021-2022 NXP + * + * 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 <linux/busfreq-imx.h> +#include <linux/clk.h> +#include <linux/iopoll.h> +#include <linux/media-bus-format.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> +#include <linux/types.h> +#include <drm/drm_fourcc.h> +#include <video/imx-lcdif.h> +#include <video/videomode.h> + +#include "lcdif-regs.h" + +#define DRIVER_NAME "imx-lcdif" + +struct lcdif_soc { + struct device *dev; + + int irq; + void __iomem *base; + struct reset_control *soft_resetn; + struct reset_control *clk_enable; + atomic_t rpm_suspended; + + struct clk *clk_pix; + struct clk *clk_disp_axi; + struct clk *clk_disp_apb; +}; + +struct lcdif_soc_pdata { + bool hsync_invert; + bool vsync_invert; + bool de_invert; +}; + +struct lcdif_platform_reg { + struct lcdif_client_platformdata pdata; + char *name; +}; + +struct lcdif_platform_reg client_reg[] = { + { + .pdata = { }, + .name = "imx-lcdif-crtc", + }, +}; + +struct lcdif_soc_pdata imx8mm_pdata = { + .hsync_invert = true, + .vsync_invert = true, + .de_invert = true, +}; + +static const struct of_device_id imx_lcdif_dt_ids[] = { + { .compatible = "fsl,imx8mm-lcdif", .data = &imx8mm_pdata, }, + { .compatible = "fsl,imx8mn-lcdif", .data = &imx8mm_pdata, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_lcdif_dt_ids); + +#ifdef CONFIG_PM +static int imx_lcdif_runtime_suspend(struct device *dev); +static int imx_lcdif_runtime_resume(struct device *dev); +#else +static int imx_lcdif_runtime_suspend(struct device *dev) +{ + return 0; +} +static int imx_lcdif_runtime_resume(struct device *dev) +{ + return 0; +} +#endif + +static int lcdif_rstc_reset(struct reset_control *rstc, bool assert) +{ + int ret; + + if (!rstc) + return 0; + + ret = assert ? reset_control_assert(rstc) : + reset_control_deassert(rstc); + + return ret; +} + +static int lcdif_enable_clocks(struct lcdif_soc *lcdif) +{ + int ret; + + if (lcdif->clk_disp_axi) { + ret = clk_prepare_enable(lcdif->clk_disp_axi); + if (ret) + return ret; + } + + if (lcdif->clk_disp_apb) { + ret = clk_prepare_enable(lcdif->clk_disp_apb); + if (ret) + goto disable_disp_axi; + } + + ret = clk_prepare_enable(lcdif->clk_pix); + if (ret) + goto disable_disp_apb; + + return 0; + +disable_disp_apb: + if (lcdif->clk_disp_apb) + clk_disable_unprepare(lcdif->clk_disp_apb); +disable_disp_axi: + if (lcdif->clk_disp_axi) + clk_disable_unprepare(lcdif->clk_disp_axi); + + return ret; +} + +static void lcdif_disable_clocks(struct lcdif_soc *lcdif) +{ + clk_disable_unprepare(lcdif->clk_pix); + + if (lcdif->clk_disp_axi) + clk_disable_unprepare(lcdif->clk_disp_axi); + + if (lcdif->clk_disp_apb) + clk_disable_unprepare(lcdif->clk_disp_apb); +} + +int lcdif_vblank_irq_get(struct lcdif_soc *lcdif) +{ + return lcdif->irq; +} +EXPORT_SYMBOL(lcdif_vblank_irq_get); + +void lcdif_dump_registers(struct lcdif_soc *lcdif) +{ + pr_info("%#x : %#x\n", LCDIF_CTRL, + readl(lcdif->base + LCDIF_CTRL)); + pr_info("%#x : %#x\n", LCDIF_CTRL1, + readl(lcdif->base + LCDIF_CTRL1)); + pr_info("%#x : %#x\n", LCDIF_CTRL2, + readl(lcdif->base + LCDIF_CTRL2)); + pr_info("%#x : %#x\n", LCDIF_TRANSFER_COUNT, + readl(lcdif->base + LCDIF_TRANSFER_COUNT)); + pr_info("%#x : %#x\n", LCDIF_CUR_BUF, + readl(lcdif->base + LCDIF_CUR_BUF)); + pr_info("%#x : %#x\n", LCDIF_NEXT_BUF, + readl(lcdif->base + LCDIF_NEXT_BUF)); + pr_info("%#x : %#x\n", LCDIF_VDCTRL0, + readl(lcdif->base + LCDIF_VDCTRL0)); + pr_info("%#x : %#x\n", LCDIF_VDCTRL1, + readl(lcdif->base + LCDIF_VDCTRL1)); + pr_info("%#x : %#x\n", LCDIF_VDCTRL2, + readl(lcdif->base + LCDIF_VDCTRL2)); + pr_info("%#x : %#x\n", LCDIF_VDCTRL3, + readl(lcdif->base + LCDIF_VDCTRL3)); + pr_info("%#x : %#x\n", LCDIF_VDCTRL4, + readl(lcdif->base + LCDIF_VDCTRL4)); +} +EXPORT_SYMBOL(lcdif_dump_registers); + +void lcdif_vblank_irq_enable(struct lcdif_soc *lcdif) +{ + writel(CTRL1_CUR_FRAME_DONE_IRQ, lcdif->base + LCDIF_CTRL1 + REG_CLR); + writel(CTRL1_CUR_FRAME_DONE_IRQ_EN, lcdif->base + LCDIF_CTRL1 + REG_SET); +} +EXPORT_SYMBOL(lcdif_vblank_irq_enable); + +void lcdif_vblank_irq_disable(struct lcdif_soc *lcdif) +{ + writel(CTRL1_CUR_FRAME_DONE_IRQ_EN, lcdif->base + LCDIF_CTRL1 + REG_CLR); + writel(CTRL1_CUR_FRAME_DONE_IRQ, lcdif->base + LCDIF_CTRL1 + REG_CLR); +} +EXPORT_SYMBOL(lcdif_vblank_irq_disable); + +void lcdif_vblank_irq_clear(struct lcdif_soc *lcdif) +{ + writel(CTRL1_CUR_FRAME_DONE_IRQ, lcdif->base + LCDIF_CTRL1 + REG_CLR); +} +EXPORT_SYMBOL(lcdif_vblank_irq_clear); + +static uint32_t lcdif_get_bpp_from_fmt(uint32_t format) +{ + /* TODO: only support RGB for now */ + + switch (format) { + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_XBGR1555: + return 16; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: + return 32; + default: + /* unsupported format */ + return 0; + } +} + +/* + * Get the bus format supported by LCDIF + * according to drm fourcc format + */ +int lcdif_get_bus_fmt_from_pix_fmt(struct lcdif_soc *lcdif, + uint32_t format) +{ + uint32_t bpp; + + bpp = lcdif_get_bpp_from_fmt(format); + if (!bpp) + return -EINVAL; + + switch (bpp) { + case 16: + return MEDIA_BUS_FMT_RGB565_1X16; + case 18: + return MEDIA_BUS_FMT_RGB666_1X18; + case 24: + case 32: + return MEDIA_BUS_FMT_RGB888_1X24; + default: + return -EINVAL; + } +} +EXPORT_SYMBOL(lcdif_get_bus_fmt_from_pix_fmt); + +int lcdif_set_pix_fmt(struct lcdif_soc *lcdif, u32 format) +{ + u32 ctrl = 0, ctrl1 = 0; + + /* TODO: lcdif should be disabled to set pixel format */ + + ctrl = readl(lcdif->base + LCDIF_CTRL); + ctrl1 = readl(lcdif->base + LCDIF_CTRL1); + + /* clear pixel format related bits */ + ctrl &= ~(CTRL_SHIFT_NUM(0x3f) | CTRL_INPUT_SWIZZLE(0x3) | + CTRL_CSC_SWIZZLE(0x3) | CTRL_SET_WORD_LENGTH(0x3)); + + ctrl1 &= ~CTRL1_SET_BYTE_PACKAGING(0xf); + + /* default is 'RGB' order */ + writel(CTRL2_ODD_LINE_PATTERN(0x7) | + CTRL2_EVEN_LINE_PATTERN(0x7), + lcdif->base + LCDIF_CTRL2 + REG_CLR); + + switch (format) { + /* bpp 16 */ + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_XBGR1555: + /* Data format */ + ctrl = (format == DRM_FORMAT_RGB565 || + format == DRM_FORMAT_BGR565) ? + (ctrl & ~CTRL_DF16) : (ctrl | CTRL_DF16); + + ctrl |= CTRL_SET_WORD_LENGTH(0x0); + + /* Byte packing */ + ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0xf); + + /* 'BGR' order */ + if (format == DRM_FORMAT_BGR565 || + format == DRM_FORMAT_ABGR1555 || + format == DRM_FORMAT_XBGR1555) + writel(CTRL2_ODD_LINE_PATTERN(0x5) | + CTRL2_EVEN_LINE_PATTERN(0x5), + lcdif->base + LCDIF_CTRL2 + REG_SET); + break; + /* bpp 32 */ + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: + /*Data format */ + ctrl &= ~CTRL_DF24; + ctrl |= CTRL_SET_WORD_LENGTH(3); + + if (format == DRM_FORMAT_RGBA8888 || + format == DRM_FORMAT_RGBX8888) + ctrl |= CTRL_SHIFT_DIR(1) | CTRL_SHIFT_NUM(8); + + /* Byte packing */ + ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0x7); + + /* 'BGR' order */ + if (format == DRM_FORMAT_ABGR8888 || + format == DRM_FORMAT_XBGR8888) + writel(CTRL2_ODD_LINE_PATTERN(0x5) | + CTRL2_EVEN_LINE_PATTERN(0x5), + lcdif->base + LCDIF_CTRL2 + REG_SET); + break; + default: + dev_err(lcdif->dev, "unsupported pixel format: %p4cc\n", + &format); + return -EINVAL; + } + + writel(ctrl, lcdif->base + LCDIF_CTRL); + writel(ctrl1, lcdif->base + LCDIF_CTRL1); + + return 0; +} +EXPORT_SYMBOL(lcdif_set_pix_fmt); + +void lcdif_set_bus_fmt(struct lcdif_soc *lcdif, u32 bus_format) +{ + u32 bus_width; + + switch (bus_format) { + case MEDIA_BUS_FMT_RGB565_1X16: + bus_width = CTRL_SET_BUS_WIDTH(STMLCDIF_16BIT); + break; + case MEDIA_BUS_FMT_RGB666_1X18: + bus_width = CTRL_SET_BUS_WIDTH(STMLCDIF_18BIT); + break; + case MEDIA_BUS_FMT_RGB888_1X24: + bus_width = CTRL_SET_BUS_WIDTH(STMLCDIF_24BIT); + break; + default: + dev_err(lcdif->dev, "unknown bus format: %#x\n", bus_format); + return; + } + + writel(CTRL_SET_BUS_WIDTH(0x3), lcdif->base + LCDIF_CTRL + REG_CLR); + writel(bus_width, lcdif->base + LCDIF_CTRL + REG_SET); +} +EXPORT_SYMBOL(lcdif_set_bus_fmt); + +void lcdif_set_fb_addr(struct lcdif_soc *lcdif, int id, u32 addr, bool use_i80) +{ + switch (id) { + case 0: + /* primary plane */ + if (use_i80) + writel(addr, lcdif->base + LCDIF_CUR_BUF); + else + writel(addr, lcdif->base + LCDIF_NEXT_BUF); + break; + default: + /* TODO: add overlay support */ + return; + } +} +EXPORT_SYMBOL(lcdif_set_fb_addr); + +void lcdif_set_fb_hcrop(struct lcdif_soc *lcdif, u32 src_w, + u32 fb_w, bool crop) +{ + u32 mask_cnt; + u32 vdctrl3, vdctrl4, transfer_count; + u32 pigeon_12_0, pigeon_12_1, pigeon_12_2; + + if (!crop) { + writel(0x0, lcdif->base + HW_EPDC_PIGEON_12_0); + writel(0x0, lcdif->base + HW_EPDC_PIGEON_12_1); + + return; + } + + /* + * transfer_count's hcount and vdctrl4's H_VALID_DATA_CNT + * should use fb width instead of hactive when requires cropping. + */ + transfer_count = readl(lcdif->base + LCDIF_TRANSFER_COUNT); + transfer_count &= ~TRANSFER_COUNT_SET_HCOUNT(0xffff); + transfer_count |= TRANSFER_COUNT_SET_HCOUNT(fb_w); + writel(transfer_count, lcdif->base + LCDIF_TRANSFER_COUNT); + + vdctrl4 = readl(lcdif->base + LCDIF_VDCTRL4); + vdctrl4 &= ~SET_DOTCLK_H_VALID_DATA_CNT(0x3ffff); + vdctrl4 |= SET_DOTCLK_H_VALID_DATA_CNT(fb_w); + writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4); + + /* configure related pigeon registers */ + vdctrl3 = readl(lcdif->base + LCDIF_VDCTRL3); + mask_cnt = GET_HOR_WAIT_CNT(vdctrl3) - 5; + + pigeon_12_0 = PIGEON_12_0_SET_STATE_MASK(0x24) | + PIGEON_12_0_SET_MASK_CNT(mask_cnt) | + PIGEON_12_0_SET_MASK_CNT_SEL(0x6) | + PIGEON_12_0_POL_ACTIVE_LOW | + PIGEON_12_0_EN; + writel(pigeon_12_0, lcdif->base + HW_EPDC_PIGEON_12_0); + + pigeon_12_1 = PIGEON_12_1_SET_CLR_CNT(src_w) | + PIGEON_12_1_SET_SET_CNT(0x0); + writel(pigeon_12_1, lcdif->base + HW_EPDC_PIGEON_12_1); + + pigeon_12_2 = 0x0; + writel(pigeon_12_2, lcdif->base + HW_EPDC_PIGEON_12_2); +} +EXPORT_SYMBOL(lcdif_set_fb_hcrop); + + +void lcdif_set_mode(struct lcdif_soc *lcdif, struct videomode *vmode, + bool use_i80) +{ + const struct of_device_id *of_id = + of_match_device(imx_lcdif_dt_ids, lcdif->dev); + const struct lcdif_soc_pdata *soc_pdata; + u32 vdctrl0, vdctrl1, vdctrl2, vdctrl3, vdctrl4, htotal; + + if (unlikely(!of_id)) + return; + soc_pdata = of_id->data; + + /* Clear the FIFO */ + writel(CTRL1_FIFO_CLEAR, lcdif->base + LCDIF_CTRL1 + REG_SET); + writel(CTRL1_FIFO_CLEAR, lcdif->base + LCDIF_CTRL1 + REG_CLR); + + /* set pixel clock rate */ + clk_disable_unprepare(lcdif->clk_pix); + clk_set_rate(lcdif->clk_pix, vmode->pixelclock); + clk_prepare_enable(lcdif->clk_pix); + + /* config display timings */ + writel(TRANSFER_COUNT_SET_VCOUNT(vmode->vactive) | + TRANSFER_COUNT_SET_HCOUNT(vmode->hactive), + lcdif->base + LCDIF_TRANSFER_COUNT); + + if (use_i80) { + /* use MPU 8080 mode */ + writel(CTRL1_MODE86, lcdif->base + LCDIF_CTRL1 + REG_CLR); + return; + } + + vdctrl0 = VDCTRL0_ENABLE_PRESENT | + VDCTRL0_VSYNC_PERIOD_UNIT | + VDCTRL0_VSYNC_PULSE_WIDTH_UNIT | + VDCTRL0_SET_VSYNC_PULSE_WIDTH(vmode->vsync_len); + + /* Polarities */ + if (soc_pdata) { + if ((soc_pdata->hsync_invert && + vmode->flags & DISPLAY_FLAGS_HSYNC_LOW) || + (!soc_pdata->hsync_invert && + vmode->flags & DISPLAY_FLAGS_HSYNC_HIGH)) + vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH; + + if ((soc_pdata->vsync_invert && + vmode->flags & DISPLAY_FLAGS_VSYNC_LOW) || + (!soc_pdata->vsync_invert && + vmode->flags & DISPLAY_FLAGS_VSYNC_HIGH)) + vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH; + + if ((soc_pdata->de_invert && + vmode->flags & DISPLAY_FLAGS_DE_LOW) || + (!soc_pdata->de_invert && + vmode->flags & DISPLAY_FLAGS_DE_HIGH)) + vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH; + } else { + if (vmode->flags & DISPLAY_FLAGS_HSYNC_HIGH) + vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH; + if (vmode->flags & DISPLAY_FLAGS_VSYNC_HIGH) + vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH; + if (vmode->flags & DISPLAY_FLAGS_DE_HIGH) + vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH; + } + + if (vmode->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE) + vdctrl0 |= VDCTRL0_DOTCLK_ACT_FALLING; + + writel(vdctrl0, lcdif->base + LCDIF_VDCTRL0); + + vdctrl1 = vmode->vactive + vmode->vsync_len + + vmode->vfront_porch + vmode->vback_porch; + writel(vdctrl1, lcdif->base + LCDIF_VDCTRL1); + + htotal = vmode->hactive + vmode->hsync_len + + vmode->hfront_porch + vmode->hback_porch; + vdctrl2 = VDCTRL2_SET_HSYNC_PULSE_WIDTH(vmode->hsync_len) | + VDCTRL2_SET_HSYNC_PERIOD(htotal); + writel(vdctrl2, lcdif->base + LCDIF_VDCTRL2); + + vdctrl3 = SET_HOR_WAIT_CNT(vmode->hsync_len + vmode->hback_porch) | + SET_VERT_WAIT_CNT(vmode->vsync_len + vmode->vback_porch); + writel(vdctrl3, lcdif->base + LCDIF_VDCTRL3); + + vdctrl4 = SET_DOTCLK_H_VALID_DATA_CNT(vmode->hactive); + writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4); +} +EXPORT_SYMBOL(lcdif_set_mode); + +void lcdif_enable_controller(struct lcdif_soc *lcdif, bool use_i80) +{ + u32 ctrl2, vdctrl4, timing; + + ctrl2 = readl(lcdif->base + LCDIF_CTRL2); + vdctrl4 = readl(lcdif->base + LCDIF_VDCTRL4); + + ctrl2 &= ~CTRL2_OUTSTANDING_REQS(0x7); + ctrl2 |= CTRL2_OUTSTANDING_REQS(use_i80 ? REQ_8 : REQ_16); + writel(ctrl2, lcdif->base + LCDIF_CTRL2); + + if (use_i80) { + /* MPU 8080 write mode */ + writel(CTRL_DATA_SELECT, lcdif->base + LCDIF_CTRL + REG_SET); + writel(CTRL_READ_WRITEB, lcdif->base + LCDIF_CTRL + REG_CLR); + writel(CTRL_BYPASS_COUNT, lcdif->base + LCDIF_CTRL + REG_CLR); + writel(CTRL_DVI_MODE | CTRL_VSYNC_MODE | CTRL_DOTCLK_MODE, + lcdif->base + LCDIF_CTRL + REG_CLR); + + writel(CTRL1_COMBINE_MPU_WR_STRB, + lcdif->base + LCDIF_CTRL1 + REG_CLR); + + timing = TIMING_CMD_HOLD(2) | TIMING_CMD_SETUP(1) | + TIMING_DATA_HOLD(2) | TIMING_DATA_SETUP(1); + writel(timing, lcdif->base + LCDIF_TIMING); + } else { + /* Continuous dotclock mode */ + writel(CTRL_BYPASS_COUNT | CTRL_DOTCLK_MODE, + lcdif->base + LCDIF_CTRL + REG_SET); + } + + /* enable the SYNC signals first, then the DMA engine */ + vdctrl4 |= VDCTRL4_SYNC_SIGNALS_ON; + writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4); + + /* enable underflow recovery */ + writel(CTRL1_RECOVERY_ON_UNDERFLOW, + lcdif->base + LCDIF_CTRL1 + REG_SET); + + /* run lcdif */ + writel(CTRL_MASTER, lcdif->base + LCDIF_CTRL + REG_SET); + writel(CTRL_RUN, lcdif->base + LCDIF_CTRL + REG_SET); +} +EXPORT_SYMBOL(lcdif_enable_controller); + +void lcdif_disable_controller(struct lcdif_soc *lcdif, bool use_i80) +{ + int ret; + u32 ctrl, vdctrl4; + + writel(CTRL_RUN, lcdif->base + LCDIF_CTRL + REG_CLR); + + if (!use_i80) { + writel(CTRL_DOTCLK_MODE, lcdif->base + LCDIF_CTRL + REG_CLR); + + ret = readl_poll_timeout(lcdif->base + LCDIF_CTRL, ctrl, + !(ctrl & CTRL_RUN), 0, 1000); + if (WARN_ON(ret)) + dev_err(lcdif->dev, "disable lcdif run timeout\n"); + } + + writel(CTRL_MASTER, lcdif->base + LCDIF_CTRL + REG_CLR); + + vdctrl4 = readl(lcdif->base + LCDIF_VDCTRL4); + vdctrl4 &= ~VDCTRL4_SYNC_SIGNALS_ON; + writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4); +} +EXPORT_SYMBOL(lcdif_disable_controller); + +long lcdif_pix_clk_round_rate(struct lcdif_soc *lcdif, + unsigned long rate) +{ + if (unlikely(!rate)) + return -EINVAL; + + return clk_round_rate(lcdif->clk_pix, rate); +} +EXPORT_SYMBOL(lcdif_pix_clk_round_rate); + +static int platform_remove_device_fn(struct device *dev, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + + return 0; +} + +static void platform_device_unregister_children(struct platform_device *pdev) +{ + device_for_each_child(&pdev->dev, NULL, platform_remove_device_fn); +} + +static int lcdif_add_client_devices(struct lcdif_soc *lcdif) +{ + int ret = 0, i; + struct device *dev = lcdif->dev; + struct platform_device *pdev = NULL; + struct device_node *of_node; + + for (i = 0; i < ARRAY_SIZE(client_reg); i++) { + of_node = of_graph_get_port_by_id(dev->of_node, i); + if (!of_node) { + dev_info(dev, "no port@%d node in %s\n", + i, dev->of_node->full_name); + continue; + } + of_node_put(of_node); + + pdev = platform_device_alloc(client_reg[i].name, i); + if (!pdev) { + dev_err(dev, "Can't allocate port pdev\n"); + ret = -ENOMEM; + goto err_register; + } + + pdev->dev.parent = dev; + client_reg[i].pdata.of_node = of_node; + + ret = platform_device_add_data(pdev, &client_reg[i].pdata, + sizeof(client_reg[i].pdata)); + if (!ret) + ret = platform_device_add(pdev); + if (ret) { + platform_device_put(pdev); + goto err_register; + } + + pdev->dev.of_node = of_node; + } + + if (!pdev) + return -ENODEV; + + return 0; + +err_register: + platform_device_unregister_children(to_platform_device(dev)); + return ret; +} + +static int lcdif_of_parse_resets(struct lcdif_soc *lcdif) +{ + int ret; + struct device *dev = lcdif->dev; + struct device_node *np = dev->of_node; + struct device_node *parent, *child; + struct of_phandle_args args; + struct reset_control *rstc; + const char *compat; + uint32_t len, rstc_num = 0; + + ret = of_parse_phandle_with_args(np, "resets", "#reset-cells", + 0, &args); + if (ret) + return ret; + + parent = args.np; + for_each_child_of_node(parent, child) { + compat = of_get_property(child, "compatible", NULL); + if (!compat) + continue; + + rstc = of_reset_control_array_get(child, false, false, true); + if (IS_ERR(rstc)) + continue; + + len = strlen(compat); + if (!of_compat_cmp("lcdif,soft-resetn", compat, len)) { + lcdif->soft_resetn = rstc; + rstc_num++; + } else if (!of_compat_cmp("lcdif,clk-enable", compat, len)) { + lcdif->clk_enable = rstc; + rstc_num++; + } + else + dev_warn(dev, "invalid lcdif reset node: %s\n", compat); + } + + if (!rstc_num) { + dev_err(dev, "no invalid reset control exists\n"); + return -EINVAL; + } + + return 0; +} + +static int imx_lcdif_probe(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct lcdif_soc *lcdif; + struct resource *res; + + dev_dbg(dev, "%s: probe begin\n", __func__); + + lcdif = devm_kzalloc(dev, sizeof(*lcdif), GFP_KERNEL); + if (!lcdif) { + dev_err(dev, "Can't allocate 'lcdif_soc' structure\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + lcdif->irq = platform_get_irq(pdev, 0); + if (lcdif->irq < 0) + return -ENODEV; + + lcdif->clk_pix = devm_clk_get(dev, "pix"); + if (IS_ERR(lcdif->clk_pix)) + return PTR_ERR(lcdif->clk_pix); + + lcdif->clk_disp_axi = devm_clk_get(dev, "disp-axi"); + if (IS_ERR(lcdif->clk_disp_axi)) + lcdif->clk_disp_axi = NULL; + + lcdif->clk_disp_apb = devm_clk_get(dev, "disp-apb"); + if (IS_ERR(lcdif->clk_disp_apb)) + lcdif->clk_disp_apb = NULL; + + lcdif->base = devm_ioremap_resource(dev, res); + if (IS_ERR(lcdif->base)) + return PTR_ERR(lcdif->base); + + lcdif->dev = dev; + ret = lcdif_of_parse_resets(lcdif); + if (ret) + return ret; + + platform_set_drvdata(pdev, lcdif); + + atomic_set(&lcdif->rpm_suspended, 0); + pm_runtime_enable(dev); + atomic_inc(&lcdif->rpm_suspended); + + dev_dbg(dev, "%s: probe end\n", __func__); + + return lcdif_add_client_devices(lcdif); +} + +static int imx_lcdif_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int imx_lcdif_suspend(struct device *dev) +{ + return imx_lcdif_runtime_suspend(dev); +} + +static int imx_lcdif_resume(struct device *dev) +{ + return imx_lcdif_runtime_resume(dev); +} +#endif + +#ifdef CONFIG_PM +static int imx_lcdif_runtime_suspend(struct device *dev) +{ + struct lcdif_soc *lcdif = dev_get_drvdata(dev); + + if (atomic_inc_return(&lcdif->rpm_suspended) > 1) + return 0; + + lcdif_disable_clocks(lcdif); + + release_bus_freq(BUS_FREQ_HIGH); + + return 0; +} + +static int imx_lcdif_runtime_resume(struct device *dev) +{ + int ret = 0; + struct lcdif_soc *lcdif = dev_get_drvdata(dev); + + if (unlikely(!atomic_read(&lcdif->rpm_suspended))) { + dev_warn(lcdif->dev, "Unbalanced %s!\n", __func__); + return 0; + } + + if (!atomic_dec_and_test(&lcdif->rpm_suspended)) + return 0; + + request_bus_freq(BUS_FREQ_HIGH); + + ret = lcdif_enable_clocks(lcdif); + if (ret) { + release_bus_freq(BUS_FREQ_HIGH); + return ret; + } + + ret = lcdif_rstc_reset(lcdif->soft_resetn, false); + if (ret) { + dev_err(dev, "deassert soft_resetn failed\n"); + return ret; + } + + ret = lcdif_rstc_reset(lcdif->clk_enable, true); + if (ret) { + dev_err(dev, "assert clk_enable failed\n"); + return ret; + } + + /* Pull LCDIF out of reset */ + writel(0x0, lcdif->base + LCDIF_CTRL); + + return ret; +} +#endif + +static const struct dev_pm_ops imx_lcdif_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx_lcdif_suspend, imx_lcdif_resume) + SET_RUNTIME_PM_OPS(imx_lcdif_runtime_suspend, + imx_lcdif_runtime_resume, NULL) +}; + +struct platform_driver imx_lcdif_driver = { + .probe = imx_lcdif_probe, + .remove = imx_lcdif_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = imx_lcdif_dt_ids, + .pm = &imx_lcdif_pm_ops, + }, +}; + +module_platform_driver(imx_lcdif_driver); + +MODULE_DESCRIPTION("NXP i.MX LCDIF Display Controller driver"); +MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/imx/lcdif/lcdif-regs.h b/drivers/gpu/imx/lcdif/lcdif-regs.h new file mode 100644 index 000000000000..e40d5a7d5190 --- /dev/null +++ b/drivers/gpu/imx/lcdif/lcdif-regs.h @@ -0,0 +1,153 @@ +/* + * Copyright 2018,2021 NXP + * + * 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. + */ + +#ifndef __LCDIF_REGS_H +#define __LCDIF_REGS_H + +#define REG_SET 4 +#define REG_CLR 8 + +/* regs offset */ +#define LCDIF_CTRL 0x00 +#define LCDIF_CTRL1 0X10 +#define LCDIF_CTRL2 0X20 +#define LCDIF_TRANSFER_COUNT 0x30 +#define LCDIF_CUR_BUF 0x40 +#define LCDIF_NEXT_BUF 0x50 +#define LCDIF_TIMING 0x60 +#define LCDIF_VDCTRL0 0x70 +#define LCDIF_VDCTRL1 0x80 +#define LCDIF_VDCTRL2 0x90 +#define LCDIF_VDCTRL3 0xa0 +#define LCDIF_VDCTRL4 0xb0 + +/* pigeon registers for crop */ +#define HW_EPDC_PIGEON_12_0 0xb00 +#define HW_EPDC_PIGEON_12_1 0xb10 +#define HW_EPDC_PIGEON_12_2 0xb20 + +/* reg bit manipulation */ +#define REG_MASK(e, s) (((1 << ((e) - (s) + 1)) - 1) << (s)) +#define REG_PUT(x, e, s) (((x) << (s)) & REG_MASK(e, s)) +#define REG_GET(x, e, s) (((x) & REG_MASK(e, s)) >> (s)) + +#define SWIZZLE_LE 0 /* Little-Endian or No swap */ +#define SWIZZLE_BE 1 /* Big-Endian or swap all */ +#define SWIZZLE_HWD 2 /* Swap half-words */ +#define SWIZZLE_HWD_BYTE 3 /* Swap bytes within each half-word */ + +/* regs bit fields */ +#define CTRL_SFTRST BIT(31) +#define CTRL_CLKGATE BIT(30) +#define CTRL_READ_WRITEB BIT(28) +#define CTRL_SHIFT_DIR(x) REG_PUT((x), 26, 26) +#define CTRL_SHIFT_NUM(x) REG_PUT((x), 25, 21) +#define CTRL_DVI_MODE BIT(20) +#define CTRL_BYPASS_COUNT BIT(19) +#define CTRL_VSYNC_MODE BIT(18) +#define CTRL_DOTCLK_MODE BIT(17) +#define CTRL_DATA_SELECT BIT(16) +#define CTRL_INPUT_SWIZZLE(x) REG_PUT((x), 15, 14) +#define CTRL_CSC_SWIZZLE(x) REG_PUT((x), 13, 12) +#define CTRL_SET_BUS_WIDTH(x) REG_PUT((x), 11, 10) +#define CTRL_GET_BUS_WIDTH(x) REG_GET((x), 11, 10) +#define CTRL_BUS_WIDTH_MASK REG_PUT((0x3), 11, 10) +#define CTRL_SET_WORD_LENGTH(x) REG_PUT((x), 9, 8) +#define CTRL_GET_WORD_LENGTH(x) REG_GET((x), 9, 8) +#define CTRL_MASTER BIT(5) +#define CTRL_DF16 BIT(3) +#define CTRL_DF18 BIT(2) +#define CTRL_DF24 BIT(1) +#define CTRL_RUN BIT(0) + +#define CTRL1_COMBINE_MPU_WR_STRB BIT(27) +#define CTRL1_RECOVERY_ON_UNDERFLOW BIT(24) +#define CTRL1_FIFO_CLEAR BIT(21) +#define CTRL1_SET_BYTE_PACKAGING(x) REG_PUT((x), 19, 16) +#define CTRL1_GET_BYTE_PACKAGING(x) REG_GET((x), 19, 16) +#define CTRL1_CUR_FRAME_DONE_IRQ_EN BIT(13) +#define CTRL1_CUR_FRAME_DONE_IRQ BIT(9) +#define CTRL1_MODE86 BIT(1) + +#define REQ_1 0 +#define REQ_2 1 +#define REQ_4 2 +#define REQ_8 3 +#define REQ_16 4 + +#define CTRL2_OUTSTANDING_REQS(x) REG_PUT((x), 23, 21) +#define CTRL2_ODD_LINE_PATTERN(x) REG_PUT((x), 18, 16) +#define CTRL2_EVEN_LINE_PATTERN(x) REG_PUT((x), 14, 12) + +#define TRANSFER_COUNT_SET_VCOUNT(x) (((x) & 0xffff) << 16) +#define TRANSFER_COUNT_GET_VCOUNT(x) (((x) >> 16) & 0xffff) +#define TRANSFER_COUNT_SET_HCOUNT(x) ((x) & 0xffff) +#define TRANSFER_COUNT_GET_HCOUNT(x) ((x) & 0xffff) + +#define TIMING_CMD_HOLD(x) REG_PUT((x), 31, 24) +#define TIMING_CMD_SETUP(x) REG_PUT((x), 23, 16) +#define TIMING_DATA_HOLD(x) REG_PUT((x), 15, 8) +#define TIMING_DATA_SETUP(x) REG_PUT((x), 7, 0) + +#define VDCTRL0_ENABLE_PRESENT BIT(28) +#define VDCTRL0_VSYNC_ACT_HIGH BIT(27) +#define VDCTRL0_HSYNC_ACT_HIGH BIT(26) +#define VDCTRL0_DOTCLK_ACT_FALLING BIT(25) +#define VDCTRL0_ENABLE_ACT_HIGH BIT(24) +#define VDCTRL0_VSYNC_PERIOD_UNIT BIT(21) +#define VDCTRL0_VSYNC_PULSE_WIDTH_UNIT BIT(20) +#define VDCTRL0_HALF_LINE BIT(19) +#define VDCTRL0_HALF_LINE_MODE BIT(18) +#define VDCTRL0_SET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff) +#define VDCTRL0_GET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff) + +#define VDCTRL2_SET_HSYNC_PULSE_WIDTH(x) (((x) & 0x3fff) << 18) +#define VDCTRL2_GET_HSYNC_PULSE_WIDTH(x) (((x) >> 18) & 0x3fff) +#define VDCTRL2_SET_HSYNC_PERIOD(x) ((x) & 0x3ffff) +#define VDCTRL2_GET_HSYNC_PERIOD(x) ((x) & 0x3ffff) + +#define VDCTRL3_MUX_SYNC_SIGNALS BIT(29) +#define VDCTRL3_VSYNC_ONLY BIT(28) +#define SET_HOR_WAIT_CNT(x) (((x) & 0xfff) << 16) +#define GET_HOR_WAIT_CNT(x) (((x) >> 16) & 0xfff) +#define SET_VERT_WAIT_CNT(x) ((x) & 0xffff) +#define GET_VERT_WAIT_CNT(x) ((x) & 0xffff) + +#define VDCTRL4_SET_DOTCLK_DLY(x) (((x) & 0x7) << 29) /* v4 only */ +#define VDCTRL4_GET_DOTCLK_DLY(x) (((x) >> 29) & 0x7) /* v4 only */ +#define VDCTRL4_SYNC_SIGNALS_ON BIT(18) +#define SET_DOTCLK_H_VALID_DATA_CNT(x) ((x) & 0x3ffff) + +#define PIGEON_12_0_SET_STATE_MASK(x) REG_PUT((x), 31, 24) +#define PIGEON_12_0_SET_MASK_CNT(x) REG_PUT((x), 23, 12) +#define PIGEON_12_0_SET_MASK_CNT_SEL(x) REG_PUT((x), 11, 8) +#define PIGEON_12_0_SET_OFFSET(x) REG_PUT((x), 7, 4) +#define PIGEON_12_0_SET_INC_SEL(x) REG_PUT((x), 3, 2) +#define PIGEON_12_0_POL_ACTIVE_LOW BIT(1) +#define PIGEON_12_0_EN BIT(0) + +#define PIGEON_12_1_SET_CLR_CNT(x) REG_PUT((x), 31, 16) +#define PIGEON_12_1_SET_SET_CNT(x) REG_PUT((x), 15, 0) + +#define STMLCDIF_8BIT 1 /* pixel data bus to the display is of 8 bit width */ +#define STMLCDIF_16BIT 0 /* pixel data bus to the display is of 16 bit width */ +#define STMLCDIF_18BIT 2 /* pixel data bus to the display is of 18 bit width */ +#define STMLCDIF_24BIT 3 /* pixel data bus to the display is of 24 bit width */ + +#define MIN_XRES 120 +#define MIN_YRES 120 +#define MAX_XRES 0xffff +#define MAX_YRES 0xffff + +#endif diff --git a/drivers/gpu/imx/lcdifv3/Kconfig b/drivers/gpu/imx/lcdifv3/Kconfig new file mode 100644 index 000000000000..8365c285f4a4 --- /dev/null +++ b/drivers/gpu/imx/lcdifv3/Kconfig @@ -0,0 +1,10 @@ +config IMX_LCDIFV3_CORE + tristate "i.MX LCDIFV3 core support" + depends on ARCH_MXC + depends on DRM && OF + select RESET_CONTROLLER + help + Choose this if you have a NXP i.MX8MP platform and want to use the + LCDIFV3 display controller. This option only enables LCDIFV3 base + support. + diff --git a/drivers/gpu/imx/lcdifv3/Makefile b/drivers/gpu/imx/lcdifv3/Makefile new file mode 100644 index 000000000000..812043e52f34 --- /dev/null +++ b/drivers/gpu/imx/lcdifv3/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_IMX_LCDIFV3_CORE) += imx-lcdifv3-core.o + +imx-lcdifv3-core-objs := lcdifv3-common.o diff --git a/drivers/gpu/imx/lcdifv3/lcdifv3-common.c b/drivers/gpu/imx/lcdifv3/lcdifv3-common.c new file mode 100644 index 000000000000..8dab74c3fdad --- /dev/null +++ b/drivers/gpu/imx/lcdifv3/lcdifv3-common.c @@ -0,0 +1,883 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright 2019,2022 NXP + */ + +#include <linux/busfreq-imx.h> +#include <linux/clk.h> +#include <linux/iopoll.h> +#include <linux/media-bus-format.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/types.h> +#include <drm/drm_fourcc.h> +#include <video/imx-lcdifv3.h> +#include <video/videomode.h> + +#include "lcdifv3-regs.h" + +#define DRIVER_NAME "imx-lcdifv3" + +struct lcdifv3_soc { + struct device *dev; + + int irq; + void __iomem *base; + struct regmap *gpr; + atomic_t rpm_suspended; + + struct clk *clk_pix; + struct clk *clk_disp_axi; + struct clk *clk_disp_apb; + + u32 thres_low_mul; + u32 thres_low_div; + u32 thres_high_mul; + u32 thres_high_div; +}; + +struct lcdifv3_soc_pdata { + bool hsync_invert; + bool vsync_invert; + bool de_invert; + bool hdmimix; +}; + +struct lcdifv3_platform_reg { + struct lcdifv3_client_platformdata pdata; + char *name; +}; + +static struct lcdifv3_platform_reg client_reg[] = { + { + .pdata = { }, + .name = "imx-lcdifv3-crtc", + }, +}; + +static struct lcdifv3_soc_pdata imx8mp_lcdif1_pdata = { + .hsync_invert = false, + .vsync_invert = false, + .de_invert = false, + .hdmimix = false, +}; + +static struct lcdifv3_soc_pdata imx8mp_lcdif2_pdata = { + .hsync_invert = false, + .vsync_invert = false, + .de_invert = true, + .hdmimix = false, +}; + +static struct lcdifv3_soc_pdata imx8mp_lcdif3_pdata = { + .hsync_invert = false, + .vsync_invert = false, + .de_invert = false, + .hdmimix = true, +}; +static const struct of_device_id imx_lcdifv3_dt_ids[] = { + { .compatible = "fsl,imx93-lcdif", }, + { .compatible = "fsl,imx8mp-lcdif1", .data = &imx8mp_lcdif1_pdata, }, + { .compatible = "fsl,imx8mp-lcdif2", .data = &imx8mp_lcdif2_pdata, }, + { .compatible = "fsl,imx8mp-lcdif3", .data = &imx8mp_lcdif3_pdata,}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_lcdifv3_dt_ids); + +static int lcdifv3_enable_clocks(struct lcdifv3_soc *lcdifv3) +{ + int ret; + + if (lcdifv3->clk_disp_axi) { + ret = clk_prepare_enable(lcdifv3->clk_disp_axi); + if (ret) + return ret; + } + + if (lcdifv3->clk_disp_apb) { + ret = clk_prepare_enable(lcdifv3->clk_disp_apb); + if (ret) + goto disable_disp_axi; + } + + ret = clk_prepare_enable(lcdifv3->clk_pix); + if (ret) + goto disable_disp_apb; + + return 0; + +disable_disp_apb: + if (lcdifv3->clk_disp_apb) + clk_disable_unprepare(lcdifv3->clk_disp_apb); +disable_disp_axi: + if (lcdifv3->clk_disp_axi) + clk_disable_unprepare(lcdifv3->clk_disp_axi); + + return ret; +} + +static void lcdifv3_disable_clocks(struct lcdifv3_soc *lcdifv3) +{ + clk_disable_unprepare(lcdifv3->clk_pix); + + if (lcdifv3->clk_disp_axi) + clk_disable_unprepare(lcdifv3->clk_disp_axi); + + if (lcdifv3->clk_disp_apb) + clk_disable_unprepare(lcdifv3->clk_disp_apb); +} + +static void lcdifv3_enable_plane_panic(struct lcdifv3_soc *lcdifv3) +{ + u32 panic_thres, thres_low, thres_high; + + /* apb clock has been enabled */ + + /* As suggestion, the thres_low should be 1/3 FIFO, + * and thres_high should be 2/3 FIFO (The FIFO size + * is 8KB = 512 * 128bit). + * threshold = n * 128bit (n: 0 ~ 511) + */ + thres_low = DIV_ROUND_UP(511 * lcdifv3->thres_low_mul, + lcdifv3->thres_low_div); + thres_high = DIV_ROUND_UP(511 * lcdifv3->thres_high_mul, + lcdifv3->thres_high_div); + + panic_thres = PANIC0_THRES_PANIC_THRES_LOW(thres_low) | + PANIC0_THRES_PANIC_THRES_HIGH(thres_high); + + writel(panic_thres, lcdifv3->base + LCDIFV3_PANIC0_THRES); + + /* Enable Panic: + * + * As designed, the panic won't trigger an irq, + * so it is unnecessary to handle this as an irq + * and NoC + QoS modules will handle panic + * automatically. + */ + writel(INT_ENABLE_D1_PLANE_PANIC_EN, + lcdifv3->base + LCDIFV3_INT_ENABLE_D1); +} + +int lcdifv3_vblank_irq_get(struct lcdifv3_soc *lcdifv3) +{ + return lcdifv3->irq; +} +EXPORT_SYMBOL(lcdifv3_vblank_irq_get); + +/* TODO: use VS_BLANK or VSYNC? */ +void lcdifv3_vblank_irq_enable(struct lcdifv3_soc *lcdifv3) +{ + uint32_t int_enable_d0; + + int_enable_d0 = readl(lcdifv3->base + LCDIFV3_INT_ENABLE_D0); + int_enable_d0 |= INT_STATUS_D0_VS_BLANK; + + /* W1C */ + writel(INT_STATUS_D0_VS_BLANK, + lcdifv3->base + LCDIFV3_INT_STATUS_D0); + /* enable */ + writel(int_enable_d0, + lcdifv3->base + LCDIFV3_INT_ENABLE_D0); +} +EXPORT_SYMBOL(lcdifv3_vblank_irq_enable); + +void lcdifv3_vblank_irq_disable(struct lcdifv3_soc *lcdifv3) +{ + uint32_t int_enable_d0; + + int_enable_d0 = readl(lcdifv3->base + LCDIFV3_INT_ENABLE_D0); + int_enable_d0 &= ~INT_STATUS_D0_VS_BLANK; + + /* disable */ + writel(int_enable_d0, + lcdifv3->base + LCDIFV3_INT_ENABLE_D0); + /* W1C */ + writel(INT_STATUS_D0_VS_BLANK, + lcdifv3->base + LCDIFV3_INT_STATUS_D0); +} +EXPORT_SYMBOL(lcdifv3_vblank_irq_disable); + +void lcdifv3_vblank_irq_clear(struct lcdifv3_soc *lcdifv3) +{ + /* W1C */ + writel(INT_STATUS_D0_VS_BLANK, + lcdifv3->base + LCDIFV3_INT_STATUS_D0); +} +EXPORT_SYMBOL(lcdifv3_vblank_irq_clear); + +static uint32_t lcdifv3_get_bpp_from_fmt(uint32_t format) +{ + /* TODO: only support RGB for now */ + + switch (format) { + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_XBGR1555: + return 16; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: + return 32; + default: + /* unsupported format */ + return 0; + } +} + +/* + * Get the bus format supported by LCDIF + * according to drm fourcc format + */ +int lcdifv3_get_bus_fmt_from_pix_fmt(struct lcdifv3_soc *lcdifv3, + uint32_t format) +{ + uint32_t bpp; + + bpp = lcdifv3_get_bpp_from_fmt(format); + if (!bpp) + return -EINVAL; + + switch (bpp) { + case 16: + return MEDIA_BUS_FMT_RGB565_1X16; + case 18: + return MEDIA_BUS_FMT_RGB666_1X18; + case 24: + case 32: + return MEDIA_BUS_FMT_RGB888_1X24; + default: + return -EINVAL; + } +} +EXPORT_SYMBOL(lcdifv3_get_bus_fmt_from_pix_fmt); + +int lcdifv3_set_pix_fmt(struct lcdifv3_soc *lcdifv3, u32 format) +{ + uint32_t ctrldescl0_5 = 0; + + ctrldescl0_5 = readl(lcdifv3->base + LCDIFV3_CTRLDESCL0_5); + + ctrldescl0_5 &= ~(CTRLDESCL0_5_BPP(0xf) | CTRLDESCL0_5_YUV_FORMAT(0x3)); + + switch (format) { + case DRM_FORMAT_RGB565: + ctrldescl0_5 |= CTRLDESCL0_5_BPP(BPP16_RGB565); + break; + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + ctrldescl0_5 |= CTRLDESCL0_5_BPP(BPP16_ARGB1555); + break; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + ctrldescl0_5 |= CTRLDESCL0_5_BPP(BPP32_ARGB8888); + break; + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + ctrldescl0_5 |= CTRLDESCL0_5_BPP(BPP32_ABGR8888); + break; + default: + dev_err(lcdifv3->dev, "unsupported pixel format: %p4cc\n", + &format); + return -EINVAL; + } + + writel(ctrldescl0_5, lcdifv3->base + LCDIFV3_CTRLDESCL0_5); + + return 0; +} +EXPORT_SYMBOL(lcdifv3_set_pix_fmt); + +void lcdifv3_set_bus_fmt(struct lcdifv3_soc *lcdifv3, u32 bus_format) +{ + uint32_t disp_para = 0; + + disp_para = readl(lcdifv3->base + LCDIFV3_DISP_PARA); + + /* clear line pattern bits */ + disp_para &= ~DISP_PARA_LINE_PATTERN(0xf); + + switch (bus_format) { + case MEDIA_BUS_FMT_RGB565_1X16: + disp_para |= DISP_PARA_LINE_PATTERN(LP_RGB565); + break; + case MEDIA_BUS_FMT_RGB888_1X24: + disp_para |= DISP_PARA_LINE_PATTERN(LP_RGB888_OR_YUV444); + break; + default: + dev_err(lcdifv3->dev, "unknown bus format: %#x\n", bus_format); + return; + } + + /* config display mode: default is normal mode */ + disp_para &= ~DISP_PARA_DISP_MODE(3); + disp_para |= DISP_PARA_DISP_MODE(0); + + writel(disp_para, lcdifv3->base + LCDIFV3_DISP_PARA); +} +EXPORT_SYMBOL(lcdifv3_set_bus_fmt); + +void lcdifv3_set_fb_addr(struct lcdifv3_soc *lcdifv3, int id, u32 addr) +{ + switch (id) { + case 0: + /* primary plane */ + writel(addr, lcdifv3->base + LCDIFV3_CTRLDESCL_LOW0_4); + break; + default: + /* TODO: add overlay support */ + return; + } +} +EXPORT_SYMBOL(lcdifv3_set_fb_addr); + +void lcdifv3_set_fb_hcrop(struct lcdifv3_soc *lcdifv3, u32 src_w, + u32 pitch, bool crop) +{ + uint32_t ctrldescl0_3 = 0; + + /* config P_SIZE and T_SIZE: + * 1. P_SIZE and T_SIZE should never + * be less than AXI bus width. + * 2. P_SIZE should never be less than T_SIZE. + */ + ctrldescl0_3 |= CTRLDESCL0_3_P_SIZE(2); + ctrldescl0_3 |= CTRLDESCL0_3_T_SIZE(2); + + /* config pitch */ + ctrldescl0_3 |= CTRLDESCL0_3_PITCH(pitch); + + /* enable frame clear to clear FIFO data on + * every vsync blank period to make sure no + * dirty data exits to affect next frame + * display, otherwise some flicker issue may + * be observed in some cases. + */ + ctrldescl0_3 |= CTRLDESCL0_3_STATE_CLEAR_VSYNC; + + writel(ctrldescl0_3, lcdifv3->base + LCDIFV3_CTRLDESCL0_3); +} +EXPORT_SYMBOL(lcdifv3_set_fb_hcrop); + + +void lcdifv3_set_mode(struct lcdifv3_soc *lcdifv3, struct videomode *vmode) +{ + const struct of_device_id *of_id = + of_match_device(imx_lcdifv3_dt_ids, lcdifv3->dev); + const struct lcdifv3_soc_pdata *soc_pdata; + u32 disp_size, hsyn_para, vsyn_para, vsyn_hsyn_width, ctrldescl0_1; + + if (unlikely(!of_id)) + return; + soc_pdata = of_id->data; + + /* set pixel clock rate */ + clk_disable_unprepare(lcdifv3->clk_pix); + clk_set_rate(lcdifv3->clk_pix, vmode->pixelclock); + clk_prepare_enable(lcdifv3->clk_pix); + + /* config display timings */ + disp_size = DISP_SIZE_DELTA_Y(vmode->vactive) | + DISP_SIZE_DELTA_X(vmode->hactive); + writel(disp_size, lcdifv3->base + LCDIFV3_DISP_SIZE); + + WARN_ON(!vmode->hback_porch || !vmode->hfront_porch); + hsyn_para = HSYN_PARA_BP_H(vmode->hback_porch) | + HSYN_PARA_FP_H(vmode->hfront_porch); + writel(hsyn_para, lcdifv3->base + LCDIFV3_HSYN_PARA); + + WARN_ON(!vmode->vback_porch || !vmode->vfront_porch); + vsyn_para = VSYN_PARA_BP_V(vmode->vback_porch) | + VSYN_PARA_FP_V(vmode->vfront_porch); + writel(vsyn_para, lcdifv3->base + LCDIFV3_VSYN_PARA); + + WARN_ON(!vmode->vsync_len || !vmode->hsync_len); + vsyn_hsyn_width = VSYN_HSYN_WIDTH_PW_V(vmode->vsync_len) | + VSYN_HSYN_WIDTH_PW_H(vmode->hsync_len); + writel(vsyn_hsyn_width, lcdifv3->base + LCDIFV3_VSYN_HSYN_WIDTH); + + /* config layer size */ + /* TODO: 32bits alignment for width */ + ctrldescl0_1 = CTRLDESCL0_1_HEIGHT(vmode->vactive) | + CTRLDESCL0_1_WIDTH(vmode->hactive); + writel(ctrldescl0_1, lcdifv3->base + LCDIFV3_CTRLDESCL0_1); + + /* Polarities */ + if (soc_pdata) { + if ((soc_pdata->hsync_invert && + vmode->flags & DISPLAY_FLAGS_HSYNC_HIGH) || + (!soc_pdata->hsync_invert && + vmode->flags & DISPLAY_FLAGS_HSYNC_LOW)) + writel(CTRL_INV_HS, lcdifv3->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_HS, lcdifv3->base + LCDIFV3_CTRL_CLR); + + if ((soc_pdata->vsync_invert && + vmode->flags & DISPLAY_FLAGS_VSYNC_HIGH) || + (!soc_pdata->vsync_invert && + vmode->flags & DISPLAY_FLAGS_VSYNC_LOW)) + writel(CTRL_INV_VS, lcdifv3->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_VS, lcdifv3->base + LCDIFV3_CTRL_CLR); + + if ((soc_pdata->de_invert && + vmode->flags & DISPLAY_FLAGS_DE_HIGH) || + (!soc_pdata->de_invert && + vmode->flags & DISPLAY_FLAGS_DE_LOW)) + writel(CTRL_INV_DE, lcdifv3->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_DE, lcdifv3->base + LCDIFV3_CTRL_CLR); + } else { + if (vmode->flags & DISPLAY_FLAGS_HSYNC_LOW) + writel(CTRL_INV_HS, lcdifv3->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_HS, lcdifv3->base + LCDIFV3_CTRL_CLR); + if (vmode->flags & DISPLAY_FLAGS_VSYNC_LOW) + writel(CTRL_INV_VS, lcdifv3->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_VS, lcdifv3->base + LCDIFV3_CTRL_CLR); + if (vmode->flags & DISPLAY_FLAGS_DE_LOW) + writel(CTRL_INV_DE, lcdifv3->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_DE, lcdifv3->base + LCDIFV3_CTRL_CLR); + } + + if (vmode->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE) + writel(CTRL_INV_PXCK, lcdifv3->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_PXCK, lcdifv3->base + LCDIFV3_CTRL_CLR); +} +EXPORT_SYMBOL(lcdifv3_set_mode); + +void lcdifv3_en_shadow_load(struct lcdifv3_soc *lcdifv3) +{ + u32 ctrldescl0_5; + + ctrldescl0_5 = readl(lcdifv3->base + LCDIFV3_CTRLDESCL0_5); + ctrldescl0_5 |= CTRLDESCL0_5_SHADOW_LOAD_EN; + + writel(ctrldescl0_5, lcdifv3->base + LCDIFV3_CTRLDESCL0_5); +} +EXPORT_SYMBOL(lcdifv3_en_shadow_load); + +void lcdifv3_enable_controller(struct lcdifv3_soc *lcdifv3) +{ + u32 disp_para, ctrldescl0_5; + + disp_para = readl(lcdifv3->base + LCDIFV3_DISP_PARA); + ctrldescl0_5 = readl(lcdifv3->base + LCDIFV3_CTRLDESCL0_5); + + /* disp on */ + disp_para |= DISP_PARA_DISP_ON; + writel(disp_para, lcdifv3->base + LCDIFV3_DISP_PARA); + + /* enable layer dma */ + ctrldescl0_5 |= CTRLDESCL0_5_EN; + writel(ctrldescl0_5, lcdifv3->base + LCDIFV3_CTRLDESCL0_5); +} +EXPORT_SYMBOL(lcdifv3_enable_controller); + +void lcdifv3_disable_controller(struct lcdifv3_soc *lcdifv3) +{ + u32 disp_para, ctrldescl0_5; + + disp_para = readl(lcdifv3->base + LCDIFV3_DISP_PARA); + ctrldescl0_5 = readl(lcdifv3->base + LCDIFV3_CTRLDESCL0_5); + + /* disable dma */ + ctrldescl0_5 &= ~CTRLDESCL0_5_EN; + writel(ctrldescl0_5, lcdifv3->base + LCDIFV3_CTRLDESCL0_5); + + /* dma config only takes effect at the end of + * one frame, so add delay to wait dma disable + * done before turn off disp. + */ + usleep_range(20000, 25000); + + /* disp off */ + disp_para &= ~DISP_PARA_DISP_ON; + writel(disp_para, lcdifv3->base + LCDIFV3_DISP_PARA); +} +EXPORT_SYMBOL(lcdifv3_disable_controller); + +long lcdifv3_pix_clk_round_rate(struct lcdifv3_soc *lcdifv3, + unsigned long rate) +{ + if (unlikely(!rate)) + return -EINVAL; + + return clk_round_rate(lcdifv3->clk_pix, rate); +} +EXPORT_SYMBOL(lcdifv3_pix_clk_round_rate); + +static int hdmimix_lcdif3_setup(struct lcdifv3_soc *lcdifv3) +{ + struct device *dev = lcdifv3->dev; + int ret; + + struct clk_bulk_data clocks[] = { + { .id = "mix_apb" }, + { .id = "mix_axi" }, + { .id = "xtl_24m" }, + { .id = "mix_pix" }, + { .id = "lcdif_apb" }, + { .id = "lcdif_axi" }, + { .id = "lcdif_pdi" }, + { .id = "lcdif_pix" }, + { .id = "lcdif_spu" }, + { .id = "noc_hdmi" }, + }; + + /* power up hdmimix lcdif and nor */ + ret = device_reset(dev); + if (ret) + dev_warn(dev, "No hdmimix sub reset found\n"); + if (ret == -EPROBE_DEFER) + return ret; + + /* enable lpcg of hdmimix lcdif and nor */ + ret = devm_clk_bulk_get(dev, ARRAY_SIZE(clocks), clocks); + if (ret < 0) + return ret; + ret = clk_bulk_prepare_enable(ARRAY_SIZE(clocks), clocks); + if (ret < 0) + return ret; + + return 0; +} + +static int platform_remove_device_fn(struct device *dev, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + + return 0; +} + +static void platform_device_unregister_children(struct platform_device *pdev) +{ + device_for_each_child(&pdev->dev, NULL, platform_remove_device_fn); +} + +static DEFINE_MUTEX(lcdifv3_client_id_mutex); +static int lcdifv3_client_id; + +static int lcdifv3_add_client_devices(struct lcdifv3_soc *lcdifv3) +{ + int ret = 0, i, id; + struct device *dev = lcdifv3->dev; + struct platform_device *pdev = NULL; + struct device_node *of_node; + + for (i = 0; i < ARRAY_SIZE(client_reg); i++) { + of_node = of_graph_get_port_by_id(dev->of_node, i); + if (!of_node) { + dev_info(dev, "no port@%d node in %s\n", + i, dev->of_node->full_name); + continue; + } + of_node_put(of_node); + + mutex_lock(&lcdifv3_client_id_mutex); + id = lcdifv3_client_id++; + mutex_unlock(&lcdifv3_client_id_mutex); + + pdev = platform_device_alloc(client_reg[i].name, id); + if (!pdev) { + dev_err(dev, "Can't allocate port pdev\n"); + ret = -ENOMEM; + goto err_register; + } + + pdev->dev.parent = dev; + client_reg[i].pdata.of_node = of_node; + + /* make child device 'dma_mask' to point to its + * coherent dma mask, otherwise later probe will + * print warning message: 'DMA mask not set'. + */ + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + + ret = platform_device_add_data(pdev, &client_reg[i].pdata, + sizeof(client_reg[i].pdata)); + if (!ret) + ret = platform_device_add(pdev); + if (ret) { + platform_device_put(pdev); + goto err_register; + } + + pdev->dev.of_node = of_node; + } + + if (!pdev) + return -ENODEV; + + return 0; + +err_register: + platform_device_unregister_children(to_platform_device(dev)); + return ret; +} + +static int imx_lcdifv3_check_thres_value(u32 mul, u32 div) +{ + if (!div) + return -EINVAL; + + if (mul > div) + return -EINVAL; + + return 0; +} + +static void imx_lcdifv3_of_parse_thres(struct lcdifv3_soc *lcdifv3) +{ + int ret; + u32 thres_low[2], thres_high[2]; + struct device_node *np = lcdifv3->dev->of_node; + + /* default 'thres-low' value: FIFO * 1/3; + * default 'thres-high' value: FIFO * 2/3. + */ + lcdifv3->thres_low_mul = 1; + lcdifv3->thres_low_div = 3; + lcdifv3->thres_high_mul = 2; + lcdifv3->thres_high_div = 3; + + ret = of_property_read_u32_array(np, "thres-low", thres_low, 2); + if (!ret) { + /* check the value effectiveness */ + ret = imx_lcdifv3_check_thres_value(thres_low[0], thres_low[1]); + if (!ret) { + lcdifv3->thres_low_mul = thres_low[0]; + lcdifv3->thres_low_div = thres_low[1]; + } + } + + ret = of_property_read_u32_array(np, "thres-high", thres_high, 2); + if (!ret) { + /* check the value effectiveness */ + ret = imx_lcdifv3_check_thres_value(thres_high[0], thres_high[1]); + if (!ret) { + lcdifv3->thres_high_mul = thres_high[0]; + lcdifv3->thres_high_div = thres_high[1]; + } + } +} + +static int imx_lcdifv3_probe(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct lcdifv3_soc *lcdifv3; + struct resource *res; + const struct of_device_id *of_id; + const struct lcdifv3_soc_pdata *soc_pdata; + + dev_dbg(dev, "%s: probe begin\n", __func__); + + of_id = of_match_device(imx_lcdifv3_dt_ids, dev); + if (!of_id) { + dev_err(&pdev->dev, "OF data missing\n"); + return -EINVAL; + } + + soc_pdata = of_id->data; + + lcdifv3 = devm_kzalloc(dev, sizeof(*lcdifv3), GFP_KERNEL); + if (!lcdifv3) { + dev_err(dev, "Can't allocate 'lcdifv3_soc' structure\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + lcdifv3->irq = platform_get_irq(pdev, 0); + if (lcdifv3->irq < 0) { + dev_err(dev, "No irq get, ret=%d\n", lcdifv3->irq); + return lcdifv3->irq; + } + + lcdifv3->clk_pix = devm_clk_get(dev, "pix"); + if (IS_ERR(lcdifv3->clk_pix)) { + ret = PTR_ERR(lcdifv3->clk_pix); + dev_err(dev, "No pix clock get: %d\n", ret); + return ret; + } + + lcdifv3->clk_disp_axi = devm_clk_get(dev, "disp-axi"); + if (IS_ERR(lcdifv3->clk_disp_axi)) + lcdifv3->clk_disp_axi = NULL; + + lcdifv3->clk_disp_apb = devm_clk_get(dev, "disp-apb"); + if (IS_ERR(lcdifv3->clk_disp_apb)) + lcdifv3->clk_disp_apb = NULL; + + lcdifv3->base = devm_ioremap_resource(dev, res); + if (IS_ERR(lcdifv3->base)) + return PTR_ERR(lcdifv3->base); + + if (of_device_is_compatible(np, "fsl,imx93-lcdif")) { + lcdifv3->gpr = syscon_regmap_lookup_by_phandle(np, "fsl,gpr"); + if (IS_ERR(lcdifv3->gpr)) { + ret = PTR_ERR(lcdifv3->gpr); + dev_err(dev, "failed to get gpr: %d\n", ret); + return ret; + } + } + + lcdifv3->dev = dev; + + /* reset controller to avoid any conflict + * with uboot splash screen settings. + */ + if (of_device_is_compatible(np, "fsl,imx8mp-lcdif1")) { + /* TODO: Maybe the clock enable should + * be done in reset driver. + */ + clk_prepare_enable(lcdifv3->clk_disp_axi); + clk_prepare_enable(lcdifv3->clk_disp_apb); + + writel(CTRL_SW_RESET, lcdifv3->base + LCDIFV3_CTRL_CLR); + + ret = device_reset(dev); + if (ret) + dev_warn(dev, "lcdif1 reset failed: %d\n", ret); + + clk_disable_unprepare(lcdifv3->clk_disp_axi); + clk_disable_unprepare(lcdifv3->clk_disp_apb); + } + + imx_lcdifv3_of_parse_thres(lcdifv3); + + platform_set_drvdata(pdev, lcdifv3); + + if (soc_pdata && soc_pdata->hdmimix) { + ret = hdmimix_lcdif3_setup(lcdifv3); + if (ret < 0) { + dev_err(dev, "hdmimix lcdif3 setup failed\n"); + return ret; + } + } + + atomic_set(&lcdifv3->rpm_suspended, 0); + pm_runtime_enable(dev); + atomic_inc(&lcdifv3->rpm_suspended); + + dev_dbg(dev, "%s: probe end\n", __func__); + + return lcdifv3_add_client_devices(lcdifv3); +} + +static int imx_lcdifv3_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int imx_lcdifv3_runtime_suspend(struct device *dev) +{ + struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(dev); + + if (atomic_inc_return(&lcdifv3->rpm_suspended) > 1) + return 0; + + lcdifv3_disable_clocks(lcdifv3); + + release_bus_freq(BUS_FREQ_HIGH); + + /* clear LCDIF QoS and cache */ + if (of_device_is_compatible(dev->of_node, "fsl,imx93-lcdif")) + regmap_write(lcdifv3->gpr, 0xc, 0x0); + + return 0; +} + +static int imx_lcdifv3_runtime_resume(struct device *dev) +{ + int ret = 0; + struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(dev); + + if (unlikely(!atomic_read(&lcdifv3->rpm_suspended))) { + dev_warn(lcdifv3->dev, "Unbalanced %s!\n", __func__); + return 0; + } + + if (!atomic_dec_and_test(&lcdifv3->rpm_suspended)) + return 0; + + /* set LCDIF QoS and cache */ + if (of_device_is_compatible(dev->of_node, "fsl,imx93-lcdif")) + regmap_write(lcdifv3->gpr, 0xc, 0x3712); + + request_bus_freq(BUS_FREQ_HIGH); + + ret = lcdifv3_enable_clocks(lcdifv3); + if (ret) { + release_bus_freq(BUS_FREQ_HIGH); + return ret; + } + + /* clear sw_reset */ + writel(CTRL_SW_RESET, lcdifv3->base + LCDIFV3_CTRL_CLR); + + /* enable plane FIFO panic */ + lcdifv3_enable_plane_panic(lcdifv3); + + return ret; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int imx_lcdifv3_suspend(struct device *dev) +{ + return imx_lcdifv3_runtime_suspend(dev); +} + +static int imx_lcdifv3_resume(struct device *dev) +{ + return imx_lcdifv3_runtime_resume(dev); +} +#endif + +static const struct dev_pm_ops imx_lcdifv3_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(imx_lcdifv3_suspend, + imx_lcdifv3_resume) + SET_RUNTIME_PM_OPS(imx_lcdifv3_runtime_suspend, + imx_lcdifv3_runtime_resume, NULL) +}; + +struct platform_driver imx_lcdifv3_driver = { + .probe = imx_lcdifv3_probe, + .remove = imx_lcdifv3_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = imx_lcdifv3_dt_ids, + .pm = &imx_lcdifv3_pm_ops, + }, +}; + +module_platform_driver(imx_lcdifv3_driver); + +MODULE_DESCRIPTION("NXP i.MX LCDIFV3 Display Controller driver"); +MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/imx/lcdifv3/lcdifv3-regs.h b/drivers/gpu/imx/lcdifv3/lcdifv3-regs.h new file mode 100644 index 000000000000..d47c2f80b8c3 --- /dev/null +++ b/drivers/gpu/imx/lcdifv3/lcdifv3-regs.h @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2019 NXP + */ + +#ifndef __LCDIFV3_REGS_H +#define __LCDIFV3_REGS_H + +/* regs offset */ +#define LCDIFV3_CTRL 0x00 +#define LCDIFV3_CTRL_SET 0x04 +#define LCDIFV3_CTRL_CLR 0x08 +#define LCDIFV3_CTRL_TOG 0x0c +#define LCDIFV3_DISP_PARA 0x10 +#define LCDIFV3_DISP_SIZE 0x14 +#define LCDIFV3_HSYN_PARA 0x18 +#define LCDIFV3_VSYN_PARA 0x1c +#define LCDIFV3_VSYN_HSYN_WIDTH 0x20 +#define LCDIFV3_INT_STATUS_D0 0x24 +#define LCDIFV3_INT_ENABLE_D0 0x28 +#define LCDIFV3_INT_STATUS_D1 0x30 +#define LCDIFV3_INT_ENABLE_D1 0x34 + +#define LCDIFV3_CTRLDESCL0_1 0x200 +#define LCDIFV3_CTRLDESCL0_3 0x208 +#define LCDIFV3_CTRLDESCL_LOW0_4 0x20c +#define LCDIFV3_CTRLDESCL_HIGH0_4 0x210 +#define LCDIFV3_CTRLDESCL0_5 0x214 +#define LCDIFV3_CSC0_CTRL 0x21c +#define LCDIFV3_CSC0_COEF0 0x220 +#define LCDIFV3_CSC0_COEF1 0x224 +#define LCDIFV3_CSC0_COEF2 0x228 +#define LCDIFV3_CSC0_COEF3 0x22c +#define LCDIFV3_CSC0_COEF4 0x230 +#define LCDIFV3_CSC0_COEF5 0x234 +#define LCDIFV3_PANIC0_THRES 0x238 + +/* reg bit manipulation */ +#define REG_MASK(e, s) (((1 << ((e) - (s) + 1)) - 1) << (s)) +#define REG_PUT(x, e, s) (((x) << (s)) & REG_MASK(e, s)) +#define REG_GET(x, e, s) (((x) & REG_MASK(e, s)) >> (s)) + +/* regs bit fields */ +#define CTRL_SW_RESET BIT(31) +#define CTRL_FETCH_START_OPTION(x) REG_PUT((x), 9, 8) + #define FPV 0 + #define PWV 1 + #define BPV 2 + #define RESV 3 +#define CTRL_NEG BIT(4) +#define CTRL_INV_PXCK BIT(3) +#define CTRL_INV_DE BIT(2) +#define CTRL_INV_VS BIT(1) +#define CTRL_INV_HS BIT(0) + +#define DISP_PARA_DISP_ON BIT(31) +#define DISP_PARA_SWAP_EN BIT(30) +#define DISP_PARA_LINE_PATTERN(x) REG_PUT((x), 29, 26) + /* line pattern formats (output) */ + #define LP_RGB888_OR_YUV444 0x0 + #define LP_RBG888 0x1 + #define LP_GBR888 0x2 + #define LP_GRB888_OR_UYV444 0x3 + #define LP_BRG888 0x4 + #define LP_BGR888 0x5 + #define LP_RGB555 0x6 + #define LP_RGB565 0x7 + #define LP_YUYV_16_0 0x8 + #define LP_UYVY_16_0 0x9 + #define LP_YVYU_16_0 0xa + #define LP_VYUY_16_0 0xb + #define LP_YUYV_23_8 0xc + #define LP_UYVY_23_8 0xd + #define LP_YVYU_23_8 0xe + #define LP_VYUY_23_8 0xf + +#define DISP_PARA_DISP_MODE(x) REG_PUT((x), 25, 24) +#define DISP_PARA_BGND_R(x) REG_PUT((x), 23, 16) +#define DISP_PARA_BGND_G(x) REG_PUT((x), 15, 8) +#define DISP_PARA_BGND_B(x) REG_PUT((x), 7, 0) + +#define DISP_SIZE_DELTA_Y(x) REG_PUT((x), 31, 16) +#define DISP_SIZE_DELTA_X(x) REG_PUT((x), 15, 0) + +#define HSYN_PARA_BP_H(x) REG_PUT((x), 31, 16) +#define HSYN_PARA_FP_H(x) REG_PUT((x), 15, 0) + +#define VSYN_PARA_BP_V(x) REG_PUT((x), 31, 16) +#define VSYN_PARA_FP_V(x) REG_PUT((x), 15, 0) + +#define VSYN_HSYN_WIDTH_PW_V(x) REG_PUT((x), 31, 16) +#define VSYN_HSYN_WIDTH_PW_H(x) REG_PUT((x), 15, 0) + +#define INT_STATUS_D0_FIFO_EMPTY BIT(24) +#define INT_STATUS_D0_DMA_DONE BIT(16) +#define INT_STATUS_D0_DMA_ERR BIT(8) +#define INT_STATUS_D0_VS_BLANK BIT(2) +#define INT_STATUS_D0_UNDERRUN BIT(1) +#define INT_STATUS_D0_VSYNC BIT(0) + +#define INT_ENABLE_D0_FIFO_EMPTY_EN BIT(24) +#define INT_ENABLE_D0_DMA_DONE_EN BIT(16) +#define INT_ENABLE_D0_DMA_ERR_EN BIT(8) +#define INT_ENABLE_D0_VS_BLANK_EN BIT(2) +#define INT_ENABLE_D0_UNDERRUN_EN BIT(1) +#define INT_ENABLE_D0_VSYNC_EN BIT(0) + +#define INT_STATUS_D1_PLANE_PANIC BIT(0) +#define INT_ENABLE_D1_PLANE_PANIC_EN BIT(0) + +#define CTRLDESCL0_1_HEIGHT(x) REG_PUT((x), 31, 16) +#define CTRLDESCL0_1_WIDTH(x) REG_PUT((x), 15, 0) +#define CTRLDESCL0_3_STATE_CLEAR_VSYNC BIT(23) +#define CTRLDESCL0_3_P_SIZE(x) REG_PUT((x), 22, 20) +#define CTRLDESCL0_3_T_SIZE(x) REG_PUT((x), 17, 16) +#define CTRLDESCL0_3_PITCH(x) REG_PUT((x), 15, 0) +//#define CTRLDESCL_LOW0_4_ADDR_LOW(x) REG_PUT((x), 31, 0) +#define CTRLDESCL_HIGH0_4_ADDR_HIGH(x) REG_PUT((x), 3, 0) +#define CTRLDESCL0_5_EN BIT(31) /* enable layer for DMA */ +#define CTRLDESCL0_5_SHADOW_LOAD_EN BIT(30) +#define CTRLDESCL0_5_BPP(x) REG_PUT((x), 27, 24) + /* layer encoding formats (input) */ + #define BPP16_RGB565 0x4 + #define BPP16_ARGB1555 0x5 + #define BPP16_ARGB4444 0x6 + #define BPP16_YCbCr422 0x7 + #define BPP24_RGB888 0x8 + #define BPP32_ARGB8888 0x9 + #define BPP32_ABGR8888 0xa +#define CTRLDESCL0_5_YUV_FORMAT(x) REG_PUT((x), 15, 14) + +#define CSC0_CTRL_CSC_MODE(x) REG_PUT((x), 2, 1) +#define CSC0_CTRL_BYPASS BIT(0) +#define CSC0_COEF0_A2(x) REG_PUT((x), 26, 16) +#define CSC0_COEF0_A1(x) REG_PUT((x), 10, 0) +#define CSC0_COEF1_B1(x) REG_PUT((x), 26, 16) +#define CSC0_COEF1_A3(x) REG_PUT((x), 10, 0) +#define CSC0_COEF2_B3(x) REG_PUT((x), 26, 16) +#define CSC0_COEF2_B2(x) REG_PUT((x), 10, 0) +#define CSC0_COEF3_C2(x) REG_PUT((x), 26, 16) +#define CSC0_COEF3_C1(x) REG_PUT((x), 10, 0) +#define CSC0_COEF4_D1(x) REG_PUT((x), 24, 16) +#define CSC0_COEF4_C3(x) REG_PUT((x), 10, 0) +#define CSC0_COEF5_D3(x) REG_PUT((x), 24, 16) +#define CSC0_COEF5_D2(x) REG_PUT((x), 8, 0) + +#define PANIC0_THRES_PANIC_THRES_LOW(x) REG_PUT((x), 24, 16) +#define PANIC0_THRES_PANIC_THRES_HIGH(x) REG_PUT((x), 8, 0) + +#endif /* __LCDIFV3_REGS_H */ |