diff options
Diffstat (limited to 'drivers/gpu/drm/bridge')
-rw-r--r-- | drivers/gpu/drm/bridge/Kconfig | 14 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 6 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/analogix/anx7625.c | 24 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/analogix/anx7625.h | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/chipone-icn6211.c | 119 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/display-connector.c | 15 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/lontium-lt9211.c | 802 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/panel.c | 7 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/synopsys/Kconfig | 10 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/synopsys/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c | 199 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 186 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/synopsys/dw-hdmi.h | 16 |
14 files changed, 1353 insertions, 51 deletions
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 658837caaf39..c08ccb4b332b 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -32,6 +32,7 @@ config DRM_CHIPONE_ICN6211 select DRM_KMS_HELPER select DRM_MIPI_DSI select DRM_PANEL_BRIDGE + select REGMAP_I2C help ICN6211 is MIPI-DSI/RGB Converter bridge from chipone. @@ -99,6 +100,19 @@ config DRM_LONTIUM_LT8912B Say M here if you want to support this hardware as a module. The module will be named "lontium-lt8912b". +config DRM_LONTIUM_LT9211 + tristate "Lontium LT9211 DSI/LVDS/DPI bridge" + depends on OF + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select REGMAP_I2C + help + Driver for Lontium LT9211 Single/Dual-Link DSI/LVDS or Single DPI + input to Single-link/Dual-Link DSI/LVDS or Single DPI output bridge + chip. + Please say Y if you have such hardware. + config DRM_LONTIUM_LT9611 tristate "Lontium LT9611 DSI/HDMI bridge" select SND_SOC_HDMI_CODEC if SND_SOC diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 425844c30495..b0edf2022fa0 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o +obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index 668dcefbae17..b3f10c54e064 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -1292,8 +1292,10 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) goto err_unregister_cec; adv7511->bridge.funcs = &adv7511_bridge_funcs; - adv7511->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID - | DRM_BRIDGE_OP_HPD; + adv7511->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID; + if (adv7511->i2c_main->irq) + adv7511->bridge.ops |= DRM_BRIDGE_OP_HPD; + adv7511->bridge.of_node = dev->of_node; adv7511->bridge.type = DRM_MODE_CONNECTOR_HDMIA; diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 6516f9570b86..376da01243a3 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -1486,12 +1486,12 @@ static void anx7625_dp_adjust_swing(struct anx7625_data *ctx) for (i = 0; i < ctx->pdata.dp_lane0_swing_reg_cnt; i++) anx7625_reg_write(ctx, ctx->i2c.tx_p1_client, DP_TX_LANE0_SWING_REG0 + i, - ctx->pdata.lane0_reg_data[i] & 0xFF); + ctx->pdata.lane0_reg_data[i]); for (i = 0; i < ctx->pdata.dp_lane1_swing_reg_cnt; i++) anx7625_reg_write(ctx, ctx->i2c.tx_p1_client, DP_TX_LANE1_SWING_REG0 + i, - ctx->pdata.lane1_reg_data[i] & 0xFF); + ctx->pdata.lane1_reg_data[i]); } static void dp_hpd_change_handler(struct anx7625_data *ctx, bool on) @@ -1598,8 +1598,8 @@ static int anx7625_get_swing_setting(struct device *dev, num_regs = DP_TX_SWING_REG_CNT; pdata->dp_lane0_swing_reg_cnt = num_regs; - of_property_read_u32_array(dev->of_node, "analogix,lane0-swing", - pdata->lane0_reg_data, num_regs); + of_property_read_u8_array(dev->of_node, "analogix,lane0-swing", + pdata->lane0_reg_data, num_regs); } if (of_get_property(dev->of_node, @@ -1608,8 +1608,8 @@ static int anx7625_get_swing_setting(struct device *dev, num_regs = DP_TX_SWING_REG_CNT; pdata->dp_lane1_swing_reg_cnt = num_regs; - of_property_read_u32_array(dev->of_node, "analogix,lane1-swing", - pdata->lane1_reg_data, num_regs); + of_property_read_u8_array(dev->of_node, "analogix,lane1-swing", + pdata->lane1_reg_data, num_regs); } return 0; @@ -1932,14 +1932,14 @@ static int anx7625_audio_get_eld(struct device *dev, void *data, struct anx7625_data *ctx = dev_get_drvdata(dev); if (!ctx->connector) { - dev_err(dev, "connector not initial\n"); - return -EINVAL; + /* Pass en empty ELD if connector not available */ + memset(buf, 0, len); + } else { + dev_dbg(dev, "audio copy eld\n"); + memcpy(buf, ctx->connector->eld, + min(sizeof(ctx->connector->eld), len)); } - dev_dbg(dev, "audio copy eld\n"); - memcpy(buf, ctx->connector->eld, - min(sizeof(ctx->connector->eld), len)); - return 0; } diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.h b/drivers/gpu/drm/bridge/analogix/anx7625.h index edbbfe410a56..e257a84db962 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.h +++ b/drivers/gpu/drm/bridge/analogix/anx7625.h @@ -426,9 +426,9 @@ struct anx7625_platform_data { int mipi_lanes; int audio_en; int dp_lane0_swing_reg_cnt; - int lane0_reg_data[DP_TX_SWING_REG_CNT]; + u8 lane0_reg_data[DP_TX_SWING_REG_CNT]; int dp_lane1_swing_reg_cnt; - int lane1_reg_data[DP_TX_SWING_REG_CNT]; + u8 lane1_reg_data[DP_TX_SWING_REG_CNT]; u32 low_power_mode; struct device_node *mipi_host_node; }; diff --git a/drivers/gpu/drm/bridge/chipone-icn6211.c b/drivers/gpu/drm/bridge/chipone-icn6211.c index b72aa3f3b5c2..47dea657a752 100644 --- a/drivers/gpu/drm/bridge/chipone-icn6211.c +++ b/drivers/gpu/drm/bridge/chipone-icn6211.c @@ -14,6 +14,7 @@ #include <linux/i2c.h> #include <linux/module.h> #include <linux/of_device.h> +#include <linux/regmap.h> #include <linux/regulator/consumer.h> #define VENDOR_ID 0x00 @@ -134,6 +135,7 @@ struct chipone { struct device *dev; + struct regmap *regmap; struct i2c_client *client; struct drm_bridge bridge; struct drm_display_mode mode; @@ -146,6 +148,77 @@ struct chipone { bool interface_i2c; }; +static const struct regmap_range chipone_dsi_readable_ranges[] = { + regmap_reg_range(VENDOR_ID, VERSION_ID), + regmap_reg_range(FIRMWARE_VERSION, PLL_SSC_OFFSET(3)), + regmap_reg_range(GPIO_OEN, MIPI_ULPS_CTRL), + regmap_reg_range(MIPI_CLK_CHK_VAR, MIPI_T_TA_SURE_PRE), + regmap_reg_range(MIPI_T_LPX_SET, MIPI_INIT_TIME_H), + regmap_reg_range(MIPI_T_CLK_TERM_EN, MIPI_T_CLK_SETTLE), + regmap_reg_range(MIPI_TO_HS_RX_L, MIPI_PHY_(5)), + regmap_reg_range(MIPI_PD_RX, MIPI_RST_NUM), + regmap_reg_range(MIPI_DBG_SET_(0), MIPI_DBG_SET_(9)), + regmap_reg_range(MIPI_DBG_SEL, MIPI_ATE_STATUS_(1)), +}; + +static const struct regmap_access_table chipone_dsi_readable_table = { + .yes_ranges = chipone_dsi_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(chipone_dsi_readable_ranges), +}; + +static const struct regmap_range chipone_dsi_writeable_ranges[] = { + regmap_reg_range(CONFIG_FINISH, PLL_SSC_OFFSET(3)), + regmap_reg_range(GPIO_OEN, MIPI_ULPS_CTRL), + regmap_reg_range(MIPI_CLK_CHK_VAR, MIPI_T_TA_SURE_PRE), + regmap_reg_range(MIPI_T_LPX_SET, MIPI_INIT_TIME_H), + regmap_reg_range(MIPI_T_CLK_TERM_EN, MIPI_T_CLK_SETTLE), + regmap_reg_range(MIPI_TO_HS_RX_L, MIPI_PHY_(5)), + regmap_reg_range(MIPI_PD_RX, MIPI_RST_NUM), + regmap_reg_range(MIPI_DBG_SET_(0), MIPI_DBG_SET_(9)), + regmap_reg_range(MIPI_DBG_SEL, MIPI_ATE_STATUS_(1)), +}; + +static const struct regmap_access_table chipone_dsi_writeable_table = { + .yes_ranges = chipone_dsi_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(chipone_dsi_writeable_ranges), +}; + +static const struct regmap_config chipone_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = &chipone_dsi_readable_table, + .wr_table = &chipone_dsi_writeable_table, + .cache_type = REGCACHE_RBTREE, + .max_register = MIPI_ATE_STATUS_(1), +}; + +static int chipone_dsi_read(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct mipi_dsi_device *dsi = context; + const u16 reg16 = (val_size << 8) | *(u8 *)reg; + int ret; + + ret = mipi_dsi_generic_read(dsi, ®16, 2, val, val_size); + + return ret == val_size ? 0 : -EINVAL; +} + +static int chipone_dsi_write(void *context, const void *data, size_t count) +{ + struct mipi_dsi_device *dsi = context; + + return mipi_dsi_generic_write(dsi, data, 2); +} + +static const struct regmap_bus chipone_dsi_regmap_bus = { + .read = chipone_dsi_read, + .write = chipone_dsi_write, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, + .val_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + static inline struct chipone *bridge_to_chipone(struct drm_bridge *bridge) { return container_of(bridge, struct chipone, bridge); @@ -153,18 +226,16 @@ static inline struct chipone *bridge_to_chipone(struct drm_bridge *bridge) static void chipone_readb(struct chipone *icn, u8 reg, u8 *val) { - if (icn->interface_i2c) - *val = i2c_smbus_read_byte_data(icn->client, reg); - else - mipi_dsi_generic_read(icn->dsi, (u8[]){reg, 1}, 2, val, 1); + int ret, pval; + + ret = regmap_read(icn->regmap, reg, &pval); + + *val = ret ? 0 : pval & 0xff; } static int chipone_writeb(struct chipone *icn, u8 reg, u8 val) { - if (icn->interface_i2c) - return i2c_smbus_write_byte_data(icn->client, reg, val); - else - return mipi_dsi_generic_write(icn->dsi, (u8[]){reg, val}, 2); + return regmap_write(icn->regmap, reg, val); } static void chipone_configure_pll(struct chipone *icn, @@ -324,6 +395,11 @@ static void chipone_atomic_enable(struct drm_bridge *bridge, /* dsi specific sequence */ chipone_writeb(icn, SYNC_EVENT_DLY, 0x80); chipone_writeb(icn, HFP_MIN, hfp & 0xff); + + /* DSI data lane count */ + chipone_writeb(icn, DSI_CTRL, + DSI_CTRL_UNKNOWN | DSI_CTRL_DSI_LANES(icn->dsi->lanes - 1)); + chipone_writeb(icn, MIPI_PD_CK_LANE, 0xa0); chipone_writeb(icn, PLL_CTRL(12), 0xff); chipone_writeb(icn, MIPI_PN_SWAP, 0x00); @@ -409,9 +485,23 @@ static void chipone_mode_set(struct drm_bridge *bridge, static int chipone_dsi_attach(struct chipone *icn) { struct mipi_dsi_device *dsi = icn->dsi; - int ret; + struct device *dev = icn->dev; + struct device_node *endpoint; + int dsi_lanes, ret; + + endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0); + dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes"); + of_node_put(endpoint); + + /* + * If the 'data-lanes' property does not exist in DT or is invalid, + * default to previously hard-coded behavior, which was 4 data lanes. + */ + if (dsi_lanes >= 1 && dsi_lanes <= 4) + icn->dsi->lanes = dsi_lanes; + else + icn->dsi->lanes = 4; - dsi->lanes = 4; dsi->format = MIPI_DSI_FMT_RGB888; dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET; @@ -591,6 +681,11 @@ static int chipone_dsi_probe(struct mipi_dsi_device *dsi) if (ret) return ret; + icn->regmap = devm_regmap_init(dev, &chipone_dsi_regmap_bus, + dsi, &chipone_regmap_config); + if (IS_ERR(icn->regmap)) + return PTR_ERR(icn->regmap); + icn->interface_i2c = false; icn->dsi = dsi; @@ -616,6 +711,10 @@ static int chipone_i2c_probe(struct i2c_client *client, if (ret) return ret; + icn->regmap = devm_regmap_init_i2c(client, &chipone_regmap_config); + if (IS_ERR(icn->regmap)) + return PTR_ERR(icn->regmap); + icn->interface_i2c = true; icn->client = client; dev_set_drvdata(dev, icn); diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c index d24f5b90feab..e4d52a7e31b7 100644 --- a/drivers/gpu/drm/bridge/display-connector.c +++ b/drivers/gpu/drm/bridge/display-connector.c @@ -24,6 +24,7 @@ struct display_connector { int hpd_irq; struct regulator *dp_pwr; + struct gpio_desc *ddc_en; }; static inline struct display_connector * @@ -345,6 +346,17 @@ static int display_connector_probe(struct platform_device *pdev) } } + /* enable DDC */ + if (type == DRM_MODE_CONNECTOR_HDMIA) { + conn->ddc_en = devm_gpiod_get_optional(&pdev->dev, "ddc-en", + GPIOD_OUT_HIGH); + + if (IS_ERR(conn->ddc_en)) { + dev_err(&pdev->dev, "Couldn't get ddc-en gpio\n"); + return PTR_ERR(conn->ddc_en); + } + } + conn->bridge.funcs = &display_connector_bridge_funcs; conn->bridge.of_node = pdev->dev.of_node; @@ -373,6 +385,9 @@ static int display_connector_remove(struct platform_device *pdev) { struct display_connector *conn = platform_get_drvdata(pdev); + if (conn->ddc_en) + gpiod_set_value(conn->ddc_en, 0); + if (conn->dp_pwr) regulator_disable(conn->dp_pwr); diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c new file mode 100644 index 000000000000..e92821fbc639 --- /dev/null +++ b/drivers/gpu/drm/bridge/lontium-lt9211.c @@ -0,0 +1,802 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Lontium LT9211 bridge driver + * + * LT9211 is capable of converting: + * 2xDSI/2xLVDS/1xDPI -> 2xDSI/2xLVDS/1xDPI + * Currently supported is: + * 1xDSI -> 1xLVDS + * + * Copyright (C) 2022 Marek Vasut <marex@denx.de> + */ + +#include <linux/bits.h> +#include <linux/clk.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#define REG_PAGE_CONTROL 0xff +#define REG_CHIPID0 0x8100 +#define REG_CHIPID0_VALUE 0x18 +#define REG_CHIPID1 0x8101 +#define REG_CHIPID1_VALUE 0x01 +#define REG_CHIPID2 0x8102 +#define REG_CHIPID2_VALUE 0xe3 + +#define REG_DSI_LANE 0xd000 +/* DSI lane count - 0 means 4 lanes ; 1, 2, 3 means 1, 2, 3 lanes. */ +#define REG_DSI_LANE_COUNT(n) ((n) & 3) + +struct lt9211 { + struct drm_bridge bridge; + struct device *dev; + struct regmap *regmap; + struct mipi_dsi_device *dsi; + struct drm_bridge *panel_bridge; + struct gpio_desc *reset_gpio; + struct regulator *vccio; + bool lvds_dual_link; + bool lvds_dual_link_even_odd_swap; +}; + +static const struct regmap_range lt9211_rw_ranges[] = { + regmap_reg_range(0xff, 0xff), + regmap_reg_range(0x8100, 0x816b), + regmap_reg_range(0x8200, 0x82aa), + regmap_reg_range(0x8500, 0x85ff), + regmap_reg_range(0x8600, 0x86a0), + regmap_reg_range(0x8700, 0x8746), + regmap_reg_range(0xd000, 0xd0a7), + regmap_reg_range(0xd400, 0xd42c), + regmap_reg_range(0xd800, 0xd838), + regmap_reg_range(0xd9c0, 0xd9d5), +}; + +static const struct regmap_access_table lt9211_rw_table = { + .yes_ranges = lt9211_rw_ranges, + .n_yes_ranges = ARRAY_SIZE(lt9211_rw_ranges), +}; + +static const struct regmap_range_cfg lt9211_range = { + .name = "lt9211", + .range_min = 0x0000, + .range_max = 0xda00, + .selector_reg = REG_PAGE_CONTROL, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 0x100, +}; + +static const struct regmap_config lt9211_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = <9211_rw_table, + .wr_table = <9211_rw_table, + .volatile_table = <9211_rw_table, + .ranges = <9211_range, + .num_ranges = 1, + .cache_type = REGCACHE_RBTREE, + .max_register = 0xda00, +}; + +static struct lt9211 *bridge_to_lt9211(struct drm_bridge *bridge) +{ + return container_of(bridge, struct lt9211, bridge); +} + +static int lt9211_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct lt9211 *ctx = bridge_to_lt9211(bridge); + + return drm_bridge_attach(bridge->encoder, ctx->panel_bridge, + &ctx->bridge, flags); +} + +static int lt9211_read_chipid(struct lt9211 *ctx) +{ + u8 chipid[3]; + int ret; + + /* Read Chip ID registers and verify the chip can communicate. */ + ret = regmap_bulk_read(ctx->regmap, REG_CHIPID0, chipid, 3); + if (ret < 0) { + dev_err(ctx->dev, "Failed to read Chip ID: %d\n", ret); + return ret; + } + + /* Test for known Chip ID. */ + if (chipid[0] != REG_CHIPID0_VALUE || chipid[1] != REG_CHIPID1_VALUE || + chipid[2] != REG_CHIPID2_VALUE) { + dev_err(ctx->dev, "Unknown Chip ID: 0x%02x 0x%02x 0x%02x\n", + chipid[0], chipid[1], chipid[2]); + return -EINVAL; + } + + return 0; +} + +static int lt9211_system_init(struct lt9211 *ctx) +{ + const struct reg_sequence lt9211_system_init_seq[] = { + { 0x8201, 0x18 }, + { 0x8606, 0x61 }, + { 0x8607, 0xa8 }, + { 0x8714, 0x08 }, + { 0x8715, 0x00 }, + { 0x8718, 0x0f }, + { 0x8722, 0x08 }, + { 0x8723, 0x00 }, + { 0x8726, 0x0f }, + { 0x810b, 0xfe }, + }; + + return regmap_multi_reg_write(ctx->regmap, lt9211_system_init_seq, + ARRAY_SIZE(lt9211_system_init_seq)); +} + +static int lt9211_configure_rx(struct lt9211 *ctx) +{ + const struct reg_sequence lt9211_rx_phy_seq[] = { + { 0x8202, 0x44 }, + { 0x8204, 0xa0 }, + { 0x8205, 0x22 }, + { 0x8207, 0x9f }, + { 0x8208, 0xfc }, + /* ORR with 0xf8 here to enable DSI DN/DP swap. */ + { 0x8209, 0x01 }, + { 0x8217, 0x0c }, + { 0x8633, 0x1b }, + }; + + const struct reg_sequence lt9211_rx_cal_reset_seq[] = { + { 0x8120, 0x7f }, + { 0x8120, 0xff }, + }; + + const struct reg_sequence lt9211_rx_dig_seq[] = { + { 0x8630, 0x85 }, + /* 0x8588: BIT 6 set = MIPI-RX, BIT 4 unset = LVDS-TX */ + { 0x8588, 0x40 }, + { 0x85ff, 0xd0 }, + { REG_DSI_LANE, REG_DSI_LANE_COUNT(ctx->dsi->lanes) }, + { 0xd002, 0x05 }, + }; + + const struct reg_sequence lt9211_rx_div_reset_seq[] = { + { 0x810a, 0xc0 }, + { 0x8120, 0xbf }, + }; + + const struct reg_sequence lt9211_rx_div_clear_seq[] = { + { 0x810a, 0xc1 }, + { 0x8120, 0xff }, + }; + + int ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_phy_seq, + ARRAY_SIZE(lt9211_rx_phy_seq)); + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_cal_reset_seq, + ARRAY_SIZE(lt9211_rx_cal_reset_seq)); + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_dig_seq, + ARRAY_SIZE(lt9211_rx_dig_seq)); + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_div_reset_seq, + ARRAY_SIZE(lt9211_rx_div_reset_seq)); + if (ret) + return ret; + + usleep_range(10000, 15000); + + return regmap_multi_reg_write(ctx->regmap, lt9211_rx_div_clear_seq, + ARRAY_SIZE(lt9211_rx_div_clear_seq)); +} + +static int lt9211_autodetect_rx(struct lt9211 *ctx, + const struct drm_display_mode *mode) +{ + u16 width, height; + u32 byteclk; + u8 buf[5]; + u8 format; + u8 bc[3]; + int ret; + + /* Measure ByteClock frequency. */ + ret = regmap_write(ctx->regmap, 0x8600, 0x01); + if (ret) + return ret; + + /* Give the chip time to lock onto RX stream. */ + msleep(100); + + /* Read the ByteClock frequency from the chip. */ + ret = regmap_bulk_read(ctx->regmap, 0x8608, bc, sizeof(bc)); + if (ret) + return ret; + + /* RX ByteClock in kHz */ + byteclk = ((bc[0] & 0xf) << 16) | (bc[1] << 8) | bc[2]; + + /* Width/Height/Format Auto-detection */ + ret = regmap_bulk_read(ctx->regmap, 0xd082, buf, sizeof(buf)); + if (ret) + return ret; + + width = (buf[0] << 8) | buf[1]; + height = (buf[3] << 8) | buf[4]; + format = buf[2] & 0xf; + + if (format == 0x3) { /* YUV422 16bit */ + width /= 2; + } else if (format == 0xa) { /* RGB888 24bit */ + width /= 3; + } else { + dev_err(ctx->dev, "Unsupported DSI pixel format 0x%01x\n", + format); + return -EINVAL; + } + + if (width != mode->hdisplay) { + dev_err(ctx->dev, + "RX: Detected DSI width (%d) does not match mode hdisplay (%d)\n", + width, mode->hdisplay); + return -EINVAL; + } + + if (height != mode->vdisplay) { + dev_err(ctx->dev, + "RX: Detected DSI height (%d) does not match mode vdisplay (%d)\n", + height, mode->vdisplay); + return -EINVAL; + } + + dev_dbg(ctx->dev, "RX: %dx%d format=0x%01x byteclock=%d kHz\n", + width, height, format, byteclk); + + return 0; +} + +static int lt9211_configure_timing(struct lt9211 *ctx, + const struct drm_display_mode *mode) +{ + const struct reg_sequence lt9211_timing[] = { + { 0xd00d, (mode->vtotal >> 8) & 0xff }, + { 0xd00e, mode->vtotal & 0xff }, + { 0xd00f, (mode->vdisplay >> 8) & 0xff }, + { 0xd010, mode->vdisplay & 0xff }, + { 0xd011, (mode->htotal >> 8) & 0xff }, + { 0xd012, mode->htotal & 0xff }, + { 0xd013, (mode->hdisplay >> 8) & 0xff }, + { 0xd014, mode->hdisplay & 0xff }, + { 0xd015, (mode->vsync_end - mode->vsync_start) & 0xff }, + { 0xd016, (mode->hsync_end - mode->hsync_start) & 0xff }, + { 0xd017, ((mode->vsync_start - mode->vdisplay) >> 8) & 0xff }, + { 0xd018, (mode->vsync_start - mode->vdisplay) & 0xff }, + { 0xd019, ((mode->hsync_start - mode->hdisplay) >> 8) & 0xff }, + { 0xd01a, (mode->hsync_start - mode->hdisplay) & 0xff }, + }; + + return regmap_multi_reg_write(ctx->regmap, lt9211_timing, + ARRAY_SIZE(lt9211_timing)); +} + +static int lt9211_configure_plls(struct lt9211 *ctx, + const struct drm_display_mode *mode) +{ + const struct reg_sequence lt9211_pcr_seq[] = { + { 0xd026, 0x17 }, + { 0xd027, 0xc3 }, + { 0xd02d, 0x30 }, + { 0xd031, 0x10 }, + { 0xd023, 0x20 }, + { 0xd038, 0x02 }, + { 0xd039, 0x10 }, + { 0xd03a, 0x20 }, + { 0xd03b, 0x60 }, + { 0xd03f, 0x04 }, + { 0xd040, 0x08 }, + { 0xd041, 0x10 }, + { 0x810b, 0xee }, + { 0x810b, 0xfe }, + }; + + unsigned int pval; + int ret; + + /* DeSSC PLL reference clock is 25 MHz XTal. */ + ret = regmap_write(ctx->regmap, 0x822d, 0x48); + if (ret) + return ret; + + if (mode->clock < 44000) { + ret = regmap_write(ctx->regmap, 0x8235, 0x83); + } else if (mode->clock < 88000) { + ret = regmap_write(ctx->regmap, 0x8235, 0x82); + } else if (mode->clock < 176000) { + ret = regmap_write(ctx->regmap, 0x8235, 0x81); + } else { + dev_err(ctx->dev, + "Unsupported mode clock (%d kHz) above 176 MHz.\n", + mode->clock); + return -EINVAL; + } + + if (ret) + return ret; + + /* Wait for the DeSSC PLL to stabilize. */ + msleep(100); + + ret = regmap_multi_reg_write(ctx->regmap, lt9211_pcr_seq, + ARRAY_SIZE(lt9211_pcr_seq)); + if (ret) + return ret; + + /* PCR stability test takes seconds. */ + ret = regmap_read_poll_timeout(ctx->regmap, 0xd087, pval, pval & 0x8, + 20000, 10000000); + if (ret) + dev_err(ctx->dev, "PCR unstable, ret=%i\n", ret); + + return ret; +} + +static int lt9211_configure_tx(struct lt9211 *ctx, bool jeida, + bool bpp24, bool de) +{ + const struct reg_sequence system_lt9211_tx_phy_seq[] = { + /* DPI output disable */ + { 0x8262, 0x00 }, + /* BIT(7) is LVDS dual-port */ + { 0x823b, 0x38 | (ctx->lvds_dual_link ? BIT(7) : 0) }, + { 0x823e, 0x92 }, + { 0x823f, 0x48 }, + { 0x8240, 0x31 }, + { 0x8243, 0x80 }, + { 0x8244, 0x00 }, + { 0x8245, 0x00 }, + { 0x8249, 0x00 }, + { 0x824a, 0x01 }, + { 0x824e, 0x00 }, + { 0x824f, 0x00 }, + { 0x8250, 0x00 }, + { 0x8253, 0x00 }, + { 0x8254, 0x01 }, + /* LVDS channel order, Odd:Even 0x10..A:B, 0x40..B:A */ + { 0x8646, ctx->lvds_dual_link_even_odd_swap ? 0x40 : 0x10 }, + { 0x8120, 0x7b }, + { 0x816b, 0xff }, + }; + + const struct reg_sequence system_lt9211_tx_dig_seq[] = { + { 0x8559, 0x40 | (jeida ? BIT(7) : 0) | + (de ? BIT(5) : 0) | (bpp24 ? BIT(4) : 0) }, + { 0x855a, 0xaa }, + { 0x855b, 0xaa }, + { 0x855c, ctx->lvds_dual_link ? BIT(0) : 0 }, + { 0x85a1, 0x77 }, + { 0x8640, 0x40 }, + { 0x8641, 0x34 }, + { 0x8642, 0x10 }, + { 0x8643, 0x23 }, + { 0x8644, 0x41 }, + { 0x8645, 0x02 }, + }; + + const struct reg_sequence system_lt9211_tx_pll_seq[] = { + /* TX PLL power down */ + { 0x8236, 0x01 }, + { 0x8237, ctx->lvds_dual_link ? 0x2a : 0x29 }, + { 0x8238, 0x06 }, + { 0x8239, 0x30 }, + { 0x823a, 0x8e }, + { 0x8737, 0x14 }, + { 0x8713, 0x00 }, + { 0x8713, 0x80 }, + }; + + unsigned int pval; + int ret; + + ret = regmap_multi_reg_write(ctx->regmap, system_lt9211_tx_phy_seq, + ARRAY_SIZE(system_lt9211_tx_phy_seq)); + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, system_lt9211_tx_dig_seq, + ARRAY_SIZE(system_lt9211_tx_dig_seq)); + if (ret) + return ret; + + ret = regmap_multi_reg_write(ctx->regmap, system_lt9211_tx_pll_seq, + ARRAY_SIZE(system_lt9211_tx_pll_seq)); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(ctx->regmap, 0x871f, pval, pval & 0x80, + 10000, 1000000); + if (ret) { + dev_err(ctx->dev, "TX PLL unstable, ret=%i\n", ret); + return ret; + } + + ret = regmap_read_poll_timeout(ctx->regmap, 0x8720, pval, pval & 0x80, + 10000, 1000000); + if (ret) { + dev_err(ctx->dev, "TX PLL unstable, ret=%i\n", ret); + return ret; + } + + return 0; +} + +static void lt9211_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct lt9211 *ctx = bridge_to_lt9211(bridge); + struct drm_atomic_state *state = old_bridge_state->base.state; + const struct drm_bridge_state *bridge_state; + const struct drm_crtc_state *crtc_state; + const struct drm_display_mode *mode; + struct drm_connector *connector; + struct drm_crtc *crtc; + bool lvds_format_24bpp; + bool lvds_format_jeida; + u32 bus_flags; + int ret; + + ret = regulator_enable(ctx->vccio); + if (ret) { + dev_err(ctx->dev, "Failed to enable vccio: %d\n", ret); + return; + } + + /* Deassert reset */ + gpiod_set_value(ctx->reset_gpio, 1); + usleep_range(20000, 21000); /* Very long post-reset delay. */ + + /* Get the LVDS format from the bridge state. */ + bridge_state = drm_atomic_get_new_bridge_state(state, bridge); + bus_flags = bridge_state->output_bus_cfg.flags; + + switch (bridge_state->output_bus_cfg.format) { + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + lvds_format_24bpp = false; + lvds_format_jeida = true; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + lvds_format_24bpp = true; + lvds_format_jeida = true; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + lvds_format_24bpp = true; + lvds_format_jeida = false; + break; + default: + /* + * Some bridges still don't set the correct + * LVDS bus pixel format, use SPWG24 default + * format until those are fixed. + */ + lvds_format_24bpp = true; + lvds_format_jeida = false; + dev_warn(ctx->dev, + "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n", + bridge_state->output_bus_cfg.format); + break; + } + + /* + * Retrieve the CRTC adjusted mode. This requires a little dance to go + * from the bridge to the encoder, to the connector and to the CRTC. + */ + connector = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + crtc = drm_atomic_get_new_connector_state(state, connector)->crtc; + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + mode = &crtc_state->adjusted_mode; + + ret = lt9211_read_chipid(ctx); + if (ret) + return; + + ret = lt9211_system_init(ctx); + if (ret) + return; + + ret = lt9211_configure_rx(ctx); + if (ret) + return; + + ret = lt9211_autodetect_rx(ctx, mode); + if (ret) + return; + + ret = lt9211_configure_timing(ctx, mode); + if (ret) + return; + + ret = lt9211_configure_plls(ctx, mode); + if (ret) + return; + + ret = lt9211_configure_tx(ctx, lvds_format_jeida, lvds_format_24bpp, + bus_flags & DRM_BUS_FLAG_DE_HIGH); + if (ret) + return; + + dev_dbg(ctx->dev, "LT9211 enabled.\n"); +} + +static void lt9211_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct lt9211 *ctx = bridge_to_lt9211(bridge); + int ret; + + /* + * Put the chip in reset, pull nRST line low, + * and assure lengthy 10ms reset low timing. + */ + gpiod_set_value(ctx->reset_gpio, 0); + usleep_range(10000, 11000); /* Very long reset duration. */ + + ret = regulator_disable(ctx->vccio); + if (ret) + dev_err(ctx->dev, "Failed to disable vccio: %d\n", ret); + + regcache_mark_dirty(ctx->regmap); +} + +static enum drm_mode_status +lt9211_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + /* LVDS output clock range 25..176 MHz */ + if (mode->clock < 25000) + return MODE_CLOCK_LOW; + if (mode->clock > 176000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +#define MAX_INPUT_SEL_FORMATS 1 + +static u32 * +lt9211_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; + + *num_input_fmts = 0; + + input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts), + GFP_KERNEL); + if (!input_fmts) + return NULL; + + /* This is the DSI-end bus format */ + input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; + *num_input_fmts = 1; + + return input_fmts; +} + +static const struct drm_bridge_funcs lt9211_funcs = { + .attach = lt9211_attach, + .mode_valid = lt9211_mode_valid, + .atomic_enable = lt9211_atomic_enable, + .atomic_disable = lt9211_atomic_disable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_get_input_bus_fmts = lt9211_atomic_get_input_bus_fmts, + .atomic_reset = drm_atomic_helper_bridge_reset, +}; + +static int lt9211_parse_dt(struct lt9211 *ctx) +{ + struct device_node *port2, *port3; + struct drm_bridge *panel_bridge; + struct device *dev = ctx->dev; + struct drm_panel *panel; + int dual_link; + int ret; + + ctx->vccio = devm_regulator_get(dev, "vccio"); + if (IS_ERR(ctx->vccio)) + return dev_err_probe(dev, PTR_ERR(ctx->vccio), + "Failed to get supply 'vccio'\n"); + + ctx->lvds_dual_link = false; + ctx->lvds_dual_link_even_odd_swap = false; + + port2 = of_graph_get_port_by_id(dev->of_node, 2); + port3 = of_graph_get_port_by_id(dev->of_node, 3); + dual_link = drm_of_lvds_get_dual_link_pixel_order(port2, port3); + of_node_put(port2); + of_node_put(port3); + + if (dual_link == DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS) { + ctx->lvds_dual_link = true; + /* Odd pixels to LVDS Channel A, even pixels to B */ + ctx->lvds_dual_link_even_odd_swap = false; + } else if (dual_link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) { + ctx->lvds_dual_link = true; + /* Even pixels to LVDS Channel A, odd pixels to B */ + ctx->lvds_dual_link_even_odd_swap = true; + } + + ret = drm_of_find_panel_or_bridge(dev->of_node, 2, 0, &panel, &panel_bridge); + if (ret < 0) + return ret; + if (panel) { + panel_bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(panel_bridge)) + return PTR_ERR(panel_bridge); + } + + ctx->panel_bridge = panel_bridge; + + return 0; +} + +static int lt9211_host_attach(struct lt9211 *ctx) +{ + const struct mipi_dsi_device_info info = { + .type = "lt9211", + .channel = 0, + .node = NULL, + }; + struct device *dev = ctx->dev; + struct device_node *host_node; + struct device_node *endpoint; + struct mipi_dsi_device *dsi; + struct mipi_dsi_host *host; + int dsi_lanes; + int ret; + + endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1); + dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes"); + host_node = of_graph_get_remote_port_parent(endpoint); + host = of_find_mipi_dsi_host_by_node(host_node); + of_node_put(host_node); + of_node_put(endpoint); + + if (!host) + return -EPROBE_DEFER; + + if (dsi_lanes < 0 || dsi_lanes > 4) + return -EINVAL; + + dsi = devm_mipi_dsi_device_register_full(dev, host, &info); + if (IS_ERR(dsi)) + return dev_err_probe(dev, PTR_ERR(dsi), + "failed to create dsi device\n"); + + ctx->dsi = dsi; + + dsi->lanes = dsi_lanes; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_VIDEO_HSE; + + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) { + dev_err(dev, "failed to attach dsi to host: %d\n", ret); + return ret; + } + + return 0; +} + +static int lt9211_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct lt9211 *ctx; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->dev = dev; + + /* + * Put the chip in reset, pull nRST line low, + * and assure lengthy 10ms reset low timing. + */ + ctx->reset_gpio = devm_gpiod_get_optional(ctx->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset_gpio)) + return PTR_ERR(ctx->reset_gpio); + + usleep_range(10000, 11000); /* Very long reset duration. */ + + ret = lt9211_parse_dt(ctx); + if (ret) + return ret; + + ctx->regmap = devm_regmap_init_i2c(client, <9211_regmap_config); + if (IS_ERR(ctx->regmap)) + return PTR_ERR(ctx->regmap); + + dev_set_drvdata(dev, ctx); + i2c_set_clientdata(client, ctx); + + ctx->bridge.funcs = <9211_funcs; + ctx->bridge.of_node = dev->of_node; + drm_bridge_add(&ctx->bridge); + + ret = lt9211_host_attach(ctx); + if (ret) + drm_bridge_remove(&ctx->bridge); + + return ret; +} + +static int lt9211_remove(struct i2c_client *client) +{ + struct lt9211 *ctx = i2c_get_clientdata(client); + + drm_bridge_remove(&ctx->bridge); + + return 0; +} + +static struct i2c_device_id lt9211_id[] = { + { "lontium,lt9211" }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, lt9211_id); + +static const struct of_device_id lt9211_match_table[] = { + { .compatible = "lontium,lt9211" }, + {}, +}; +MODULE_DEVICE_TABLE(of, lt9211_match_table); + +static struct i2c_driver lt9211_driver = { + .probe = lt9211_probe, + .remove = lt9211_remove, + .id_table = lt9211_id, + .driver = { + .name = "lt9211", + .of_match_table = lt9211_match_table, + }, +}; +module_i2c_driver(lt9211_driver); + +MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); +MODULE_DESCRIPTION("Lontium LT9211 DSI/LVDS/DPI bridge driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c index ff1c37b2e6e5..0ee563eb2b6f 100644 --- a/drivers/gpu/drm/bridge/panel.c +++ b/drivers/gpu/drm/bridge/panel.c @@ -83,8 +83,11 @@ static int panel_bridge_attach(struct drm_bridge *bridge, drm_connector_attach_encoder(&panel_bridge->connector, bridge->encoder); - if (connector->funcs->reset) - connector->funcs->reset(connector); + if (bridge->dev->registered) { + if (connector->funcs->reset) + connector->funcs->reset(connector); + drm_connector_register(connector); + } return 0; } diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig index 21a1be3ced0f..a4a31b669b65 100644 --- a/drivers/gpu/drm/bridge/synopsys/Kconfig +++ b/drivers/gpu/drm/bridge/synopsys/Kconfig @@ -25,6 +25,16 @@ 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. + 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-gp-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c new file mode 100644 index 000000000000..11ea1c84eb35 --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * dw-hdmi-gp-audio.c + * + * Copyright 2020-2022 NXP + */ +#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) +{ + struct dw_hdmi_audio_data *data = pdev->dev.platform_data; + struct snd_dw_hdmi *dw; + + const struct hdmi_codec_pdata codec_data = { + .i2s = 1, + .spdif = 0, + .ops = &audio_codec_ops, + .max_i2s_channels = 8, + .data = 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("Synopsys Designware HDMI GPA ALSA interface"); +MODULE_LICENSE("GPL"); +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 a563460f8d20..f3f82a04c252 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; @@ -659,8 +668,8 @@ 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) { + /* Compute CTS when using internal AHB audio or General Parallel audio*/ + 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 @@ -765,6 +791,89 @@ static u8 *hdmi_audio_get_eld(struct dw_hdmi *hdmi) return hdmi->curr_conn->eld; } +static void dw_hdmi_gp_audio_enable(struct dw_hdmi *hdmi) +{ + const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; + 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 (pdata->enable_audio) + pdata->enable_audio(hdmi, + hdmi->channels, + hdmi->sample_width, + hdmi->sample_rate, + hdmi->sample_non_pcm); +} + +static void dw_hdmi_gp_audio_disable(struct dw_hdmi *hdmi) +{ + const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; + + hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0); + + hdmi_modb(hdmi, 0, 0x3, HDMI_FC_DATAUTO3); + if (pdata->disable_audio) + pdata->disable_audio(hdmi); + + hdmi_enable_audio_clk(hdmi, false); +} + static void dw_hdmi_ahb_audio_enable(struct dw_hdmi *hdmi) { hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); @@ -1108,6 +1217,8 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi) unsigned int output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_PP; struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data; u8 val, vp_conf; + u8 clear_gcp_auto = 0; + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format) || hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format) || @@ -1117,6 +1228,7 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi) case 8: color_depth = 4; output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS; + clear_gcp_auto = 1; break; case 10: color_depth = 5; @@ -1136,6 +1248,7 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi) case 0: case 8: remap_size = HDMI_VP_REMAP_YCC422_16bit; + clear_gcp_auto = 1; break; case 10: remap_size = HDMI_VP_REMAP_YCC422_20bit; @@ -1160,6 +1273,19 @@ 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); + /* HDMI1.4b specification section 6.5.3: + * Source shall only send GCPs with non-zero CD to sinks + * that indicate support for Deep Color. + * GCP only transmit CD and do not handle AVMUTE, PP norDefault_Phase (yet). + * Disable Auto GCP when 24-bit color for sinks that not support Deep Color. + */ + val = hdmi_readb(hdmi, HDMI_FC_DATAUTO3); + if (clear_gcp_auto == 1) + val &= ~HDMI_FC_DATAUTO3_GCP_AUTO; + else + val |= HDMI_FC_DATAUTO3_GCP_AUTO; + 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); @@ -1357,13 +1483,21 @@ static void dw_hdmi_phy_sel_interface_control(struct dw_hdmi *hdmi, u8 enable) HDMI_PHY_CONF0_SELDIPIF_MASK); } -void dw_hdmi_phy_reset(struct dw_hdmi *hdmi) +void dw_hdmi_phy_gen1_reset(struct dw_hdmi *hdmi) +{ + /* PHY reset. The reset signal is active low 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_gen1_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 +1651,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); @@ -2086,30 +2220,21 @@ static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi) * then write one of the FC registers several times. * * 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. - * 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 - * been identified as needing the workaround with a single iteration. + * (and possibly on the platform). + * 4 iterations for i.MX6Q(v1.30a) and 1 iteration for others. + * i.MX6DL (v1.31a), Allwinner SoCs (v1.32a), Rockchip RK3288 SoC (v2.00a), + * Amlogic Meson GX SoCs (v2.01a), RK3328/RK3399 SoCs (v2.11a) + * and i.MX8MPlus (v2.13a) have been identified as needing the workaround + * with a single iteration. */ switch (hdmi->version) { case 0x130a: count = 4; break; - case 0x131a: - case 0x132a: - case 0x200a: - case 0x201a: - case 0x211a: - case 0x212a: + default: count = 1; break; - default: - return; } /* TMDS software reset */ @@ -3242,6 +3367,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); @@ -3465,6 +3591,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..af43a0414b78 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 @@ -850,6 +859,9 @@ enum { HDMI_FC_DATAUTO0_VSD_MASK = 0x08, HDMI_FC_DATAUTO0_VSD_OFFSET = 3, +/* FC_DATAUTO3 field values */ + HDMI_FC_DATAUTO3_GCP_AUTO = 0x04, + /* PHY_CONF0 field values */ HDMI_PHY_CONF0_PDZ_MASK = 0x80, HDMI_PHY_CONF0_PDZ_OFFSET = 7, |