diff options
Diffstat (limited to 'drivers/gpu/drm/bridge')
| -rw-r--r-- | drivers/gpu/drm/bridge/imx/Kconfig | 11 | ||||
| -rw-r--r-- | drivers/gpu/drm/bridge/imx/Makefile | 1 | ||||
| -rw-r--r-- | drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c | 158 | ||||
| -rw-r--r-- | drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 65 | ||||
| -rw-r--r-- | drivers/gpu/drm/bridge/imx/imx8qxp-ldb.c | 7 | ||||
| -rw-r--r-- | drivers/gpu/drm/bridge/ite-it66121.c | 68 | ||||
| -rw-r--r-- | drivers/gpu/drm/bridge/simple-bridge.c | 10 | ||||
| -rw-r--r-- | drivers/gpu/drm/bridge/synopsys/Kconfig | 8 | ||||
| -rw-r--r-- | drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2 | ||||
| -rw-r--r-- | drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c | 5 | ||||
| -rw-r--r-- | drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 235 | ||||
| -rw-r--r-- | drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h | 14 | ||||
| -rw-r--r-- | drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 18 | ||||
| -rw-r--r-- | drivers/gpu/drm/bridge/ti-sn65dsi86.c | 112 |
14 files changed, 662 insertions, 52 deletions
diff --git a/drivers/gpu/drm/bridge/imx/Kconfig b/drivers/gpu/drm/bridge/imx/Kconfig index 9a480c6abb85..b9028a5e5a06 100644 --- a/drivers/gpu/drm/bridge/imx/Kconfig +++ b/drivers/gpu/drm/bridge/imx/Kconfig @@ -18,12 +18,23 @@ config DRM_IMX8MP_DW_HDMI_BRIDGE depends on OF depends on COMMON_CLK select DRM_DW_HDMI + imply DRM_IMX8MP_HDMI_PAI imply DRM_IMX8MP_HDMI_PVI imply PHY_FSL_SAMSUNG_HDMI_PHY help Choose this to enable support for the internal HDMI encoder found on the i.MX8MP SoC. +config DRM_IMX8MP_HDMI_PAI + tristate "Freescale i.MX8MP HDMI PAI bridge support" + depends on OF + select DRM_DW_HDMI + select REGMAP + select REGMAP_MMIO + help + Choose this to enable support for the internal HDMI TX Parallel + Audio Interface found on the Freescale i.MX8MP SoC. + config DRM_IMX8MP_HDMI_PVI tristate "Freescale i.MX8MP HDMI PVI bridge support" depends on OF diff --git a/drivers/gpu/drm/bridge/imx/Makefile b/drivers/gpu/drm/bridge/imx/Makefile index dd5d48584806..8d01fda25451 100644 --- a/drivers/gpu/drm/bridge/imx/Makefile +++ b/drivers/gpu/drm/bridge/imx/Makefile @@ -1,6 +1,7 @@ obj-$(CONFIG_DRM_IMX_LDB_HELPER) += imx-ldb-helper.o obj-$(CONFIG_DRM_IMX_LEGACY_BRIDGE) += imx-legacy-bridge.o obj-$(CONFIG_DRM_IMX8MP_DW_HDMI_BRIDGE) += imx8mp-hdmi-tx.o +obj-$(CONFIG_DRM_IMX8MP_HDMI_PAI) += imx8mp-hdmi-pai.o obj-$(CONFIG_DRM_IMX8MP_HDMI_PVI) += imx8mp-hdmi-pvi.o obj-$(CONFIG_DRM_IMX8QM_LDB) += imx8qm-ldb.o obj-$(CONFIG_DRM_IMX8QXP_LDB) += imx8qxp-ldb.o diff --git a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c new file mode 100644 index 000000000000..8d13a35b206a --- /dev/null +++ b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2025 NXP + */ + +#include <linux/bitfield.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <drm/bridge/dw_hdmi.h> +#include <sound/asoundef.h> + +#define HTX_PAI_CTRL 0x00 +#define ENABLE BIT(0) + +#define HTX_PAI_CTRL_EXT 0x04 +#define WTMK_HIGH_MASK GENMASK(31, 24) +#define WTMK_LOW_MASK GENMASK(23, 16) +#define NUM_CH_MASK GENMASK(10, 8) +#define WTMK_HIGH(n) FIELD_PREP(WTMK_HIGH_MASK, (n)) +#define WTMK_LOW(n) FIELD_PREP(WTMK_LOW_MASK, (n)) +#define NUM_CH(n) FIELD_PREP(NUM_CH_MASK, (n) - 1) + +#define HTX_PAI_FIELD_CTRL 0x08 +#define PRE_SEL GENMASK(28, 24) +#define D_SEL GENMASK(23, 20) +#define V_SEL GENMASK(19, 15) +#define U_SEL GENMASK(14, 10) +#define C_SEL GENMASK(9, 5) +#define P_SEL GENMASK(4, 0) + +struct imx8mp_hdmi_pai { + struct regmap *regmap; +}; + +static void imx8mp_hdmi_pai_enable(struct dw_hdmi *dw_hdmi, int channel, + int width, int rate, int non_pcm, + int iec958) +{ + const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi); + struct imx8mp_hdmi_pai *hdmi_pai = pdata->priv_audio; + int val; + + /* PAI set control extended */ + val = WTMK_HIGH(3) | WTMK_LOW(3); + val |= NUM_CH(channel); + regmap_write(hdmi_pai->regmap, HTX_PAI_CTRL_EXT, val); + + /* IEC60958 format */ + if (iec958) { + val = FIELD_PREP_CONST(P_SEL, + __bf_shf(IEC958_SUBFRAME_PARITY)); + val |= FIELD_PREP_CONST(C_SEL, + __bf_shf(IEC958_SUBFRAME_CHANNEL_STATUS)); + val |= FIELD_PREP_CONST(U_SEL, + __bf_shf(IEC958_SUBFRAME_USER_DATA)); + val |= FIELD_PREP_CONST(V_SEL, + __bf_shf(IEC958_SUBFRAME_VALIDITY)); + val |= FIELD_PREP_CONST(D_SEL, + __bf_shf(IEC958_SUBFRAME_SAMPLE_24_MASK)); + val |= FIELD_PREP_CONST(PRE_SEL, + __bf_shf(IEC958_SUBFRAME_PREAMBLE_MASK)); + } else { + /* + * The allowed PCM widths are 24bit and 32bit, as they are supported + * by aud2htx module. + * for 24bit, D_SEL = 0, select all the bits. + * for 32bit, D_SEL = 8, select 24bit in MSB. + */ + val = FIELD_PREP(D_SEL, width - 24); + } + + regmap_write(hdmi_pai->regmap, HTX_PAI_FIELD_CTRL, val); + + /* PAI start running */ + regmap_write(hdmi_pai->regmap, HTX_PAI_CTRL, ENABLE); +} + +static void imx8mp_hdmi_pai_disable(struct dw_hdmi *dw_hdmi) +{ + const struct dw_hdmi_plat_data *pdata = dw_hdmi_to_plat_data(dw_hdmi); + struct imx8mp_hdmi_pai *hdmi_pai = pdata->priv_audio; + + /* Stop PAI */ + regmap_write(hdmi_pai->regmap, HTX_PAI_CTRL, 0); +} + +static const struct regmap_config imx8mp_hdmi_pai_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = HTX_PAI_FIELD_CTRL, +}; + +static int imx8mp_hdmi_pai_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dw_hdmi_plat_data *plat_data = data; + struct imx8mp_hdmi_pai *hdmi_pai; + struct resource *res; + void __iomem *base; + + hdmi_pai = devm_kzalloc(dev, sizeof(*hdmi_pai), GFP_KERNEL); + if (!hdmi_pai) + return -ENOMEM; + + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) + return PTR_ERR(base); + + hdmi_pai->regmap = devm_regmap_init_mmio_clk(dev, "apb", base, + &imx8mp_hdmi_pai_regmap_config); + if (IS_ERR(hdmi_pai->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(hdmi_pai->regmap); + } + + plat_data->enable_audio = imx8mp_hdmi_pai_enable; + plat_data->disable_audio = imx8mp_hdmi_pai_disable; + plat_data->priv_audio = hdmi_pai; + + return 0; +} + +static const struct component_ops imx8mp_hdmi_pai_ops = { + .bind = imx8mp_hdmi_pai_bind, +}; + +static int imx8mp_hdmi_pai_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &imx8mp_hdmi_pai_ops); +} + +static void imx8mp_hdmi_pai_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &imx8mp_hdmi_pai_ops); +} + +static const struct of_device_id imx8mp_hdmi_pai_of_table[] = { + { .compatible = "fsl,imx8mp-hdmi-pai" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx8mp_hdmi_pai_of_table); + +static struct platform_driver imx8mp_hdmi_pai_platform_driver = { + .probe = imx8mp_hdmi_pai_probe, + .remove = imx8mp_hdmi_pai_remove, + .driver = { + .name = "imx8mp-hdmi-pai", + .of_match_table = imx8mp_hdmi_pai_of_table, + }, +}; +module_platform_driver(imx8mp_hdmi_pai_platform_driver); + +MODULE_DESCRIPTION("i.MX8MP HDMI PAI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c index 1e7a789ec289..32fd3554e267 100644 --- a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c +++ b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c @@ -5,11 +5,13 @@ */ #include <linux/clk.h> +#include <linux/component.h> #include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_device.h> #include <drm/bridge/dw_hdmi.h> #include <drm/drm_modes.h> +#include <drm/drm_of.h> struct imx8mp_hdmi { struct dw_hdmi_plat_data plat_data; @@ -79,10 +81,45 @@ static const struct dw_hdmi_phy_ops imx8mp_hdmi_phy_ops = { .update_hpd = dw_hdmi_phy_update_hpd, }; +static int imx8mp_dw_hdmi_bind(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct imx8mp_hdmi *hdmi = dev_get_drvdata(dev); + int ret; + + ret = component_bind_all(dev, &hdmi->plat_data); + if (ret) + return dev_err_probe(dev, ret, "component_bind_all failed!\n"); + + hdmi->dw_hdmi = dw_hdmi_probe(pdev, &hdmi->plat_data); + if (IS_ERR(hdmi->dw_hdmi)) { + component_unbind_all(dev, &hdmi->plat_data); + return PTR_ERR(hdmi->dw_hdmi); + } + + return 0; +} + +static void imx8mp_dw_hdmi_unbind(struct device *dev) +{ + struct imx8mp_hdmi *hdmi = dev_get_drvdata(dev); + + dw_hdmi_remove(hdmi->dw_hdmi); + + component_unbind_all(dev, &hdmi->plat_data); +} + +static const struct component_master_ops imx8mp_dw_hdmi_ops = { + .bind = imx8mp_dw_hdmi_bind, + .unbind = imx8mp_dw_hdmi_unbind, +}; + static int imx8mp_dw_hdmi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct dw_hdmi_plat_data *plat_data; + struct component_match *match = NULL; + struct device_node *remote; struct imx8mp_hdmi *hdmi; hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); @@ -102,20 +139,38 @@ static int imx8mp_dw_hdmi_probe(struct platform_device *pdev) plat_data->priv_data = hdmi; plat_data->phy_force_vendor = true; - hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data); - if (IS_ERR(hdmi->dw_hdmi)) - return PTR_ERR(hdmi->dw_hdmi); - platform_set_drvdata(pdev, hdmi); + /* port@2 is for hdmi_pai device */ + remote = of_graph_get_remote_node(pdev->dev.of_node, 2, 0); + if (!remote) { + hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data); + if (IS_ERR(hdmi->dw_hdmi)) + return PTR_ERR(hdmi->dw_hdmi); + } else { + drm_of_component_match_add(dev, &match, component_compare_of, remote); + + of_node_put(remote); + + return component_master_add_with_match(dev, &imx8mp_dw_hdmi_ops, match); + } + return 0; } static void imx8mp_dw_hdmi_remove(struct platform_device *pdev) { struct imx8mp_hdmi *hdmi = platform_get_drvdata(pdev); + struct device_node *remote; - dw_hdmi_remove(hdmi->dw_hdmi); + remote = of_graph_get_remote_node(pdev->dev.of_node, 2, 0); + if (remote) { + of_node_put(remote); + + component_master_del(&pdev->dev, &imx8mp_dw_hdmi_ops); + } else { + dw_hdmi_remove(hdmi->dw_hdmi); + } } static int imx8mp_dw_hdmi_pm_suspend(struct device *dev) diff --git a/drivers/gpu/drm/bridge/imx/imx8qxp-ldb.c b/drivers/gpu/drm/bridge/imx/imx8qxp-ldb.c index 5d272916e200..122502968927 100644 --- a/drivers/gpu/drm/bridge/imx/imx8qxp-ldb.c +++ b/drivers/gpu/drm/bridge/imx/imx8qxp-ldb.c @@ -683,11 +683,6 @@ static void imx8qxp_ldb_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); } -static int imx8qxp_ldb_runtime_suspend(struct device *dev) -{ - return 0; -} - static int imx8qxp_ldb_runtime_resume(struct device *dev) { struct imx8qxp_ldb *imx8qxp_ldb = dev_get_drvdata(dev); @@ -700,7 +695,7 @@ static int imx8qxp_ldb_runtime_resume(struct device *dev) } static const struct dev_pm_ops imx8qxp_ldb_pm_ops = { - RUNTIME_PM_OPS(imx8qxp_ldb_runtime_suspend, imx8qxp_ldb_runtime_resume, NULL) + RUNTIME_PM_OPS(NULL, imx8qxp_ldb_runtime_resume, NULL) }; static const struct of_device_id imx8qxp_ldb_dt_ids[] = { diff --git a/drivers/gpu/drm/bridge/ite-it66121.c b/drivers/gpu/drm/bridge/ite-it66121.c index aa7b1dcc5d70..0185f61e6e59 100644 --- a/drivers/gpu/drm/bridge/ite-it66121.c +++ b/drivers/gpu/drm/bridge/ite-it66121.c @@ -287,6 +287,7 @@ enum chip_id { ID_IT6610, ID_IT66121, + ID_IT66122, }; struct it66121_chip_info { @@ -312,7 +313,7 @@ struct it66121_ctx { u8 swl; bool auto_cts; } audio; - const struct it66121_chip_info *info; + enum chip_id id; }; static const struct regmap_range_cfg it66121_regmap_banks[] = { @@ -402,7 +403,7 @@ static int it66121_configure_afe(struct it66121_ctx *ctx, if (ret) return ret; - if (ctx->info->id == ID_IT66121) { + if (ctx->id == ID_IT66121 || ctx->id == ID_IT66122) { ret = regmap_write_bits(ctx->regmap, IT66121_AFE_IP_REG, IT66121_AFE_IP_EC1, 0); if (ret) @@ -428,7 +429,7 @@ static int it66121_configure_afe(struct it66121_ctx *ctx, if (ret) return ret; - if (ctx->info->id == ID_IT66121) { + if (ctx->id == ID_IT66121 || ctx->id == ID_IT66122) { ret = regmap_write_bits(ctx->regmap, IT66121_AFE_IP_REG, IT66121_AFE_IP_EC1, IT66121_AFE_IP_EC1); @@ -449,7 +450,7 @@ static int it66121_configure_afe(struct it66121_ctx *ctx, if (ret) return ret; - if (ctx->info->id == ID_IT6610) { + if (ctx->id == ID_IT6610) { ret = regmap_write_bits(ctx->regmap, IT66121_AFE_XP_REG, IT6610_AFE_XP_BYPASS, IT6610_AFE_XP_BYPASS); @@ -599,7 +600,7 @@ static int it66121_bridge_attach(struct drm_bridge *bridge, if (ret) return ret; - if (ctx->info->id == ID_IT66121) { + if (ctx->id == ID_IT66121 || ctx->id == ID_IT66122) { ret = regmap_write_bits(ctx->regmap, IT66121_CLK_BANK_REG, IT66121_CLK_BANK_PWROFF_RCLK, 0); if (ret) @@ -748,7 +749,7 @@ static int it66121_bridge_check(struct drm_bridge *bridge, { struct it66121_ctx *ctx = container_of(bridge, struct it66121_ctx, bridge); - if (ctx->info->id == ID_IT6610) { + if (ctx->id == ID_IT6610) { /* The IT6610 only supports these settings */ bridge_state->input_bus_cfg.flags |= DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; @@ -802,7 +803,7 @@ void it66121_bridge_mode_set(struct drm_bridge *bridge, if (regmap_write(ctx->regmap, IT66121_HDMI_MODE_REG, IT66121_HDMI_MODE_HDMI)) goto unlock; - if (ctx->info->id == ID_IT66121 && + if ((ctx->id == ID_IT66121 || ctx->id == ID_IT66122) && regmap_write_bits(ctx->regmap, IT66121_CLK_BANK_REG, IT66121_CLK_BANK_PWROFF_TXCLK, IT66121_CLK_BANK_PWROFF_TXCLK)) { @@ -815,7 +816,7 @@ void it66121_bridge_mode_set(struct drm_bridge *bridge, if (it66121_configure_afe(ctx, adjusted_mode)) goto unlock; - if (ctx->info->id == ID_IT66121 && + if ((ctx->id == ID_IT66121 || ctx->id == ID_IT66122) && regmap_write_bits(ctx->regmap, IT66121_CLK_BANK_REG, IT66121_CLK_BANK_PWROFF_TXCLK, 0)) { goto unlock; @@ -1384,8 +1385,6 @@ static int it66121_audio_startup(struct device *dev, void *data) int ret; struct it66121_ctx *ctx = dev_get_drvdata(dev); - dev_dbg(dev, "%s\n", __func__); - mutex_lock(&ctx->lock); ret = it661221_audio_output_enable(ctx, true); if (ret) @@ -1401,8 +1400,6 @@ static void it66121_audio_shutdown(struct device *dev, void *data) int ret; struct it66121_ctx *ctx = dev_get_drvdata(dev); - dev_dbg(dev, "%s\n", __func__); - mutex_lock(&ctx->lock); ret = it661221_audio_output_enable(ctx, false); if (ret) @@ -1479,8 +1476,6 @@ static int it66121_audio_codec_init(struct it66121_ctx *ctx, struct device *dev) .no_capture_mute = 1, }; - dev_dbg(dev, "%s\n", __func__); - if (!of_property_present(dev->of_node, "#sound-dai-cells")) { dev_info(dev, "No \"#sound-dai-cells\", no audio\n"); return 0; @@ -1504,13 +1499,20 @@ static const char * const it66121_supplies[] = { "vcn33", "vcn18", "vrf12" }; +static const struct it66121_chip_info it66xx_chip_info[] = { + {.id = ID_IT6610, .vid = 0xca00, .pid = 0x0611 }, + {.id = ID_IT66121, .vid = 0x4954, .pid = 0x0612 }, + {.id = ID_IT66122, .vid = 0x4954, .pid = 0x0622 }, +}; + static int it66121_probe(struct i2c_client *client) { u32 revision_id, vendor_ids[2] = { 0 }, device_ids[2] = { 0 }; struct device_node *ep; - int ret; + int ret, i; struct it66121_ctx *ctx; struct device *dev = &client->dev; + const struct it66121_chip_info *chip_info; if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { dev_err(dev, "I2C check functionality failed.\n"); @@ -1528,7 +1530,6 @@ static int it66121_probe(struct i2c_client *client) ctx->dev = dev; ctx->client = client; - ctx->info = i2c_get_match_data(client); of_property_read_u32(ep, "bus-width", &ctx->bus_width); of_node_put(ep); @@ -1574,11 +1575,18 @@ static int it66121_probe(struct i2c_client *client) revision_id = FIELD_GET(IT66121_REVISION_MASK, device_ids[1]); device_ids[1] &= IT66121_DEVICE_ID1_MASK; - if ((vendor_ids[1] << 8 | vendor_ids[0]) != ctx->info->vid || - (device_ids[1] << 8 | device_ids[0]) != ctx->info->pid) { - return -ENODEV; + for (i = 0; i < ARRAY_SIZE(it66xx_chip_info); i++) { + chip_info = &it66xx_chip_info[i]; + if ((vendor_ids[1] << 8 | vendor_ids[0]) == chip_info->vid && + (device_ids[1] << 8 | device_ids[0]) == chip_info->pid) { + ctx->id = chip_info->id; + break; + } } + if (i == ARRAY_SIZE(it66xx_chip_info)) + return -ENODEV; + ctx->bridge.of_node = dev->of_node; ctx->bridge.type = DRM_MODE_CONNECTOR_HDMIA; ctx->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID; @@ -1612,28 +1620,18 @@ static void it66121_remove(struct i2c_client *client) mutex_destroy(&ctx->lock); } -static const struct it66121_chip_info it66121_chip_info = { - .id = ID_IT66121, - .vid = 0x4954, - .pid = 0x0612, -}; - -static const struct it66121_chip_info it6610_chip_info = { - .id = ID_IT6610, - .vid = 0xca00, - .pid = 0x0611, -}; - static const struct of_device_id it66121_dt_match[] = { - { .compatible = "ite,it66121", &it66121_chip_info }, - { .compatible = "ite,it6610", &it6610_chip_info }, + { .compatible = "ite,it6610" }, + { .compatible = "ite,it66121" }, + { .compatible = "ite,it66122" }, { } }; MODULE_DEVICE_TABLE(of, it66121_dt_match); static const struct i2c_device_id it66121_id[] = { - { "it66121", (kernel_ulong_t) &it66121_chip_info }, - { "it6610", (kernel_ulong_t) &it6610_chip_info }, + { .name = "it6610" }, + { .name = "it66121" }, + { .name = "it66122" }, { } }; MODULE_DEVICE_TABLE(i2c, it66121_id); diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c index e4d0bc2200f8..2cd1847ba776 100644 --- a/drivers/gpu/drm/bridge/simple-bridge.c +++ b/drivers/gpu/drm/bridge/simple-bridge.c @@ -262,6 +262,16 @@ static const struct of_device_id simple_bridge_match[] = { .connector_type = DRM_MODE_CONNECTOR_VGA, }, }, { + .compatible = "asl-tek,cs5263", + .data = &(const struct simple_bridge_info) { + .connector_type = DRM_MODE_CONNECTOR_HDMIA, + }, + }, { + .compatible = "parade,ps185hdm", + .data = &(const struct simple_bridge_info) { + .connector_type = DRM_MODE_CONNECTOR_HDMIA, + }, + }, { .compatible = "radxa,ra620", .data = &(const struct simple_bridge_info) { .connector_type = DRM_MODE_CONNECTOR_HDMIA, diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig index 2c5e532410de..a46df7583bcf 100644 --- a/drivers/gpu/drm/bridge/synopsys/Kconfig +++ b/drivers/gpu/drm/bridge/synopsys/Kconfig @@ -61,6 +61,14 @@ config DRM_DW_HDMI_QP select DRM_KMS_HELPER select REGMAP_MMIO +config DRM_DW_HDMI_QP_CEC + bool "Synopsis Designware QP CEC interface" + depends on DRM_DW_HDMI_QP + select DRM_DISPLAY_HDMI_CEC_HELPER + help + Support the CEC interface which is part of the Synopsys + Designware HDMI QP block. + config DRM_DW_MIPI_DSI tristate select DRM_KMS_HELPER diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c index 9bbfe8da3de0..82aaf74e1bc0 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -2049,6 +2049,8 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, bridge->type = DRM_MODE_CONNECTOR_DisplayPort; bridge->ycbcr_420_allowed = true; + devm_drm_bridge_add(dev, bridge); + dp->aux.dev = dev; dp->aux.drm_dev = encoder->dev; dp->aux.name = dev_name(dev); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c index ab18f9a3bf23..df7a37eb47f4 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c @@ -90,6 +90,11 @@ static int audio_hw_params(struct device *dev, void *data, params->iec.status[0] & IEC958_AES0_NONAUDIO); dw_hdmi_set_sample_width(dw->data.hdmi, params->sample_width); + if (daifmt->bit_fmt == SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE) + dw_hdmi_set_sample_iec958(dw->data.hdmi, 1); + else + dw_hdmi_set_sample_iec958(dw->data.hdmi, 0); + return 0; } diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index 39332c57f2c5..fe4c026280f0 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -18,6 +18,7 @@ #include <drm/bridge/dw_hdmi_qp.h> #include <drm/display/drm_hdmi_helper.h> +#include <drm/display/drm_hdmi_cec_helper.h> #include <drm/display/drm_hdmi_state_helper.h> #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> @@ -26,6 +27,8 @@ #include <drm/drm_edid.h> #include <drm/drm_modes.h> +#include <media/cec.h> + #include <sound/hdmi-codec.h> #include "dw-hdmi-qp.h" @@ -131,17 +134,34 @@ struct dw_hdmi_qp_i2c { bool is_segment; }; +#ifdef CONFIG_DRM_DW_HDMI_QP_CEC +struct dw_hdmi_qp_cec { + struct drm_connector *connector; + int irq; + u32 addresses; + struct cec_msg rx_msg; + u8 tx_status; + bool tx_done; + bool rx_done; +}; +#endif + struct dw_hdmi_qp { struct drm_bridge bridge; struct device *dev; struct dw_hdmi_qp_i2c *i2c; +#ifdef CONFIG_DRM_DW_HDMI_QP_CEC + struct dw_hdmi_qp_cec *cec; +#endif + struct { const struct dw_hdmi_qp_phy_ops *ops; void *data; } phy; + unsigned long ref_clk_rate; struct regmap *regm; unsigned long tmds_char_rate; @@ -848,8 +868,9 @@ static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, return; if (connector->display_info.is_hdmi) { - dev_dbg(hdmi->dev, "%s mode=HDMI rate=%llu\n", - __func__, conn_state->hdmi.tmds_char_rate); + dev_dbg(hdmi->dev, "%s mode=HDMI %s rate=%llu bpc=%u\n", __func__, + drm_hdmi_connector_get_output_format_name(conn_state->hdmi.output_format), + conn_state->hdmi.tmds_char_rate, conn_state->hdmi.output_bpc); op_mode = 0; hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate; } else { @@ -965,6 +986,179 @@ static int dw_hdmi_qp_bridge_write_infoframe(struct drm_bridge *bridge, } } +#ifdef CONFIG_DRM_DW_HDMI_QP_CEC +static irqreturn_t dw_hdmi_qp_cec_hardirq(int irq, void *dev_id) +{ + struct dw_hdmi_qp *hdmi = dev_id; + struct dw_hdmi_qp_cec *cec = hdmi->cec; + irqreturn_t ret = IRQ_HANDLED; + u32 stat; + + stat = dw_hdmi_qp_read(hdmi, CEC_INT_STATUS); + if (stat == 0) + return IRQ_NONE; + + dw_hdmi_qp_write(hdmi, stat, CEC_INT_CLEAR); + + if (stat & CEC_STAT_LINE_ERR) { + cec->tx_status = CEC_TX_STATUS_ERROR; + cec->tx_done = true; + ret = IRQ_WAKE_THREAD; + } else if (stat & CEC_STAT_DONE) { + cec->tx_status = CEC_TX_STATUS_OK; + cec->tx_done = true; + ret = IRQ_WAKE_THREAD; + } else if (stat & CEC_STAT_NACK) { + cec->tx_status = CEC_TX_STATUS_NACK; + cec->tx_done = true; + ret = IRQ_WAKE_THREAD; + } + + if (stat & CEC_STAT_EOM) { + unsigned int len, i, val; + + val = dw_hdmi_qp_read(hdmi, CEC_RX_COUNT_STATUS); + len = (val & 0xf) + 1; + + if (len > sizeof(cec->rx_msg.msg)) + len = sizeof(cec->rx_msg.msg); + + for (i = 0; i < 4; i++) { + val = dw_hdmi_qp_read(hdmi, CEC_RX_DATA3_0 + i * 4); + cec->rx_msg.msg[i * 4] = val & 0xff; + cec->rx_msg.msg[i * 4 + 1] = (val >> 8) & 0xff; + cec->rx_msg.msg[i * 4 + 2] = (val >> 16) & 0xff; + cec->rx_msg.msg[i * 4 + 3] = (val >> 24) & 0xff; + } + + dw_hdmi_qp_write(hdmi, 1, CEC_LOCK_CONTROL); + + cec->rx_msg.len = len; + cec->rx_done = true; + + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +static irqreturn_t dw_hdmi_qp_cec_thread(int irq, void *dev_id) +{ + struct dw_hdmi_qp *hdmi = dev_id; + struct dw_hdmi_qp_cec *cec = hdmi->cec; + + if (cec->tx_done) { + cec->tx_done = false; + drm_connector_hdmi_cec_transmit_attempt_done(cec->connector, + cec->tx_status); + } + + if (cec->rx_done) { + cec->rx_done = false; + drm_connector_hdmi_cec_received_msg(cec->connector, &cec->rx_msg); + } + + return IRQ_HANDLED; +} + +static int dw_hdmi_qp_cec_init(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); + struct dw_hdmi_qp_cec *cec = hdmi->cec; + + cec->connector = connector; + + dw_hdmi_qp_write(hdmi, 0, CEC_TX_COUNT); + dw_hdmi_qp_write(hdmi, ~0, CEC_INT_CLEAR); + dw_hdmi_qp_write(hdmi, 0, CEC_INT_MASK_N); + + return devm_request_threaded_irq(hdmi->dev, cec->irq, + dw_hdmi_qp_cec_hardirq, + dw_hdmi_qp_cec_thread, IRQF_SHARED, + dev_name(hdmi->dev), hdmi); +} + +static int dw_hdmi_qp_cec_log_addr(struct drm_bridge *bridge, u8 logical_addr) +{ + struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); + struct dw_hdmi_qp_cec *cec = hdmi->cec; + + if (logical_addr == CEC_LOG_ADDR_INVALID) + cec->addresses = 0; + else + cec->addresses |= BIT(logical_addr) | CEC_ADDR_BROADCAST; + + dw_hdmi_qp_write(hdmi, cec->addresses, CEC_ADDR); + + return 0; +} + +static int dw_hdmi_qp_cec_enable(struct drm_bridge *bridge, bool enable) +{ + struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); + unsigned int irqs; + u32 swdisable; + + if (!enable) { + dw_hdmi_qp_write(hdmi, 0, CEC_INT_MASK_N); + dw_hdmi_qp_write(hdmi, ~0, CEC_INT_CLEAR); + + swdisable = dw_hdmi_qp_read(hdmi, GLOBAL_SWDISABLE); + swdisable = swdisable | CEC_SWDISABLE; + dw_hdmi_qp_write(hdmi, swdisable, GLOBAL_SWDISABLE); + } else { + swdisable = dw_hdmi_qp_read(hdmi, GLOBAL_SWDISABLE); + swdisable = swdisable & ~CEC_SWDISABLE; + dw_hdmi_qp_write(hdmi, swdisable, GLOBAL_SWDISABLE); + + dw_hdmi_qp_write(hdmi, ~0, CEC_INT_CLEAR); + dw_hdmi_qp_write(hdmi, 1, CEC_LOCK_CONTROL); + + dw_hdmi_qp_cec_log_addr(bridge, CEC_LOG_ADDR_INVALID); + + irqs = CEC_STAT_LINE_ERR | CEC_STAT_NACK | CEC_STAT_EOM | + CEC_STAT_DONE; + dw_hdmi_qp_write(hdmi, ~0, CEC_INT_CLEAR); + dw_hdmi_qp_write(hdmi, irqs, CEC_INT_MASK_N); + } + + return 0; +} + +static int dw_hdmi_qp_cec_transmit(struct drm_bridge *bridge, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); + unsigned int i; + u32 val; + + for (i = 0; i < msg->len; i++) { + if (!(i % 4)) + val = msg->msg[i]; + if ((i % 4) == 1) + val |= msg->msg[i] << 8; + if ((i % 4) == 2) + val |= msg->msg[i] << 16; + if ((i % 4) == 3) + val |= msg->msg[i] << 24; + + if (i == (msg->len - 1) || (i % 4) == 3) + dw_hdmi_qp_write(hdmi, val, CEC_TX_DATA3_0 + (i / 4) * 4); + } + + dw_hdmi_qp_write(hdmi, msg->len - 1, CEC_TX_COUNT); + dw_hdmi_qp_write(hdmi, CEC_CTRL_START, CEC_TX_CONTROL); + + return 0; +} +#else +#define dw_hdmi_qp_cec_init NULL +#define dw_hdmi_qp_cec_enable NULL +#define dw_hdmi_qp_cec_log_addr NULL +#define dw_hdmi_qp_cec_transmit NULL +#endif /* CONFIG_DRM_DW_HDMI_QP_CEC */ + static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, @@ -979,6 +1173,10 @@ static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = { .hdmi_audio_startup = dw_hdmi_qp_audio_enable, .hdmi_audio_shutdown = dw_hdmi_qp_audio_disable, .hdmi_audio_prepare = dw_hdmi_qp_audio_prepare, + .hdmi_cec_init = dw_hdmi_qp_cec_init, + .hdmi_cec_enable = dw_hdmi_qp_cec_enable, + .hdmi_cec_log_addr = dw_hdmi_qp_cec_log_addr, + .hdmi_cec_transmit = dw_hdmi_qp_cec_transmit, }; static irqreturn_t dw_hdmi_qp_main_hardirq(int irq, void *dev_id) @@ -1014,13 +1212,11 @@ static void dw_hdmi_qp_init_hw(struct dw_hdmi_qp *hdmi) { dw_hdmi_qp_write(hdmi, 0, MAINUNIT_0_INT_MASK_N); dw_hdmi_qp_write(hdmi, 0, MAINUNIT_1_INT_MASK_N); - dw_hdmi_qp_write(hdmi, 428571429, TIMER_BASE_CONFIG0); + dw_hdmi_qp_write(hdmi, hdmi->ref_clk_rate, TIMER_BASE_CONFIG0); /* Software reset */ dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0); - dw_hdmi_qp_write(hdmi, 0x085c085c, I2CM_FM_SCL_CONFIG0); - dw_hdmi_qp_mod(hdmi, 0, I2CM_FM_EN, I2CM_INTERFACE_CONTROL0); /* Clear DONE and ERROR interrupts */ @@ -1066,6 +1262,13 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, hdmi->phy.ops = plat_data->phy_ops; hdmi->phy.data = plat_data->phy_data; + if (plat_data->ref_clk_rate) { + hdmi->ref_clk_rate = plat_data->ref_clk_rate; + } else { + hdmi->ref_clk_rate = 428571429; + dev_warn(dev, "Set ref_clk_rate to vendor default\n"); + } + dw_hdmi_qp_init_hw(hdmi); ret = devm_request_threaded_irq(dev, plat_data->main_irq, @@ -1085,6 +1288,12 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, hdmi->bridge.vendor = "Synopsys"; hdmi->bridge.product = "DW HDMI QP TX"; + if (plat_data->supported_formats) + hdmi->bridge.supported_formats = plat_data->supported_formats; + + if (plat_data->max_bpc) + hdmi->bridge.max_bpc = plat_data->max_bpc; + hdmi->bridge.ddc = dw_hdmi_qp_i2c_adapter(hdmi); if (IS_ERR(hdmi->bridge.ddc)) return ERR_CAST(hdmi->bridge.ddc); @@ -1093,6 +1302,22 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, hdmi->bridge.hdmi_audio_dev = dev; hdmi->bridge.hdmi_audio_dai_port = 1; +#ifdef CONFIG_DRM_DW_HDMI_QP_CEC + if (plat_data->cec_irq) { + hdmi->bridge.ops |= DRM_BRIDGE_OP_HDMI_CEC_ADAPTER; + hdmi->bridge.hdmi_cec_dev = dev; + hdmi->bridge.hdmi_cec_adapter_name = dev_name(dev); + + hdmi->cec = devm_kzalloc(hdmi->dev, sizeof(*hdmi->cec), GFP_KERNEL); + if (!hdmi->cec) + return ERR_PTR(-ENOMEM); + + hdmi->cec->irq = plat_data->cec_irq; + } else { + dev_warn(dev, "Disabled CEC support due to missing IRQ\n"); + } +#endif + ret = devm_drm_bridge_add(dev, &hdmi->bridge); if (ret) return ERR_PTR(ret); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h index 72987e6c4689..91a15f82e32a 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h @@ -488,9 +488,23 @@ #define AUDPKT_VBIT_OVR0 0xf24 /* CEC Registers */ #define CEC_TX_CONTROL 0x1000 +#define CEC_CTRL_CLEAR BIT(0) +#define CEC_CTRL_START BIT(0) #define CEC_STATUS 0x1004 +#define CEC_STAT_DONE BIT(0) +#define CEC_STAT_NACK BIT(1) +#define CEC_STAT_ARBLOST BIT(2) +#define CEC_STAT_LINE_ERR BIT(3) +#define CEC_STAT_RETRANS_FAIL BIT(4) +#define CEC_STAT_DISCARD BIT(5) +#define CEC_STAT_TX_BUSY BIT(8) +#define CEC_STAT_RX_BUSY BIT(9) +#define CEC_STAT_DRIVE_ERR BIT(10) +#define CEC_STAT_EOM BIT(11) +#define CEC_STAT_NOTIFY_ERR BIT(12) #define CEC_CONFIG 0x1008 #define CEC_ADDR 0x100c +#define CEC_ADDR_BROADCAST BIT(15) #define CEC_TX_COUNT 0x1020 #define CEC_TX_DATA3_0 0x1024 #define CEC_TX_DATA7_4 0x1028 diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 206b099a35e9..3b77e73ac0ea 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -177,6 +177,7 @@ struct dw_hdmi { spinlock_t audio_lock; struct mutex audio_mutex; + unsigned int sample_iec958; unsigned int sample_non_pcm; unsigned int sample_width; unsigned int sample_rate; @@ -198,6 +199,12 @@ struct dw_hdmi { enum drm_connector_status last_connector_result; }; +const struct dw_hdmi_plat_data *dw_hdmi_to_plat_data(struct dw_hdmi *hdmi) +{ + return hdmi->plat_data; +} +EXPORT_SYMBOL_GPL(dw_hdmi_to_plat_data); + #define HDMI_IH_PHY_STAT0_RX_SENSE \ (HDMI_IH_PHY_STAT0_RX_SENSE0 | HDMI_IH_PHY_STAT0_RX_SENSE1 | \ HDMI_IH_PHY_STAT0_RX_SENSE2 | HDMI_IH_PHY_STAT0_RX_SENSE3) @@ -712,6 +719,14 @@ void dw_hdmi_set_sample_non_pcm(struct dw_hdmi *hdmi, unsigned int non_pcm) } EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_non_pcm); +void dw_hdmi_set_sample_iec958(struct dw_hdmi *hdmi, unsigned int iec958) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi->sample_iec958 = iec958; + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_iec958); + void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate) { mutex_lock(&hdmi->audio_mutex); @@ -843,7 +858,8 @@ static void dw_hdmi_gp_audio_enable(struct dw_hdmi *hdmi) hdmi->channels, hdmi->sample_width, hdmi->sample_rate, - hdmi->sample_non_pcm); + hdmi->sample_non_pcm, + hdmi->sample_iec958); } static void dw_hdmi_gp_audio_disable(struct dw_hdmi *hdmi) diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index ae0d08e5e960..276d05d25ad8 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -106,10 +106,21 @@ #define SN_PWM_EN_INV_REG 0xA5 #define SN_PWM_INV_MASK BIT(0) #define SN_PWM_EN_MASK BIT(1) + +#define SN_IRQ_EN_REG 0xE0 +#define IRQ_EN BIT(0) + +#define SN_IRQ_EVENTS_EN_REG 0xE6 +#define HPD_INSERTION_EN BIT(1) +#define HPD_REMOVAL_EN BIT(2) + #define SN_AUX_CMD_STATUS_REG 0xF4 #define AUX_IRQ_STATUS_AUX_RPLY_TOUT BIT(3) #define AUX_IRQ_STATUS_AUX_SHORT BIT(5) #define AUX_IRQ_STATUS_NAT_I2C_FAIL BIT(6) +#define SN_IRQ_STATUS_REG 0xF5 +#define HPD_REMOVAL_STATUS BIT(2) +#define HPD_INSERTION_STATUS BIT(1) #define MIN_DSI_CLK_FREQ_MHZ 40 @@ -152,7 +163,9 @@ * @ln_assign: Value to program to the LN_ASSIGN register. * @ln_polrs: Value for the 4-bit LN_POLRS field of SN_ENH_FRAME_REG. * @comms_enabled: If true then communication over the aux channel is enabled. + * @hpd_enabled: If true then HPD events are enabled. * @comms_mutex: Protects modification of comms_enabled. + * @hpd_mutex: Protects modification of hpd_enabled. * * @gchip: If we expose our GPIOs, this is used. * @gchip_output: A cache of whether we've set GPIOs to output. This @@ -190,7 +203,9 @@ struct ti_sn65dsi86 { u8 ln_assign; u8 ln_polrs; bool comms_enabled; + bool hpd_enabled; struct mutex comms_mutex; + struct mutex hpd_mutex; #if defined(CONFIG_OF_GPIO) struct gpio_chip gchip; @@ -221,6 +236,23 @@ static const struct regmap_config ti_sn65dsi86_regmap_config = { .max_register = 0xFF, }; +static int ti_sn65dsi86_read_u8(struct ti_sn65dsi86 *pdata, unsigned int reg, + u8 *val) +{ + int ret; + unsigned int reg_val; + + ret = regmap_read(pdata->regmap, reg, ®_val); + if (ret) { + dev_err(pdata->dev, "fail to read raw reg %#x: %d\n", + reg, ret); + return ret; + } + *val = (u8)reg_val; + + return 0; +} + static int __maybe_unused ti_sn65dsi86_read_u16(struct ti_sn65dsi86 *pdata, unsigned int reg, u16 *val) { @@ -379,6 +411,7 @@ static void ti_sn65dsi86_disable_comms(struct ti_sn65dsi86 *pdata) static int __maybe_unused ti_sn65dsi86_resume(struct device *dev) { struct ti_sn65dsi86 *pdata = dev_get_drvdata(dev); + const struct i2c_client *client = to_i2c_client(pdata->dev); int ret; ret = regulator_bulk_enable(SN_REGULATOR_SUPPLY_NUM, pdata->supplies); @@ -413,6 +446,13 @@ static int __maybe_unused ti_sn65dsi86_resume(struct device *dev) if (pdata->refclk) ti_sn65dsi86_enable_comms(pdata, NULL); + if (client->irq) { + ret = regmap_update_bits(pdata->regmap, SN_IRQ_EN_REG, IRQ_EN, + IRQ_EN); + if (ret) + dev_err(pdata->dev, "Failed to enable IRQ events: %d\n", ret); + } + return ret; } @@ -1211,6 +1251,8 @@ static void ti_sn65dsi86_debugfs_init(struct drm_bridge *bridge, struct dentry * static void ti_sn_bridge_hpd_enable(struct drm_bridge *bridge) { struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge); + const struct i2c_client *client = to_i2c_client(pdata->dev); + int ret; /* * Device needs to be powered on before reading the HPD state @@ -1219,11 +1261,35 @@ static void ti_sn_bridge_hpd_enable(struct drm_bridge *bridge) */ pm_runtime_get_sync(pdata->dev); + + mutex_lock(&pdata->hpd_mutex); + pdata->hpd_enabled = true; + mutex_unlock(&pdata->hpd_mutex); + + if (client->irq) { + ret = regmap_set_bits(pdata->regmap, SN_IRQ_EVENTS_EN_REG, + HPD_REMOVAL_EN | HPD_INSERTION_EN); + if (ret) + dev_err(pdata->dev, "Failed to enable HPD events: %d\n", ret); + } } static void ti_sn_bridge_hpd_disable(struct drm_bridge *bridge) { struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge); + const struct i2c_client *client = to_i2c_client(pdata->dev); + int ret; + + if (client->irq) { + ret = regmap_clear_bits(pdata->regmap, SN_IRQ_EVENTS_EN_REG, + HPD_REMOVAL_EN | HPD_INSERTION_EN); + if (ret) + dev_err(pdata->dev, "Failed to disable HPD events: %d\n", ret); + } + + mutex_lock(&pdata->hpd_mutex); + pdata->hpd_enabled = false; + mutex_unlock(&pdata->hpd_mutex); pm_runtime_put_autosuspend(pdata->dev); } @@ -1309,6 +1375,41 @@ static int ti_sn_bridge_parse_dsi_host(struct ti_sn65dsi86 *pdata) return 0; } +static irqreturn_t ti_sn_bridge_interrupt(int irq, void *private) +{ + struct ti_sn65dsi86 *pdata = private; + struct drm_device *dev = pdata->bridge.dev; + u8 status; + int ret; + bool hpd_event; + + ret = ti_sn65dsi86_read_u8(pdata, SN_IRQ_STATUS_REG, &status); + if (ret) { + dev_err(pdata->dev, "Failed to read IRQ status: %d\n", ret); + return IRQ_NONE; + } + + hpd_event = status & (HPD_REMOVAL_STATUS | HPD_INSERTION_STATUS); + + dev_dbg(pdata->dev, "(SN_IRQ_STATUS_REG = %#x)\n", status); + if (!status) + return IRQ_NONE; + + ret = regmap_write(pdata->regmap, SN_IRQ_STATUS_REG, status); + if (ret) { + dev_err(pdata->dev, "Failed to clear IRQ status: %d\n", ret); + return IRQ_NONE; + } + + /* Only send the HPD event if we are bound with a device. */ + mutex_lock(&pdata->hpd_mutex); + if (pdata->hpd_enabled && hpd_event) + drm_kms_helper_hotplug_event(dev); + mutex_unlock(&pdata->hpd_mutex); + + return IRQ_HANDLED; +} + static int ti_sn_bridge_probe(struct auxiliary_device *adev, const struct auxiliary_device_id *id) { @@ -1931,6 +2032,7 @@ static int ti_sn65dsi86_probe(struct i2c_client *client) dev_set_drvdata(dev, pdata); pdata->dev = dev; + mutex_init(&pdata->hpd_mutex); mutex_init(&pdata->comms_mutex); pdata->regmap = devm_regmap_init_i2c(client, @@ -1971,6 +2073,16 @@ static int ti_sn65dsi86_probe(struct i2c_client *client) if (strncmp(id_buf, "68ISD ", ARRAY_SIZE(id_buf))) return dev_err_probe(dev, -EOPNOTSUPP, "unsupported device id\n"); + if (client->irq) { + ret = devm_request_threaded_irq(pdata->dev, client->irq, NULL, + ti_sn_bridge_interrupt, + IRQF_ONESHOT, + dev_name(pdata->dev), pdata); + + if (ret) + return dev_err_probe(dev, ret, "failed to request interrupt\n"); + } + /* * Break ourselves up into a collection of aux devices. The only real * motiviation here is to solve the chicken-and-egg problem of probe |
