diff options
Diffstat (limited to 'drivers/gpu/drm/bridge')
-rw-r--r-- | drivers/gpu/drm/bridge/Kconfig | 22 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/Makefile | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/adv7511/Kconfig | 13 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/adv7511/Makefile | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/adv7511/adv7511.h | 40 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 20 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 44 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/panel.c | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/parade-ps8640.c | 348 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/tc358768.c | 1044 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/ti-sn65dsi86.c | 259 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/ti-tfp410.c | 13 |
13 files changed, 1689 insertions, 124 deletions
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 0b9ca5862455..20a439199cb8 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -72,6 +72,17 @@ config DRM_PARADE_PS8622 ---help--- Parade eDP-LVDS bridge chip driver. +config DRM_PARADE_PS8640 + tristate "Parade PS8640 MIPI DSI to eDP Converter" + depends on OF + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL + help + Choose this option if you have PS8640 for display + The PS8640 is a high-performance and low-power + MIPI DSI to eDP converter + config DRM_SIL_SII8620 tristate "Silicon Image SII8620 HDMI/MHL bridge" depends on OF @@ -87,6 +98,7 @@ config DRM_SII902X select DRM_KMS_HELPER select REGMAP_I2C select I2C_MUX + select SND_SOC_HDMI_CODEC if SND_SOC ---help--- Silicon Image sii902x bridge chip driver. @@ -122,6 +134,16 @@ config DRM_TOSHIBA_TC358767 ---help--- Toshiba TC358767 eDP bridge chip driver. +config DRM_TOSHIBA_TC358768 + tristate "Toshiba TC358768 MIPI DSI bridge" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + select DRM_PANEL + select DRM_MIPI_DSI + help + Toshiba TC358768AXBG/TC358778XBG DSI bridge chip driver. + config DRM_TI_TFP410 tristate "TI TFP410 DVI/HDMI bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index cd16ce830270..b0d5c3af0b5a 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -5,12 +5,14 @@ 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 obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o +obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o obj-$(CONFIG_DRM_SII902X) += sii902x.o obj-$(CONFIG_DRM_SII9234) += sii9234.o obj-$(CONFIG_DRM_THINE_THC63LVD1024) += thc63lvd1024.o obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o +obj-$(CONFIG_DRM_TOSHIBA_TC358768) += tc358768.o obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/ obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o diff --git a/drivers/gpu/drm/bridge/adv7511/Kconfig b/drivers/gpu/drm/bridge/adv7511/Kconfig index 8a56ff81f4fb..47d4eb9e845d 100644 --- a/drivers/gpu/drm/bridge/adv7511/Kconfig +++ b/drivers/gpu/drm/bridge/adv7511/Kconfig @@ -4,8 +4,9 @@ config DRM_I2C_ADV7511 depends on OF select DRM_KMS_HELPER select REGMAP_I2C + select DRM_MIPI_DSI help - Support for the Analog Device ADV7511(W) and ADV7513 HDMI encoders. + Support for the Analog Device ADV7511(W)/13/33/35 HDMI encoders. config DRM_I2C_ADV7511_AUDIO bool "ADV7511 HDMI Audio driver" @@ -15,16 +16,8 @@ config DRM_I2C_ADV7511_AUDIO Support the ADV7511 HDMI Audio interface. This is used in conjunction with the AV7511 HDMI driver. -config DRM_I2C_ADV7533 - bool "ADV7533 encoder" - depends on DRM_I2C_ADV7511 - select DRM_MIPI_DSI - default y - help - Support for the Analog Devices ADV7533 DSI to HDMI encoder. - config DRM_I2C_ADV7511_CEC - bool "ADV7511/33 HDMI CEC driver" + bool "ADV7511/33/35 HDMI CEC driver" depends on DRM_I2C_ADV7511 select CEC_CORE default y diff --git a/drivers/gpu/drm/bridge/adv7511/Makefile b/drivers/gpu/drm/bridge/adv7511/Makefile index b46ebeb35fd4..d8ceb534b51f 100644 --- a/drivers/gpu/drm/bridge/adv7511/Makefile +++ b/drivers/gpu/drm/bridge/adv7511/Makefile @@ -1,6 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only -adv7511-y := adv7511_drv.o +adv7511-y := adv7511_drv.o adv7533.o adv7511-$(CONFIG_DRM_I2C_ADV7511_AUDIO) += adv7511_audio.o adv7511-$(CONFIG_DRM_I2C_ADV7511_CEC) += adv7511_cec.o -adv7511-$(CONFIG_DRM_I2C_ADV7533) += adv7533.o obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h index 52b2adfdc877..a9bb734366ae 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511.h +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -320,6 +320,7 @@ struct adv7511_video_config { enum adv7511_type { ADV7511, ADV7533, + ADV7535, }; #define ADV7511_MAX_ADDRS 3 @@ -393,7 +394,6 @@ static inline int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511) } #endif -#ifdef CONFIG_DRM_I2C_ADV7533 void adv7533_dsi_power_on(struct adv7511 *adv); void adv7533_dsi_power_off(struct adv7511 *adv); void adv7533_mode_set(struct adv7511 *adv, const struct drm_display_mode *mode); @@ -402,44 +402,6 @@ int adv7533_patch_cec_registers(struct adv7511 *adv); int adv7533_attach_dsi(struct adv7511 *adv); void adv7533_detach_dsi(struct adv7511 *adv); int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv); -#else -static inline void adv7533_dsi_power_on(struct adv7511 *adv) -{ -} - -static inline void adv7533_dsi_power_off(struct adv7511 *adv) -{ -} - -static inline void adv7533_mode_set(struct adv7511 *adv, - const struct drm_display_mode *mode) -{ -} - -static inline int adv7533_patch_registers(struct adv7511 *adv) -{ - return -ENODEV; -} - -static inline int adv7533_patch_cec_registers(struct adv7511 *adv) -{ - return -ENODEV; -} - -static inline int adv7533_attach_dsi(struct adv7511 *adv) -{ - return -ENODEV; -} - -static inline void adv7533_detach_dsi(struct adv7511 *adv) -{ -} - -static inline int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv) -{ - return -ENODEV; -} -#endif #ifdef CONFIG_DRM_I2C_ADV7511_AUDIO int adv7511_audio_init(struct device *dev, struct adv7511 *adv7511); diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index 9e13e466e72c..a275e6c91bd7 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -367,7 +367,7 @@ static void adv7511_power_on(struct adv7511 *adv7511) */ regcache_sync(adv7511->regmap); - if (adv7511->type == ADV7533) + if (adv7511->type == ADV7533 || adv7511->type == ADV7535) adv7533_dsi_power_on(adv7511); adv7511->powered = true; } @@ -387,7 +387,7 @@ static void __adv7511_power_off(struct adv7511 *adv7511) static void adv7511_power_off(struct adv7511 *adv7511) { __adv7511_power_off(adv7511); - if (adv7511->type == ADV7533) + if (adv7511->type == ADV7533 || adv7511->type == ADV7535) adv7533_dsi_power_off(adv7511); adv7511->powered = false; } @@ -761,7 +761,7 @@ static void adv7511_mode_set(struct adv7511 *adv7511, regmap_update_bits(adv7511->regmap, 0x17, 0x60, (vsync_polarity << 6) | (hsync_polarity << 5)); - if (adv7511->type == ADV7533) + if (adv7511->type == ADV7533 || adv7511->type == ADV7535) adv7533_mode_set(adv7511, adj_mode); drm_mode_copy(&adv7511->curr_mode, adj_mode); @@ -874,7 +874,7 @@ static int adv7511_bridge_attach(struct drm_bridge *bridge) &adv7511_connector_helper_funcs); drm_connector_attach_encoder(&adv->connector, bridge->encoder); - if (adv->type == ADV7533) + if (adv->type == ADV7533 || adv->type == ADV7535) ret = adv7533_attach_dsi(adv); if (adv->i2c_main->irq) @@ -952,7 +952,7 @@ static bool adv7511_cec_register_volatile(struct device *dev, unsigned int reg) struct i2c_client *i2c = to_i2c_client(dev); struct adv7511 *adv7511 = i2c_get_clientdata(i2c); - if (adv7511->type == ADV7533) + if (adv7511->type == ADV7533 || adv7511->type == ADV7535) reg -= ADV7533_REG_CEC_OFFSET; switch (reg) { @@ -994,7 +994,7 @@ static int adv7511_init_cec_regmap(struct adv7511 *adv) goto err; } - if (adv->type == ADV7533) { + if (adv->type == ADV7533 || adv->type == ADV7535) { ret = adv7533_patch_cec_registers(adv); if (ret) goto err; @@ -1242,7 +1242,7 @@ static int adv7511_remove(struct i2c_client *i2c) { struct adv7511 *adv7511 = i2c_get_clientdata(i2c); - if (adv7511->type == ADV7533) + if (adv7511->type == ADV7533 || adv7511->type == ADV7535) adv7533_detach_dsi(adv7511); i2c_unregister_device(adv7511->i2c_cec); if (adv7511->cec_clk) @@ -1266,9 +1266,8 @@ static const struct i2c_device_id adv7511_i2c_ids[] = { { "adv7511", ADV7511 }, { "adv7511w", ADV7511 }, { "adv7513", ADV7511 }, -#ifdef CONFIG_DRM_I2C_ADV7533 { "adv7533", ADV7533 }, -#endif + { "adv7535", ADV7535 }, { } }; MODULE_DEVICE_TABLE(i2c, adv7511_i2c_ids); @@ -1277,9 +1276,8 @@ static const struct of_device_id adv7511_of_ids[] = { { .compatible = "adi,adv7511", .data = (void *)ADV7511 }, { .compatible = "adi,adv7511w", .data = (void *)ADV7511 }, { .compatible = "adi,adv7513", .data = (void *)ADV7511 }, -#ifdef CONFIG_DRM_I2C_ADV7533 { .compatible = "adi,adv7533", .data = (void *)ADV7533 }, -#endif + { .compatible = "adi,adv7535", .data = (void *)ADV7535 }, { } }; MODULE_DEVICE_TABLE(of, adv7511_of_ids); diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index 6effe532f820..dfb59a5fefea 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -1289,19 +1289,21 @@ struct drm_crtc *analogix_dp_get_new_crtc(struct analogix_dp_device *dp, return conn_state->crtc; } -static void analogix_dp_bridge_atomic_pre_enable(struct drm_bridge *bridge, - struct drm_atomic_state *state) +static void +analogix_dp_bridge_atomic_pre_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { + struct drm_atomic_state *old_state = old_bridge_state->base.state; struct analogix_dp_device *dp = bridge->driver_private; struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state; int ret; - crtc = analogix_dp_get_new_crtc(dp, state); + crtc = analogix_dp_get_new_crtc(dp, old_state); if (!crtc) return; - old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc); + old_crtc_state = drm_atomic_get_old_crtc_state(old_state, crtc); /* Don't touch the panel if we're coming back from PSR */ if (old_crtc_state && old_crtc_state->self_refresh_active) return; @@ -1366,20 +1368,22 @@ out_dp_clk_pre: return ret; } -static void analogix_dp_bridge_atomic_enable(struct drm_bridge *bridge, - struct drm_atomic_state *state) +static void +analogix_dp_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { + struct drm_atomic_state *old_state = old_bridge_state->base.state; struct analogix_dp_device *dp = bridge->driver_private; struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state; int timeout_loop = 0; int ret; - crtc = analogix_dp_get_new_crtc(dp, state); + crtc = analogix_dp_get_new_crtc(dp, old_state); if (!crtc) return; - old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc); + old_crtc_state = drm_atomic_get_old_crtc_state(old_state, crtc); /* Not a full enable, just disable PSR and continue */ if (old_crtc_state && old_crtc_state->self_refresh_active) { ret = analogix_dp_disable_psr(dp); @@ -1440,18 +1444,20 @@ static void analogix_dp_bridge_disable(struct drm_bridge *bridge) dp->dpms_mode = DRM_MODE_DPMS_OFF; } -static void analogix_dp_bridge_atomic_disable(struct drm_bridge *bridge, - struct drm_atomic_state *state) +static void +analogix_dp_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { + struct drm_atomic_state *old_state = old_bridge_state->base.state; struct analogix_dp_device *dp = bridge->driver_private; struct drm_crtc *crtc; struct drm_crtc_state *new_crtc_state = NULL; - crtc = analogix_dp_get_new_crtc(dp, state); + crtc = analogix_dp_get_new_crtc(dp, old_state); if (!crtc) goto out; - new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + new_crtc_state = drm_atomic_get_new_crtc_state(old_state, crtc); if (!new_crtc_state) goto out; @@ -1463,20 +1469,21 @@ out: analogix_dp_bridge_disable(bridge); } -static -void analogix_dp_bridge_atomic_post_disable(struct drm_bridge *bridge, - struct drm_atomic_state *state) +static void +analogix_dp_bridge_atomic_post_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { + struct drm_atomic_state *old_state = old_bridge_state->base.state; struct analogix_dp_device *dp = bridge->driver_private; struct drm_crtc *crtc; struct drm_crtc_state *new_crtc_state; int ret; - crtc = analogix_dp_get_new_crtc(dp, state); + crtc = analogix_dp_get_new_crtc(dp, old_state); if (!crtc) return; - new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + new_crtc_state = drm_atomic_get_new_crtc_state(old_state, crtc); if (!new_crtc_state || !new_crtc_state->self_refresh_active) return; @@ -1563,6 +1570,9 @@ static void analogix_dp_bridge_mode_set(struct drm_bridge *bridge, } static const struct drm_bridge_funcs analogix_dp_bridge_funcs = { + .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, .atomic_pre_enable = analogix_dp_bridge_atomic_pre_enable, .atomic_enable = analogix_dp_bridge_atomic_enable, .atomic_disable = analogix_dp_bridge_atomic_disable, diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c index f66777e24968..fa764f9a91b6 100644 --- a/drivers/gpu/drm/bridge/panel.c +++ b/drivers/gpu/drm/bridge/panel.c @@ -151,7 +151,7 @@ static const struct drm_bridge_funcs panel_bridge_bridge_funcs = { * known type. Calling this function with a panel whose connector type is * DRM_MODE_CONNECTOR_Unknown will return NULL. * - * See devm_drm_panel_bridge_add() for an automatically manged version of this + * See devm_drm_panel_bridge_add() for an automatically managed version of this * function. */ struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel) diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c new file mode 100644 index 000000000000..c6c06688aff2 --- /dev/null +++ b/drivers/gpu/drm/bridge/parade-ps8640.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016 MediaTek Inc. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/regulator/consumer.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> + +#define PAGE2_GPIO_H 0xa7 +#define PS_GPIO9 BIT(1) +#define PAGE2_I2C_BYPASS 0xea +#define I2C_BYPASS_EN 0xd0 +#define PAGE2_MCS_EN 0xf3 +#define MCS_EN BIT(0) +#define PAGE3_SET_ADD 0xfe +#define VDO_CTL_ADD 0x13 +#define VDO_DIS 0x18 +#define VDO_EN 0x1c +#define DP_NUM_LANES 4 + +/* + * PS8640 uses multiple addresses: + * page[0]: for DP control + * page[1]: for VIDEO Bridge + * page[2]: for control top + * page[3]: for DSI Link Control1 + * page[4]: for MIPI Phy + * page[5]: for VPLL + * page[6]: for DSI Link Control2 + * page[7]: for SPI ROM mapping + */ +enum page_addr_offset { + PAGE0_DP_CNTL = 0, + PAGE1_VDO_BDG, + PAGE2_TOP_CNTL, + PAGE3_DSI_CNTL1, + PAGE4_MIPI_PHY, + PAGE5_VPLL, + PAGE6_DSI_CNTL2, + PAGE7_SPI_CNTL, + MAX_DEVS +}; + +enum ps8640_vdo_control { + DISABLE = VDO_DIS, + ENABLE = VDO_EN, +}; + +struct ps8640 { + struct drm_bridge bridge; + struct drm_bridge *panel_bridge; + struct mipi_dsi_device *dsi; + struct i2c_client *page[MAX_DEVS]; + struct regulator_bulk_data supplies[2]; + struct gpio_desc *gpio_reset; + struct gpio_desc *gpio_powerdown; +}; + +static inline struct ps8640 *bridge_to_ps8640(struct drm_bridge *e) +{ + return container_of(e, struct ps8640, bridge); +} + +static int ps8640_bridge_vdo_control(struct ps8640 *ps_bridge, + const enum ps8640_vdo_control ctrl) +{ + struct i2c_client *client = ps_bridge->page[PAGE3_DSI_CNTL1]; + u8 vdo_ctrl_buf[] = { VDO_CTL_ADD, ctrl }; + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, PAGE3_SET_ADD, + sizeof(vdo_ctrl_buf), + vdo_ctrl_buf); + if (ret < 0) + return ret; + + return 0; +} + +static void ps8640_pre_enable(struct drm_bridge *bridge) +{ + struct ps8640 *ps_bridge = bridge_to_ps8640(bridge); + struct i2c_client *client = ps_bridge->page[PAGE2_TOP_CNTL]; + unsigned long timeout; + int ret, status; + + ret = regulator_bulk_enable(ARRAY_SIZE(ps_bridge->supplies), + ps_bridge->supplies); + if (ret < 0) { + DRM_ERROR("cannot enable regulators %d\n", ret); + return; + } + + gpiod_set_value(ps_bridge->gpio_powerdown, 0); + gpiod_set_value(ps_bridge->gpio_reset, 1); + usleep_range(2000, 2500); + gpiod_set_value(ps_bridge->gpio_reset, 0); + + /* + * Wait for the ps8640 embedded MCU to be ready + * First wait 200ms and then check the MCU ready flag every 20ms + */ + msleep(200); + + timeout = jiffies + msecs_to_jiffies(200) + 1; + + while (time_is_after_jiffies(timeout)) { + status = i2c_smbus_read_byte_data(client, PAGE2_GPIO_H); + if (status < 0) { + DRM_ERROR("failed read PAGE2_GPIO_H: %d\n", status); + goto err_regulators_disable; + } + if ((status & PS_GPIO9) == PS_GPIO9) + break; + + msleep(20); + } + + msleep(50); + + /* + * The Manufacturer Command Set (MCS) is a device dependent interface + * intended for factory programming of the display module default + * parameters. Once the display module is configured, the MCS shall be + * disabled by the manufacturer. Once disabled, all MCS commands are + * ignored by the display interface. + */ + status = i2c_smbus_read_byte_data(client, PAGE2_MCS_EN); + if (status < 0) { + DRM_ERROR("failed read PAGE2_MCS_EN: %d\n", status); + goto err_regulators_disable; + } + + ret = i2c_smbus_write_byte_data(client, PAGE2_MCS_EN, + status & ~MCS_EN); + if (ret < 0) { + DRM_ERROR("failed write PAGE2_MCS_EN: %d\n", ret); + goto err_regulators_disable; + } + + ret = ps8640_bridge_vdo_control(ps_bridge, ENABLE); + if (ret) { + DRM_ERROR("failed to enable VDO: %d\n", ret); + goto err_regulators_disable; + } + + /* Switch access edp panel's edid through i2c */ + ret = i2c_smbus_write_byte_data(client, PAGE2_I2C_BYPASS, + I2C_BYPASS_EN); + if (ret < 0) { + DRM_ERROR("failed write PAGE2_I2C_BYPASS: %d\n", ret); + goto err_regulators_disable; + } + + return; + +err_regulators_disable: + regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies), + ps_bridge->supplies); +} + +static void ps8640_post_disable(struct drm_bridge *bridge) +{ + struct ps8640 *ps_bridge = bridge_to_ps8640(bridge); + int ret; + + ret = ps8640_bridge_vdo_control(ps_bridge, DISABLE); + if (ret < 0) + DRM_ERROR("failed to disable VDO: %d\n", ret); + + gpiod_set_value(ps_bridge->gpio_reset, 1); + gpiod_set_value(ps_bridge->gpio_powerdown, 1); + ret = regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies), + ps_bridge->supplies); + if (ret < 0) + DRM_ERROR("cannot disable regulators %d\n", ret); +} + +static int ps8640_bridge_attach(struct drm_bridge *bridge) +{ + struct ps8640 *ps_bridge = bridge_to_ps8640(bridge); + struct device *dev = &ps_bridge->page[0]->dev; + struct device_node *in_ep, *dsi_node; + struct mipi_dsi_device *dsi; + struct mipi_dsi_host *host; + int ret; + const struct mipi_dsi_device_info info = { .type = "ps8640", + .channel = 0, + .node = NULL, + }; + /* port@0 is ps8640 dsi input port */ + in_ep = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1); + if (!in_ep) + return -ENODEV; + + dsi_node = of_graph_get_remote_port_parent(in_ep); + of_node_put(in_ep); + if (!dsi_node) + return -ENODEV; + + host = of_find_mipi_dsi_host_by_node(dsi_node); + of_node_put(dsi_node); + if (!host) + return -ENODEV; + + dsi = mipi_dsi_device_register_full(host, &info); + if (IS_ERR(dsi)) { + dev_err(dev, "failed to create dsi device\n"); + ret = PTR_ERR(dsi); + return ret; + } + + ps_bridge->dsi = dsi; + + dsi->host = host; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->lanes = DP_NUM_LANES; + ret = mipi_dsi_attach(dsi); + if (ret) + goto err_dsi_attach; + + /* Attach the panel-bridge to the dsi bridge */ + return drm_bridge_attach(bridge->encoder, ps_bridge->panel_bridge, + &ps_bridge->bridge); + +err_dsi_attach: + mipi_dsi_device_unregister(dsi); + return ret; +} + +static const struct drm_bridge_funcs ps8640_bridge_funcs = { + .attach = ps8640_bridge_attach, + .post_disable = ps8640_post_disable, + .pre_enable = ps8640_pre_enable, +}; + +static int ps8640_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device_node *np = dev->of_node; + struct ps8640 *ps_bridge; + struct drm_panel *panel; + int ret; + u32 i; + + ps_bridge = devm_kzalloc(dev, sizeof(*ps_bridge), GFP_KERNEL); + if (!ps_bridge) + return -ENOMEM; + + /* port@1 is ps8640 output port */ + ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, NULL); + if (ret < 0) + return ret; + if (!panel) + return -ENODEV; + + panel->connector_type = DRM_MODE_CONNECTOR_eDP; + + ps_bridge->panel_bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(ps_bridge->panel_bridge)) + return PTR_ERR(ps_bridge->panel_bridge); + + ps_bridge->supplies[0].supply = "vdd33"; + ps_bridge->supplies[1].supply = "vdd12"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ps_bridge->supplies), + ps_bridge->supplies); + if (ret) + return ret; + + ps_bridge->gpio_powerdown = devm_gpiod_get(&client->dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(ps_bridge->gpio_powerdown)) + return PTR_ERR(ps_bridge->gpio_powerdown); + + /* + * Assert the reset to avoid the bridge being initialized prematurely + */ + ps_bridge->gpio_reset = devm_gpiod_get(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(ps_bridge->gpio_reset)) + return PTR_ERR(ps_bridge->gpio_reset); + + ps_bridge->bridge.funcs = &ps8640_bridge_funcs; + ps_bridge->bridge.of_node = dev->of_node; + + ps_bridge->page[PAGE0_DP_CNTL] = client; + + for (i = 1; i < ARRAY_SIZE(ps_bridge->page); i++) { + ps_bridge->page[i] = devm_i2c_new_dummy_device(&client->dev, + client->adapter, + client->addr + i); + if (IS_ERR(ps_bridge->page[i])) { + dev_err(dev, "failed i2c dummy device, address %02x\n", + client->addr + i); + return PTR_ERR(ps_bridge->page[i]); + } + } + + i2c_set_clientdata(client, ps_bridge); + + drm_bridge_add(&ps_bridge->bridge); + + return 0; +} + +static int ps8640_remove(struct i2c_client *client) +{ + struct ps8640 *ps_bridge = i2c_get_clientdata(client); + + drm_bridge_remove(&ps_bridge->bridge); + + return 0; +} + +static const struct of_device_id ps8640_match[] = { + { .compatible = "parade,ps8640" }, + { } +}; +MODULE_DEVICE_TABLE(of, ps8640_match); + +static struct i2c_driver ps8640_driver = { + .probe_new = ps8640_probe, + .remove = ps8640_remove, + .driver = { + .name = "ps8640", + .of_match_table = ps8640_match, + }, +}; +module_i2c_driver(ps8640_driver); + +MODULE_AUTHOR("Jitao Shi <jitao.shi@mediatek.com>"); +MODULE_AUTHOR("CK Hu <ck.hu@mediatek.com>"); +MODULE_AUTHOR("Enric Balletbo i Serra <enric.balletbo@collabora.com>"); +MODULE_DESCRIPTION("PARADE ps8640 DSI-eDP converter driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c index b18351b6760a..12823ae91065 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c @@ -824,7 +824,8 @@ static void dw_mipi_dsi_bridge_post_disable(struct drm_bridge *bridge) * This needs to be fixed in the drm_bridge framework and the API * needs to be updated to manage our own call chains... */ - dsi->panel_bridge->funcs->post_disable(dsi->panel_bridge); + if (dsi->panel_bridge->funcs->post_disable) + dsi->panel_bridge->funcs->post_disable(dsi->panel_bridge); if (phy_ops->power_off) phy_ops->power_off(dsi->plat_data->priv_data); diff --git a/drivers/gpu/drm/bridge/tc358768.c b/drivers/gpu/drm/bridge/tc358768.c new file mode 100644 index 000000000000..da7af03256f6 --- /dev/null +++ b/drivers/gpu/drm/bridge/tc358768.c @@ -0,0 +1,1044 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com + * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <video/mipi_display.h> +#include <video/videomode.h> + +/* Global (16-bit addressable) */ +#define TC358768_CHIPID 0x0000 +#define TC358768_SYSCTL 0x0002 +#define TC358768_CONFCTL 0x0004 +#define TC358768_VSDLY 0x0006 +#define TC358768_DATAFMT 0x0008 +#define TC358768_GPIOEN 0x000E +#define TC358768_GPIODIR 0x0010 +#define TC358768_GPIOIN 0x0012 +#define TC358768_GPIOOUT 0x0014 +#define TC358768_PLLCTL0 0x0016 +#define TC358768_PLLCTL1 0x0018 +#define TC358768_CMDBYTE 0x0022 +#define TC358768_PP_MISC 0x0032 +#define TC358768_DSITX_DT 0x0050 +#define TC358768_FIFOSTATUS 0x00F8 + +/* Debug (16-bit addressable) */ +#define TC358768_VBUFCTRL 0x00E0 +#define TC358768_DBG_WIDTH 0x00E2 +#define TC358768_DBG_VBLANK 0x00E4 +#define TC358768_DBG_DATA 0x00E8 + +/* TX PHY (32-bit addressable) */ +#define TC358768_CLW_DPHYCONTTX 0x0100 +#define TC358768_D0W_DPHYCONTTX 0x0104 +#define TC358768_D1W_DPHYCONTTX 0x0108 +#define TC358768_D2W_DPHYCONTTX 0x010C +#define TC358768_D3W_DPHYCONTTX 0x0110 +#define TC358768_CLW_CNTRL 0x0140 +#define TC358768_D0W_CNTRL 0x0144 +#define TC358768_D1W_CNTRL 0x0148 +#define TC358768_D2W_CNTRL 0x014C +#define TC358768_D3W_CNTRL 0x0150 + +/* TX PPI (32-bit addressable) */ +#define TC358768_STARTCNTRL 0x0204 +#define TC358768_DSITXSTATUS 0x0208 +#define TC358768_LINEINITCNT 0x0210 +#define TC358768_LPTXTIMECNT 0x0214 +#define TC358768_TCLK_HEADERCNT 0x0218 +#define TC358768_TCLK_TRAILCNT 0x021C +#define TC358768_THS_HEADERCNT 0x0220 +#define TC358768_TWAKEUP 0x0224 +#define TC358768_TCLK_POSTCNT 0x0228 +#define TC358768_THS_TRAILCNT 0x022C +#define TC358768_HSTXVREGCNT 0x0230 +#define TC358768_HSTXVREGEN 0x0234 +#define TC358768_TXOPTIONCNTRL 0x0238 +#define TC358768_BTACNTRL1 0x023C + +/* TX CTRL (32-bit addressable) */ +#define TC358768_DSI_CONTROL 0x040C +#define TC358768_DSI_STATUS 0x0410 +#define TC358768_DSI_INT 0x0414 +#define TC358768_DSI_INT_ENA 0x0418 +#define TC358768_DSICMD_RDFIFO 0x0430 +#define TC358768_DSI_ACKERR 0x0434 +#define TC358768_DSI_ACKERR_INTENA 0x0438 +#define TC358768_DSI_ACKERR_HALT 0x043c +#define TC358768_DSI_RXERR 0x0440 +#define TC358768_DSI_RXERR_INTENA 0x0444 +#define TC358768_DSI_RXERR_HALT 0x0448 +#define TC358768_DSI_ERR 0x044C +#define TC358768_DSI_ERR_INTENA 0x0450 +#define TC358768_DSI_ERR_HALT 0x0454 +#define TC358768_DSI_CONFW 0x0500 +#define TC358768_DSI_LPCMD 0x0500 +#define TC358768_DSI_RESET 0x0504 +#define TC358768_DSI_INT_CLR 0x050C +#define TC358768_DSI_START 0x0518 + +/* DSITX CTRL (16-bit addressable) */ +#define TC358768_DSICMD_TX 0x0600 +#define TC358768_DSICMD_TYPE 0x0602 +#define TC358768_DSICMD_WC 0x0604 +#define TC358768_DSICMD_WD0 0x0610 +#define TC358768_DSICMD_WD1 0x0612 +#define TC358768_DSICMD_WD2 0x0614 +#define TC358768_DSICMD_WD3 0x0616 +#define TC358768_DSI_EVENT 0x0620 +#define TC358768_DSI_VSW 0x0622 +#define TC358768_DSI_VBPR 0x0624 +#define TC358768_DSI_VACT 0x0626 +#define TC358768_DSI_HSW 0x0628 +#define TC358768_DSI_HBPR 0x062A +#define TC358768_DSI_HACT 0x062C + +/* TC358768_DSI_CONTROL (0x040C) register */ +#define TC358768_DSI_CONTROL_DIS_MODE BIT(15) +#define TC358768_DSI_CONTROL_TXMD BIT(7) +#define TC358768_DSI_CONTROL_HSCKMD BIT(5) +#define TC358768_DSI_CONTROL_EOTDIS BIT(0) + +/* TC358768_DSI_CONFW (0x0500) register */ +#define TC358768_DSI_CONFW_MODE_SET (5 << 29) +#define TC358768_DSI_CONFW_MODE_CLR (6 << 29) +#define TC358768_DSI_CONFW_ADDR_DSI_CONTROL (0x3 << 24) + +static const char * const tc358768_supplies[] = { + "vddc", "vddmipi", "vddio" +}; + +struct tc358768_dsi_output { + struct mipi_dsi_device *dev; + struct drm_panel *panel; + struct drm_bridge *bridge; +}; + +struct tc358768_priv { + struct device *dev; + struct regmap *regmap; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(tc358768_supplies)]; + struct clk *refclk; + int enabled; + int error; + + struct mipi_dsi_host dsi_host; + struct drm_bridge bridge; + struct tc358768_dsi_output output; + + u32 pd_lines; /* number of Parallel Port Input Data Lines */ + u32 dsi_lanes; /* number of DSI Lanes */ + + /* Parameters for PLL programming */ + u32 fbd; /* PLL feedback divider */ + u32 prd; /* PLL input divider */ + u32 frs; /* PLL Freqency range for HSCK (post divider) */ + + u32 dsiclk; /* pll_clk / 2 */ +}; + +static inline struct tc358768_priv *dsi_host_to_tc358768(struct mipi_dsi_host + *host) +{ + return container_of(host, struct tc358768_priv, dsi_host); +} + +static inline struct tc358768_priv *bridge_to_tc358768(struct drm_bridge + *bridge) +{ + return container_of(bridge, struct tc358768_priv, bridge); +} + +static int tc358768_clear_error(struct tc358768_priv *priv) +{ + int ret = priv->error; + + priv->error = 0; + return ret; +} + +static void tc358768_write(struct tc358768_priv *priv, u32 reg, u32 val) +{ + size_t count = 2; + + if (priv->error) + return; + + /* 16-bit register? */ + if (reg < 0x100 || reg >= 0x600) + count = 1; + + priv->error = regmap_bulk_write(priv->regmap, reg, &val, count); +} + +static void tc358768_read(struct tc358768_priv *priv, u32 reg, u32 *val) +{ + size_t count = 2; + + if (priv->error) + return; + + /* 16-bit register? */ + if (reg < 0x100 || reg >= 0x600) { + *val = 0; + count = 1; + } + + priv->error = regmap_bulk_read(priv->regmap, reg, val, count); +} + +static void tc358768_update_bits(struct tc358768_priv *priv, u32 reg, u32 mask, + u32 val) +{ + u32 tmp, orig; + + tc358768_read(priv, reg, &orig); + tmp = orig & ~mask; + tmp |= val & mask; + if (tmp != orig) + tc358768_write(priv, reg, tmp); +} + +static int tc358768_sw_reset(struct tc358768_priv *priv) +{ + /* Assert Reset */ + tc358768_write(priv, TC358768_SYSCTL, 1); + /* Release Reset, Exit Sleep */ + tc358768_write(priv, TC358768_SYSCTL, 0); + + return tc358768_clear_error(priv); +} + +static void tc358768_hw_enable(struct tc358768_priv *priv) +{ + int ret; + + if (priv->enabled) + return; + + ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); + if (ret < 0) + dev_err(priv->dev, "error enabling regulators (%d)\n", ret); + + if (priv->reset_gpio) + usleep_range(200, 300); + + /* + * The RESX is active low (GPIO_ACTIVE_LOW). + * DEASSERT (value = 0) the reset_gpio to enable the chip + */ + gpiod_set_value_cansleep(priv->reset_gpio, 0); + + /* wait for encoder clocks to stabilize */ + usleep_range(1000, 2000); + + priv->enabled = true; +} + +static void tc358768_hw_disable(struct tc358768_priv *priv) +{ + int ret; + + if (!priv->enabled) + return; + + /* + * The RESX is active low (GPIO_ACTIVE_LOW). + * ASSERT (value = 1) the reset_gpio to disable the chip + */ + gpiod_set_value_cansleep(priv->reset_gpio, 1); + + ret = regulator_bulk_disable(ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret < 0) + dev_err(priv->dev, "error disabling regulators (%d)\n", ret); + + priv->enabled = false; +} + +static u32 tc358768_pll_to_pclk(struct tc358768_priv *priv, u32 pll_clk) +{ + return (u32)div_u64((u64)pll_clk * priv->dsi_lanes, priv->pd_lines); +} + +static u32 tc358768_pclk_to_pll(struct tc358768_priv *priv, u32 pclk) +{ + return (u32)div_u64((u64)pclk * priv->pd_lines, priv->dsi_lanes); +} + +static int tc358768_calc_pll(struct tc358768_priv *priv, + const struct drm_display_mode *mode, + bool verify_only) +{ + const u32 frs_limits[] = { + 1000000000, + 500000000, + 250000000, + 125000000, + 62500000 + }; + unsigned long refclk; + u32 prd, target_pll, i, max_pll, min_pll; + u32 frs, best_diff, best_pll, best_prd, best_fbd; + + target_pll = tc358768_pclk_to_pll(priv, mode->clock * 1000); + + /* pll_clk = RefClk * [(FBD + 1)/ (PRD + 1)] * [1 / (2^FRS)] */ + + for (i = 0; i < ARRAY_SIZE(frs_limits); i++) + if (target_pll >= frs_limits[i]) + break; + + if (i == ARRAY_SIZE(frs_limits) || i == 0) + return -EINVAL; + + frs = i - 1; + max_pll = frs_limits[i - 1]; + min_pll = frs_limits[i]; + + refclk = clk_get_rate(priv->refclk); + + best_diff = UINT_MAX; + best_pll = 0; + best_prd = 0; + best_fbd = 0; + + for (prd = 0; prd < 16; ++prd) { + u32 divisor = (prd + 1) * (1 << frs); + u32 fbd; + + for (fbd = 0; fbd < 512; ++fbd) { + u32 pll, diff; + + pll = (u32)div_u64((u64)refclk * (fbd + 1), divisor); + + if (pll >= max_pll || pll < min_pll) + continue; + + diff = max(pll, target_pll) - min(pll, target_pll); + + if (diff < best_diff) { + best_diff = diff; + best_pll = pll; + best_prd = prd; + best_fbd = fbd; + + if (best_diff == 0) + goto found; + } + } + } + + if (best_diff == UINT_MAX) { + dev_err(priv->dev, "could not find suitable PLL setup\n"); + return -EINVAL; + } + +found: + if (verify_only) + return 0; + + priv->fbd = best_fbd; + priv->prd = best_prd; + priv->frs = frs; + priv->dsiclk = best_pll / 2; + + return 0; +} + +static int tc358768_dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *dev) +{ + struct tc358768_priv *priv = dsi_host_to_tc358768(host); + struct drm_bridge *bridge; + struct drm_panel *panel; + struct device_node *ep; + int ret; + + if (dev->lanes > 4) { + dev_err(priv->dev, "unsupported number of data lanes(%u)\n", + dev->lanes); + return -EINVAL; + } + + /* + * tc358768 supports both Video and Pulse mode, but the driver only + * implements Video (event) mode currently + */ + if (!(dev->mode_flags & MIPI_DSI_MODE_VIDEO)) { + dev_err(priv->dev, "Only MIPI_DSI_MODE_VIDEO is supported\n"); + return -ENOTSUPP; + } + + /* + * tc358768 supports RGB888, RGB666, RGB666_PACKED and RGB565, but only + * RGB888 is verified. + */ + if (dev->format != MIPI_DSI_FMT_RGB888) { + dev_warn(priv->dev, "Only MIPI_DSI_FMT_RGB888 tested!\n"); + return -ENOTSUPP; + } + + ret = drm_of_find_panel_or_bridge(host->dev->of_node, 1, 0, &panel, + &bridge); + if (ret) + return ret; + + if (panel) { + bridge = drm_panel_bridge_add_typed(panel, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + } + + priv->output.dev = dev; + priv->output.bridge = bridge; + priv->output.panel = panel; + + priv->dsi_lanes = dev->lanes; + + /* get input ep (port0/endpoint0) */ + ret = -EINVAL; + ep = of_graph_get_endpoint_by_regs(host->dev->of_node, 0, 0); + if (ep) { + ret = of_property_read_u32(ep, "data-lines", &priv->pd_lines); + + of_node_put(ep); + } + + if (ret) + priv->pd_lines = mipi_dsi_pixel_format_to_bpp(dev->format); + + drm_bridge_add(&priv->bridge); + + return 0; +} + +static int tc358768_dsi_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *dev) +{ + struct tc358768_priv *priv = dsi_host_to_tc358768(host); + + drm_bridge_remove(&priv->bridge); + if (priv->output.panel) + drm_panel_bridge_remove(priv->output.bridge); + + return 0; +} + +static ssize_t tc358768_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct tc358768_priv *priv = dsi_host_to_tc358768(host); + struct mipi_dsi_packet packet; + int ret; + + if (!priv->enabled) { + dev_err(priv->dev, "Bridge is not enabled\n"); + return -ENODEV; + } + + if (msg->rx_len) { + dev_warn(priv->dev, "MIPI rx is not supported\n"); + return -ENOTSUPP; + } + + if (msg->tx_len > 8) { + dev_warn(priv->dev, "Maximum 8 byte MIPI tx is supported\n"); + return -ENOTSUPP; + } + + ret = mipi_dsi_create_packet(&packet, msg); + if (ret) + return ret; + + if (mipi_dsi_packet_format_is_short(msg->type)) { + tc358768_write(priv, TC358768_DSICMD_TYPE, + (0x10 << 8) | (packet.header[0] & 0x3f)); + tc358768_write(priv, TC358768_DSICMD_WC, 0); + tc358768_write(priv, TC358768_DSICMD_WD0, + (packet.header[2] << 8) | packet.header[1]); + } else { + int i; + + tc358768_write(priv, TC358768_DSICMD_TYPE, + (0x40 << 8) | (packet.header[0] & 0x3f)); + tc358768_write(priv, TC358768_DSICMD_WC, packet.payload_length); + for (i = 0; i < packet.payload_length; i += 2) { + u16 val = packet.payload[i]; + + if (i + 1 < packet.payload_length) + val |= packet.payload[i + 1] << 8; + + tc358768_write(priv, TC358768_DSICMD_WD0 + i, val); + } + } + + /* start transfer */ + tc358768_write(priv, TC358768_DSICMD_TX, 1); + + ret = tc358768_clear_error(priv); + if (ret) + dev_warn(priv->dev, "Software disable failed: %d\n", ret); + else + ret = packet.size; + + return ret; +} + +static const struct mipi_dsi_host_ops tc358768_dsi_host_ops = { + .attach = tc358768_dsi_host_attach, + .detach = tc358768_dsi_host_detach, + .transfer = tc358768_dsi_host_transfer, +}; + +static int tc358768_bridge_attach(struct drm_bridge *bridge) +{ + struct tc358768_priv *priv = bridge_to_tc358768(bridge); + + if (!drm_core_check_feature(bridge->dev, DRIVER_ATOMIC)) { + dev_err(priv->dev, "needs atomic updates support\n"); + return -ENOTSUPP; + } + + return drm_bridge_attach(bridge->encoder, priv->output.bridge, bridge); +} + +static enum drm_mode_status +tc358768_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_mode *mode) +{ + struct tc358768_priv *priv = bridge_to_tc358768(bridge); + + if (tc358768_calc_pll(priv, mode, true)) + return MODE_CLOCK_RANGE; + + return MODE_OK; +} + +static void tc358768_bridge_disable(struct drm_bridge *bridge) +{ + struct tc358768_priv *priv = bridge_to_tc358768(bridge); + int ret; + + /* set FrmStop */ + tc358768_update_bits(priv, TC358768_PP_MISC, BIT(15), BIT(15)); + + /* wait at least for one frame */ + msleep(50); + + /* clear PP_en */ + tc358768_update_bits(priv, TC358768_CONFCTL, BIT(6), 0); + + /* set RstPtr */ + tc358768_update_bits(priv, TC358768_PP_MISC, BIT(14), BIT(14)); + + ret = tc358768_clear_error(priv); + if (ret) + dev_warn(priv->dev, "Software disable failed: %d\n", ret); +} + +static void tc358768_bridge_post_disable(struct drm_bridge *bridge) +{ + struct tc358768_priv *priv = bridge_to_tc358768(bridge); + + tc358768_hw_disable(priv); +} + +static int tc358768_setup_pll(struct tc358768_priv *priv, + const struct drm_display_mode *mode) +{ + u32 fbd, prd, frs; + int ret; + + ret = tc358768_calc_pll(priv, mode, false); + if (ret) { + dev_err(priv->dev, "PLL calculation failed: %d\n", ret); + return ret; + } + + fbd = priv->fbd; + prd = priv->prd; + frs = priv->frs; + + dev_dbg(priv->dev, "PLL: refclk %lu, fbd %u, prd %u, frs %u\n", + clk_get_rate(priv->refclk), fbd, prd, frs); + dev_dbg(priv->dev, "PLL: pll_clk: %u, DSIClk %u, DSIByteClk %u\n", + priv->dsiclk * 2, priv->dsiclk, priv->dsiclk / 4); + dev_dbg(priv->dev, "PLL: pclk %u (panel: %u)\n", + tc358768_pll_to_pclk(priv, priv->dsiclk * 2), + mode->clock * 1000); + + /* PRD[15:12] FBD[8:0] */ + tc358768_write(priv, TC358768_PLLCTL0, (prd << 12) | fbd); + + /* FRS[11:10] LBWS[9:8] CKEN[4] RESETB[1] EN[0] */ + tc358768_write(priv, TC358768_PLLCTL1, + (frs << 10) | (0x2 << 8) | BIT(1) | BIT(0)); + + /* wait for lock */ + usleep_range(1000, 2000); + + /* FRS[11:10] LBWS[9:8] CKEN[4] PLL_CKEN[4] RESETB[1] EN[0] */ + tc358768_write(priv, TC358768_PLLCTL1, + (frs << 10) | (0x2 << 8) | BIT(4) | BIT(1) | BIT(0)); + + return tc358768_clear_error(priv); +} + +#define TC358768_PRECISION 1000 +static u32 tc358768_ns_to_cnt(u32 ns, u32 period_nsk) +{ + return (ns * TC358768_PRECISION + period_nsk) / period_nsk; +} + +static u32 tc358768_to_ns(u32 nsk) +{ + return (nsk / TC358768_PRECISION); +} + +static void tc358768_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct tc358768_priv *priv = bridge_to_tc358768(bridge); + struct mipi_dsi_device *dsi_dev = priv->output.dev; + u32 val, val2, lptxcnt, hact, data_type; + const struct drm_display_mode *mode; + u32 dsibclk_nsk, dsiclk_nsk, ui_nsk, phy_delay_nsk; + u32 dsiclk, dsibclk; + int ret, i; + + tc358768_hw_enable(priv); + + ret = tc358768_sw_reset(priv); + if (ret) { + dev_err(priv->dev, "Software reset failed: %d\n", ret); + tc358768_hw_disable(priv); + return; + } + + mode = &bridge->encoder->crtc->state->adjusted_mode; + ret = tc358768_setup_pll(priv, mode); + if (ret) { + dev_err(priv->dev, "PLL setup failed: %d\n", ret); + tc358768_hw_disable(priv); + return; + } + + dsiclk = priv->dsiclk; + dsibclk = dsiclk / 4; + + /* Data Format Control Register */ + val = BIT(2) | BIT(1) | BIT(0); /* rdswap_en | dsitx_en | txdt_en */ + switch (dsi_dev->format) { + case MIPI_DSI_FMT_RGB888: + val |= (0x3 << 4); + hact = mode->hdisplay * 3; + data_type = MIPI_DSI_PACKED_PIXEL_STREAM_24; + break; + case MIPI_DSI_FMT_RGB666: + val |= (0x4 << 4); + hact = mode->hdisplay * 3; + data_type = MIPI_DSI_PACKED_PIXEL_STREAM_18; + break; + + case MIPI_DSI_FMT_RGB666_PACKED: + val |= (0x4 << 4) | BIT(3); + hact = mode->hdisplay * 18 / 8; + data_type = MIPI_DSI_PIXEL_STREAM_3BYTE_18; + break; + + case MIPI_DSI_FMT_RGB565: + val |= (0x5 << 4); + hact = mode->hdisplay * 2; + data_type = MIPI_DSI_PACKED_PIXEL_STREAM_16; + break; + default: + dev_err(priv->dev, "Invalid data format (%u)\n", + dsi_dev->format); + tc358768_hw_disable(priv); + return; + } + + /* VSDly[9:0] */ + tc358768_write(priv, TC358768_VSDLY, 1); + + tc358768_write(priv, TC358768_DATAFMT, val); + tc358768_write(priv, TC358768_DSITX_DT, data_type); + + /* Enable D-PHY (HiZ->LP11) */ + tc358768_write(priv, TC358768_CLW_CNTRL, 0x0000); + /* Enable lanes */ + for (i = 0; i < dsi_dev->lanes; i++) + tc358768_write(priv, TC358768_D0W_CNTRL + i * 4, 0x0000); + + /* DSI Timings */ + dsibclk_nsk = (u32)div_u64((u64)1000000000 * TC358768_PRECISION, + dsibclk); + dsiclk_nsk = (u32)div_u64((u64)1000000000 * TC358768_PRECISION, dsiclk); + ui_nsk = dsiclk_nsk / 2; + phy_delay_nsk = dsibclk_nsk + 2 * dsiclk_nsk; + dev_dbg(priv->dev, "dsiclk_nsk: %u\n", dsiclk_nsk); + dev_dbg(priv->dev, "ui_nsk: %u\n", ui_nsk); + dev_dbg(priv->dev, "dsibclk_nsk: %u\n", dsibclk_nsk); + dev_dbg(priv->dev, "phy_delay_nsk: %u\n", phy_delay_nsk); + + /* LP11 > 100us for D-PHY Rx Init */ + val = tc358768_ns_to_cnt(100 * 1000, dsibclk_nsk) - 1; + dev_dbg(priv->dev, "LINEINITCNT: 0x%x\n", val); + tc358768_write(priv, TC358768_LINEINITCNT, val); + + /* LPTimeCnt > 50ns */ + val = tc358768_ns_to_cnt(50, dsibclk_nsk) - 1; + lptxcnt = val; + dev_dbg(priv->dev, "LPTXTIMECNT: 0x%x\n", val); + tc358768_write(priv, TC358768_LPTXTIMECNT, val); + + /* 38ns < TCLK_PREPARE < 95ns */ + val = tc358768_ns_to_cnt(65, dsibclk_nsk) - 1; + /* TCLK_PREPARE > 300ns */ + val2 = tc358768_ns_to_cnt(300 + tc358768_to_ns(3 * ui_nsk), + dsibclk_nsk); + val |= (val2 - tc358768_to_ns(phy_delay_nsk - dsibclk_nsk)) << 8; + dev_dbg(priv->dev, "TCLK_HEADERCNT: 0x%x\n", val); + tc358768_write(priv, TC358768_TCLK_HEADERCNT, val); + + /* TCLK_TRAIL > 60ns + 3*UI */ + val = 60 + tc358768_to_ns(3 * ui_nsk); + val = tc358768_ns_to_cnt(val, dsibclk_nsk) - 5; + dev_dbg(priv->dev, "TCLK_TRAILCNT: 0x%x\n", val); + tc358768_write(priv, TC358768_TCLK_TRAILCNT, val); + + /* 40ns + 4*UI < THS_PREPARE < 85ns + 6*UI */ + val = 50 + tc358768_to_ns(4 * ui_nsk); + val = tc358768_ns_to_cnt(val, dsibclk_nsk) - 1; + /* THS_ZERO > 145ns + 10*UI */ + val2 = tc358768_ns_to_cnt(145 - tc358768_to_ns(ui_nsk), dsibclk_nsk); + val |= (val2 - tc358768_to_ns(phy_delay_nsk)) << 8; + dev_dbg(priv->dev, "THS_HEADERCNT: 0x%x\n", val); + tc358768_write(priv, TC358768_THS_HEADERCNT, val); + + /* TWAKEUP > 1ms in lptxcnt steps */ + val = tc358768_ns_to_cnt(1020000, dsibclk_nsk); + val = val / (lptxcnt + 1) - 1; + dev_dbg(priv->dev, "TWAKEUP: 0x%x\n", val); + tc358768_write(priv, TC358768_TWAKEUP, val); + + /* TCLK_POSTCNT > 60ns + 52*UI */ + val = tc358768_ns_to_cnt(60 + tc358768_to_ns(52 * ui_nsk), + dsibclk_nsk) - 3; + dev_dbg(priv->dev, "TCLK_POSTCNT: 0x%x\n", val); + tc358768_write(priv, TC358768_TCLK_POSTCNT, val); + + /* 60ns + 4*UI < THS_PREPARE < 105ns + 12*UI */ + val = tc358768_ns_to_cnt(60 + tc358768_to_ns(15 * ui_nsk), + dsibclk_nsk) - 5; + dev_dbg(priv->dev, "THS_TRAILCNT: 0x%x\n", val); + tc358768_write(priv, TC358768_THS_TRAILCNT, val); + + val = BIT(0); + for (i = 0; i < dsi_dev->lanes; i++) + val |= BIT(i + 1); + tc358768_write(priv, TC358768_HSTXVREGEN, val); + + if (!(dsi_dev->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)) + tc358768_write(priv, TC358768_TXOPTIONCNTRL, 0x1); + + /* TXTAGOCNT[26:16] RXTASURECNT[10:0] */ + val = tc358768_to_ns((lptxcnt + 1) * dsibclk_nsk * 4); + val = tc358768_ns_to_cnt(val, dsibclk_nsk) - 1; + val2 = tc358768_ns_to_cnt(tc358768_to_ns((lptxcnt + 1) * dsibclk_nsk), + dsibclk_nsk) - 2; + val |= val2 << 16; + dev_dbg(priv->dev, "BTACNTRL1: 0x%x\n", val); + tc358768_write(priv, TC358768_BTACNTRL1, val); + + /* START[0] */ + tc358768_write(priv, TC358768_STARTCNTRL, 1); + + /* Set event mode */ + tc358768_write(priv, TC358768_DSI_EVENT, 1); + + /* vsw (+ vbp) */ + tc358768_write(priv, TC358768_DSI_VSW, + mode->vtotal - mode->vsync_start); + /* vbp (not used in event mode) */ + tc358768_write(priv, TC358768_DSI_VBPR, 0); + /* vact */ + tc358768_write(priv, TC358768_DSI_VACT, mode->vdisplay); + + /* (hsw + hbp) * byteclk * ndl / pclk */ + val = (u32)div_u64((mode->htotal - mode->hsync_start) * + ((u64)priv->dsiclk / 4) * priv->dsi_lanes, + mode->clock * 1000); + tc358768_write(priv, TC358768_DSI_HSW, val); + /* hbp (not used in event mode) */ + tc358768_write(priv, TC358768_DSI_HBPR, 0); + /* hact (bytes) */ + tc358768_write(priv, TC358768_DSI_HACT, hact); + + /* VSYNC polarity */ + if (!(mode->flags & DRM_MODE_FLAG_NVSYNC)) + tc358768_update_bits(priv, TC358768_CONFCTL, BIT(5), BIT(5)); + /* HSYNC polarity */ + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + tc358768_update_bits(priv, TC358768_PP_MISC, BIT(0), BIT(0)); + + /* Start DSI Tx */ + tc358768_write(priv, TC358768_DSI_START, 0x1); + + /* Configure DSI_Control register */ + val = TC358768_DSI_CONFW_MODE_CLR | TC358768_DSI_CONFW_ADDR_DSI_CONTROL; + val |= TC358768_DSI_CONTROL_TXMD | TC358768_DSI_CONTROL_HSCKMD | + 0x3 << 1 | TC358768_DSI_CONTROL_EOTDIS; + tc358768_write(priv, TC358768_DSI_CONFW, val); + + val = TC358768_DSI_CONFW_MODE_SET | TC358768_DSI_CONFW_ADDR_DSI_CONTROL; + val |= (dsi_dev->lanes - 1) << 1; + + if (!(dsi_dev->mode_flags & MIPI_DSI_MODE_LPM)) + val |= TC358768_DSI_CONTROL_TXMD; + + if (!(dsi_dev->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)) + val |= TC358768_DSI_CONTROL_HSCKMD; + + if (dsi_dev->mode_flags & MIPI_DSI_MODE_EOT_PACKET) + val |= TC358768_DSI_CONTROL_EOTDIS; + + tc358768_write(priv, TC358768_DSI_CONFW, val); + + val = TC358768_DSI_CONFW_MODE_CLR | TC358768_DSI_CONFW_ADDR_DSI_CONTROL; + val |= TC358768_DSI_CONTROL_DIS_MODE; /* DSI mode */ + tc358768_write(priv, TC358768_DSI_CONFW, val); + + ret = tc358768_clear_error(priv); + if (ret) { + dev_err(priv->dev, "Bridge pre_enable failed: %d\n", ret); + tc358768_bridge_disable(bridge); + tc358768_bridge_post_disable(bridge); + } +} + +static void tc358768_bridge_enable(struct drm_bridge *bridge) +{ + struct tc358768_priv *priv = bridge_to_tc358768(bridge); + int ret; + + if (!priv->enabled) { + dev_err(priv->dev, "Bridge is not enabled\n"); + return; + } + + /* clear FrmStop and RstPtr */ + tc358768_update_bits(priv, TC358768_PP_MISC, 0x3 << 14, 0); + + /* set PP_en */ + tc358768_update_bits(priv, TC358768_CONFCTL, BIT(6), BIT(6)); + + ret = tc358768_clear_error(priv); + if (ret) { + dev_err(priv->dev, "Bridge enable failed: %d\n", ret); + tc358768_bridge_disable(bridge); + tc358768_bridge_post_disable(bridge); + } +} + +static const struct drm_bridge_funcs tc358768_bridge_funcs = { + .attach = tc358768_bridge_attach, + .mode_valid = tc358768_bridge_mode_valid, + .pre_enable = tc358768_bridge_pre_enable, + .enable = tc358768_bridge_enable, + .disable = tc358768_bridge_disable, + .post_disable = tc358768_bridge_post_disable, +}; + +static const struct drm_bridge_timings default_tc358768_timings = { + .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE + | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE + | DRM_BUS_FLAG_DE_HIGH, +}; + +static bool tc358768_is_reserved_reg(unsigned int reg) +{ + switch (reg) { + case 0x114 ... 0x13f: + case 0x200: + case 0x20c: + case 0x400 ... 0x408: + case 0x41c ... 0x42f: + return true; + default: + return false; + } +} + +static bool tc358768_writeable_reg(struct device *dev, unsigned int reg) +{ + if (tc358768_is_reserved_reg(reg)) + return false; + + switch (reg) { + case TC358768_CHIPID: + case TC358768_FIFOSTATUS: + case TC358768_DSITXSTATUS ... (TC358768_DSITXSTATUS + 2): + case TC358768_DSI_CONTROL ... (TC358768_DSI_INT_ENA + 2): + case TC358768_DSICMD_RDFIFO ... (TC358768_DSI_ERR_HALT + 2): + return false; + default: + return true; + } +} + +static bool tc358768_readable_reg(struct device *dev, unsigned int reg) +{ + if (tc358768_is_reserved_reg(reg)) + return false; + + switch (reg) { + case TC358768_STARTCNTRL: + case TC358768_DSI_CONFW ... (TC358768_DSI_CONFW + 2): + case TC358768_DSI_INT_CLR ... (TC358768_DSI_INT_CLR + 2): + case TC358768_DSI_START ... (TC358768_DSI_START + 2): + case TC358768_DBG_DATA: + return false; + default: + return true; + } +} + +static const struct regmap_config tc358768_regmap_config = { + .name = "tc358768", + .reg_bits = 16, + .val_bits = 16, + .max_register = TC358768_DSI_HACT, + .cache_type = REGCACHE_NONE, + .writeable_reg = tc358768_writeable_reg, + .readable_reg = tc358768_readable_reg, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static const struct i2c_device_id tc358768_i2c_ids[] = { + { "tc358768", 0 }, + { "tc358778", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tc358768_i2c_ids); + +static const struct of_device_id tc358768_of_ids[] = { + { .compatible = "toshiba,tc358768", }, + { .compatible = "toshiba,tc358778", }, + { } +}; +MODULE_DEVICE_TABLE(of, tc358768_of_ids); + +static int tc358768_get_regulators(struct tc358768_priv *priv) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(priv->supplies); ++i) + priv->supplies[i].supply = tc358768_supplies[i]; + + ret = devm_regulator_bulk_get(priv->dev, ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret < 0) + dev_err(priv->dev, "failed to get regulators: %d\n", ret); + + return ret; +} + +static int tc358768_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tc358768_priv *priv; + struct device *dev = &client->dev; + struct device_node *np = dev->of_node; + int ret; + + if (!np) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + priv->dev = dev; + + ret = tc358768_get_regulators(priv); + if (ret) + return ret; + + priv->refclk = devm_clk_get(dev, "refclk"); + if (IS_ERR(priv->refclk)) + return PTR_ERR(priv->refclk); + + /* + * RESX is low active, to disable tc358768 initially (keep in reset) + * the gpio line must be LOW. This is the ASSERTED state of + * GPIO_ACTIVE_LOW (GPIOD_OUT_HIGH == ASSERTED). + */ + priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset_gpio)) + return PTR_ERR(priv->reset_gpio); + + priv->regmap = devm_regmap_init_i2c(client, &tc358768_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(dev, "Failed to init regmap\n"); + return PTR_ERR(priv->regmap); + } + + priv->dsi_host.dev = dev; + priv->dsi_host.ops = &tc358768_dsi_host_ops; + + priv->bridge.funcs = &tc358768_bridge_funcs; + priv->bridge.timings = &default_tc358768_timings; + priv->bridge.of_node = np; + + i2c_set_clientdata(client, priv); + + return mipi_dsi_host_register(&priv->dsi_host); +} + +static int tc358768_i2c_remove(struct i2c_client *client) +{ + struct tc358768_priv *priv = i2c_get_clientdata(client); + + mipi_dsi_host_unregister(&priv->dsi_host); + + return 0; +} + +static struct i2c_driver tc358768_driver = { + .driver = { + .name = "tc358768", + .of_match_table = tc358768_of_ids, + }, + .id_table = tc358768_i2c_ids, + .probe = tc358768_i2c_probe, + .remove = tc358768_i2c_remove, +}; +module_i2c_driver(tc358768_driver); + +MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); +MODULE_DESCRIPTION("TC358768AXBG/TC358778XBG DSI bridge"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index 9a2dd986afa5..6ce60a64b603 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -51,6 +51,7 @@ #define SN_ENH_FRAME_REG 0x5A #define VSTREAM_ENABLE BIT(3) #define SN_DATA_FORMAT_REG 0x5B +#define BPP_18_RGB BIT(0) #define SN_HPD_DISABLE_REG 0x5C #define HPD_DISABLE BIT(0) #define SN_AUX_WDATA_REG(x) (0x64 + (x)) @@ -100,6 +101,7 @@ struct ti_sn_bridge { struct drm_panel *panel; struct gpio_desc *enable_gpio; struct regulator_bulk_data supplies[SN_REGULATOR_SUPPLY_NUM]; + int dp_lanes; }; static const struct regmap_range ti_sn_bridge_volatile_ranges[] = { @@ -312,7 +314,7 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge) goto err_dsi_host; } - /* TODO: setting to 4 lanes always for now */ + /* TODO: setting to 4 MIPI lanes always for now */ dsi->lanes = 4; dsi->format = MIPI_DSI_FMT_RGB888; dsi->mode_flags = MIPI_DSI_MODE_VIDEO; @@ -417,6 +419,32 @@ static void ti_sn_bridge_set_refclk_freq(struct ti_sn_bridge *pdata) REFCLK_FREQ(i)); } +static void ti_sn_bridge_set_dsi_rate(struct ti_sn_bridge *pdata) +{ + unsigned int bit_rate_mhz, clk_freq_mhz; + unsigned int val; + struct drm_display_mode *mode = + &pdata->bridge.encoder->crtc->state->adjusted_mode; + + /* set DSIA clk frequency */ + bit_rate_mhz = (mode->clock / 1000) * + mipi_dsi_pixel_format_to_bpp(pdata->dsi->format); + clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2); + + /* for each increment in val, frequency increases by 5MHz */ + val = (MIN_DSI_CLK_FREQ_MHZ / 5) + + (((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 5) & 0xFF); + regmap_write(pdata->regmap, SN_DSIA_CLK_FREQ_REG, val); +} + +static unsigned int ti_sn_bridge_get_bpp(struct ti_sn_bridge *pdata) +{ + if (pdata->connector.display_info.bpc <= 6) + return 18; + else + return 24; +} + /** * LUT index corresponds to register value and * LUT values corresponds to dp data rate supported @@ -426,32 +454,106 @@ static const unsigned int ti_sn_bridge_dp_rate_lut[] = { 0, 1620, 2160, 2430, 2700, 3240, 4320, 5400 }; -static void ti_sn_bridge_set_dsi_dp_rate(struct ti_sn_bridge *pdata) +static int ti_sn_bridge_calc_min_dp_rate_idx(struct ti_sn_bridge *pdata) { - unsigned int bit_rate_mhz, clk_freq_mhz, dp_rate_mhz; - unsigned int val, i; + unsigned int bit_rate_khz, dp_rate_mhz; + unsigned int i; struct drm_display_mode *mode = &pdata->bridge.encoder->crtc->state->adjusted_mode; - /* set DSIA clk frequency */ - bit_rate_mhz = (mode->clock / 1000) * - mipi_dsi_pixel_format_to_bpp(pdata->dsi->format); - clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2); + /* Calculate minimum bit rate based on our pixel clock. */ + bit_rate_khz = mode->clock * ti_sn_bridge_get_bpp(pdata); - /* for each increment in val, frequency increases by 5MHz */ - val = (MIN_DSI_CLK_FREQ_MHZ / 5) + - (((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 5) & 0xFF); - regmap_write(pdata->regmap, SN_DSIA_CLK_FREQ_REG, val); + /* Calculate minimum DP data rate, taking 80% as per DP spec */ + dp_rate_mhz = DIV_ROUND_UP(bit_rate_khz * DP_CLK_FUDGE_NUM, + 1000 * pdata->dp_lanes * DP_CLK_FUDGE_DEN); - /* set DP data rate */ - dp_rate_mhz = ((bit_rate_mhz / pdata->dsi->lanes) * DP_CLK_FUDGE_NUM) / - DP_CLK_FUDGE_DEN; - for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut) - 1; i++) + for (i = 1; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut) - 1; i++) if (ti_sn_bridge_dp_rate_lut[i] > dp_rate_mhz) break; - regmap_update_bits(pdata->regmap, SN_DATARATE_CONFIG_REG, - DP_DATARATE_MASK, DP_DATARATE(i)); + return i; +} + +static void ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata, + bool rate_valid[]) +{ + unsigned int rate_per_200khz; + unsigned int rate_mhz; + u8 dpcd_val; + int ret; + int i, j; + + ret = drm_dp_dpcd_readb(&pdata->aux, DP_EDP_DPCD_REV, &dpcd_val); + if (ret != 1) { + DRM_DEV_ERROR(pdata->dev, + "Can't read eDP rev (%d), assuming 1.1\n", ret); + dpcd_val = DP_EDP_11; + } + + if (dpcd_val >= DP_EDP_14) { + /* eDP 1.4 devices must provide a custom table */ + __le16 sink_rates[DP_MAX_SUPPORTED_RATES]; + + ret = drm_dp_dpcd_read(&pdata->aux, DP_SUPPORTED_LINK_RATES, + sink_rates, sizeof(sink_rates)); + + if (ret != sizeof(sink_rates)) { + DRM_DEV_ERROR(pdata->dev, + "Can't read supported rate table (%d)\n", ret); + + /* By zeroing we'll fall back to DP_MAX_LINK_RATE. */ + memset(sink_rates, 0, sizeof(sink_rates)); + } + + for (i = 0; i < ARRAY_SIZE(sink_rates); i++) { + rate_per_200khz = le16_to_cpu(sink_rates[i]); + + if (!rate_per_200khz) + break; + + rate_mhz = rate_per_200khz * 200 / 1000; + for (j = 0; + j < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); + j++) { + if (ti_sn_bridge_dp_rate_lut[j] == rate_mhz) + rate_valid[j] = true; + } + } + + for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); i++) { + if (rate_valid[i]) + return; + } + DRM_DEV_ERROR(pdata->dev, + "No matching eDP rates in table; falling back\n"); + } + + /* On older versions best we can do is use DP_MAX_LINK_RATE */ + ret = drm_dp_dpcd_readb(&pdata->aux, DP_MAX_LINK_RATE, &dpcd_val); + if (ret != 1) { + DRM_DEV_ERROR(pdata->dev, + "Can't read max rate (%d); assuming 5.4 GHz\n", + ret); + dpcd_val = DP_LINK_BW_5_4; + } + + switch (dpcd_val) { + default: + DRM_DEV_ERROR(pdata->dev, + "Unexpected max rate (%#x); assuming 5.4 GHz\n", + (int)dpcd_val); + /* fall through */ + case DP_LINK_BW_5_4: + rate_valid[7] = 1; + /* fall through */ + case DP_LINK_BW_2_7: + rate_valid[4] = 1; + /* fall through */ + case DP_LINK_BW_1_62: + rate_valid[1] = 1; + break; + } } static void ti_sn_bridge_set_video_timings(struct ti_sn_bridge *pdata) @@ -493,24 +595,30 @@ static void ti_sn_bridge_set_video_timings(struct ti_sn_bridge *pdata) usleep_range(10000, 10500); /* 10ms delay recommended by spec */ } -static void ti_sn_bridge_enable(struct drm_bridge *bridge) +static unsigned int ti_sn_get_max_lanes(struct ti_sn_bridge *pdata) { - struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge); - unsigned int val; + u8 data; int ret; - /* DSI_A lane config */ - val = CHA_DSI_LANES(4 - pdata->dsi->lanes); - regmap_update_bits(pdata->regmap, SN_DSI_LANES_REG, - CHA_DSI_LANES_MASK, val); + ret = drm_dp_dpcd_readb(&pdata->aux, DP_MAX_LANE_COUNT, &data); + if (ret != 1) { + DRM_DEV_ERROR(pdata->dev, + "Can't read lane count (%d); assuming 4\n", ret); + return 4; + } - /* DP lane config */ - val = DP_NUM_LANES(pdata->dsi->lanes - 1); - regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK, - val); + return data & DP_LANE_COUNT_MASK; +} - /* set dsi/dp clk frequency value */ - ti_sn_bridge_set_dsi_dp_rate(pdata); +static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx, + const char **last_err_str) +{ + unsigned int val; + int ret; + + /* set dp clk frequency value */ + regmap_update_bits(pdata->regmap, SN_DATARATE_CONFIG_REG, + DP_DATARATE_MASK, DP_DATARATE(dp_rate_idx)); /* enable DP PLL */ regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 1); @@ -519,10 +627,62 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge) val & DPPLL_SRC_DP_PLL_LOCK, 1000, 50 * 1000); if (ret) { - DRM_ERROR("DP_PLL_LOCK polling failed (%d)\n", ret); - return; + *last_err_str = "DP_PLL_LOCK polling failed"; + goto exit; } + /* Semi auto link training mode */ + regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0x0A); + ret = regmap_read_poll_timeout(pdata->regmap, SN_ML_TX_MODE_REG, val, + val == ML_TX_MAIN_LINK_OFF || + val == ML_TX_NORMAL_MODE, 1000, + 500 * 1000); + if (ret) { + *last_err_str = "Training complete polling failed"; + } else if (val == ML_TX_MAIN_LINK_OFF) { + *last_err_str = "Link training failed, link is off"; + ret = -EIO; + } + +exit: + /* Disable the PLL if we failed */ + if (ret) + regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 0); + + return ret; +} + +static void ti_sn_bridge_enable(struct drm_bridge *bridge) +{ + struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge); + bool rate_valid[ARRAY_SIZE(ti_sn_bridge_dp_rate_lut)] = { }; + const char *last_err_str = "No supported DP rate"; + int dp_rate_idx; + unsigned int val; + int ret = -EINVAL; + + /* + * Run with the maximum number of lanes that the DP sink supports. + * + * Depending use cases, we might want to revisit this later because: + * - It's plausible that someone may have run fewer lines to the + * sink than the sink actually supports, assuming that the lines + * will just be driven at a higher rate. + * - The DP spec seems to indicate that it's more important to minimize + * the number of lanes than the link rate. + * + * If we do revisit, it would be important to measure the power impact. + */ + pdata->dp_lanes = ti_sn_get_max_lanes(pdata); + + /* DSI_A lane config */ + val = CHA_DSI_LANES(4 - pdata->dsi->lanes); + regmap_update_bits(pdata->regmap, SN_DSI_LANES_REG, + CHA_DSI_LANES_MASK, val); + + /* set dsi clk frequency value */ + ti_sn_bridge_set_dsi_rate(pdata); + /** * The SN65DSI86 only supports ASSR Display Authentication method and * this method is enabled by default. An eDP panel must support this @@ -532,17 +692,30 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge) drm_dp_dpcd_writeb(&pdata->aux, DP_EDP_CONFIGURATION_SET, DP_ALTERNATE_SCRAMBLER_RESET_ENABLE); - /* Semi auto link training mode */ - regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0x0A); - ret = regmap_read_poll_timeout(pdata->regmap, SN_ML_TX_MODE_REG, val, - val == ML_TX_MAIN_LINK_OFF || - val == ML_TX_NORMAL_MODE, 1000, - 500 * 1000); + /* Set the DP output format (18 bpp or 24 bpp) */ + val = (ti_sn_bridge_get_bpp(pdata) == 18) ? BPP_18_RGB : 0; + regmap_update_bits(pdata->regmap, SN_DATA_FORMAT_REG, BPP_18_RGB, val); + + /* DP lane config */ + val = DP_NUM_LANES(min(pdata->dp_lanes, 3)); + regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK, + val); + + ti_sn_bridge_read_valid_rates(pdata, rate_valid); + + /* Train until we run out of rates */ + for (dp_rate_idx = ti_sn_bridge_calc_min_dp_rate_idx(pdata); + dp_rate_idx < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); + dp_rate_idx++) { + if (!rate_valid[dp_rate_idx]) + continue; + + ret = ti_sn_link_training(pdata, dp_rate_idx, &last_err_str); + if (!ret) + break; + } if (ret) { - DRM_ERROR("Training complete polling failed (%d)\n", ret); - return; - } else if (val == ML_TX_MAIN_LINK_OFF) { - DRM_ERROR("Link training failed, link is off\n"); + DRM_DEV_ERROR(pdata->dev, "%s (%d)\n", last_err_str, ret); return; } diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c index 6f6d6d1e60ae..108e8cd7ab68 100644 --- a/drivers/gpu/drm/bridge/ti-tfp410.c +++ b/drivers/gpu/drm/bridge/ti-tfp410.c @@ -167,10 +167,23 @@ static void tfp410_disable(struct drm_bridge *bridge) gpiod_set_value_cansleep(dvi->powerdown, 1); } +static enum drm_mode_status tfp410_mode_valid(struct drm_bridge *bridge, + const struct drm_display_mode *mode) +{ + if (mode->clock < 25000) + return MODE_CLOCK_LOW; + + if (mode->clock > 165000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + static const struct drm_bridge_funcs tfp410_bridge_funcs = { .attach = tfp410_attach, .enable = tfp410_enable, .disable = tfp410_disable, + .mode_valid = tfp410_mode_valid, }; static void tfp410_hpd_work_func(struct work_struct *work) |