Hi,
Here is a set of patches to allow the phy framework consumers to test and apply runtime configurations.
This is needed to support more phy classes that require tuning based on parameters depending on the current use case of the device, in addition to the power state management already provided by the current functions.
A first test bed for that API are the MIPI D-PHY devices. There's a number of solutions that have been used so far to support these phy, most of the time being an ad-hoc driver in the consumer.
That approach has a big shortcoming though, which is that this is quite difficult to deal with consumers integrated with multiple variants of phy, of multiple consumers integrated with the same phy.
The latter case can be found in the Cadence DSI bridge, and the CSI transceiver and receivers. All of them are integrated with the same phy, or can be integrated with different phy, depending on the implementation.
I've looked at all the MIPI DSI drivers I could find, and gathered all the parameters I could find. The interface should be complete, and most of the drivers can be converted in the future. The current set converts two of them: the above mentionned Cadence DSI driver so that the v4l2 drivers can use them, and the Allwinner MIPI-DSI driver.
Let me know what you think, Maxime
Changes from v3 - Rebased on 5.0-rc1 - Added the fixes suggested by Sakari
Changes from v2: - Rebased on next - Changed the interface to accomodate for the new submodes - Changed the timings units from nanoseconds to picoseconds - Added minimum and maximum boundaries to the documentation - Moved the clock enabling to phy_power_on in the Cadence DPHY driver - Exported the phy_configure and phy_validate symbols - Rework the phy pll divider computation in the cadence dphy driver
Changes from v1: - Rebased on top of 4.20-rc1 - Removed the bus mode and timings parameters from the MIPI D-PHY parameters, since that shouldn't have any impact on the PHY itself. - Reworked the Cadence DSI and D-PHY drivers to take this into account. - Remove the mode parameter from phy_configure - Added phy_configure and phy_validate stubs - Return -EOPNOTSUPP in phy_configure and phy_validate when the operation is not implemented
Maxime Ripard (9): phy: dphy: Remove unused header phy: dphy: Change units of wakeup and init parameters phy: dphy: Clarify lanes parameter documentation sun6i: dsi: Convert to generic phy handling phy: Move Allwinner A31 D-PHY driver to drivers/phy/ drm/bridge: cdns: Separate DSI and D-PHY configuration dt-bindings: phy: Move the Cadence D-PHY bindings phy: Add Cadence D-PHY support drm/bridge: cdns: Convert to phy framework
Documentation/devicetree/bindings/display/bridge/cdns,dsi.txt | 21 +- Documentation/devicetree/bindings/phy/cdns,dphy.txt | 20 +- drivers/gpu/drm/bridge/Kconfig | 1 +- drivers/gpu/drm/bridge/cdns-dsi.c | 535 +------ drivers/gpu/drm/sun4i/Kconfig | 3 +- drivers/gpu/drm/sun4i/Makefile | 5 +- drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c | 292 +---- drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c | 31 +- drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h | 17 +- drivers/phy/allwinner/Kconfig | 12 +- drivers/phy/allwinner/Makefile | 1 +- drivers/phy/allwinner/phy-sun6i-mipi-dphy.c | 318 ++++- drivers/phy/cadence/Kconfig | 13 +- drivers/phy/cadence/Makefile | 1 +- drivers/phy/cadence/cdns-dphy.c | 389 +++++- drivers/phy/phy-core-mipi-dphy.c | 8 +- include/linux/phy/phy-mipi-dphy.h | 13 +- 17 files changed, 890 insertions(+), 790 deletions(-) create mode 100644 Documentation/devicetree/bindings/phy/cdns,dphy.txt delete mode 100644 drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c create mode 100644 drivers/phy/allwinner/phy-sun6i-mipi-dphy.c create mode 100644 drivers/phy/cadence/cdns-dphy.c
base-commit: bfeffd155283772bbe78c6a05dec7c0128ee500c
The videomode.h header inclusion is an artifact from the patches development, remove it.
Suggested-by: Sakari Ailus sakari.ailus@iki.fi Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com --- include/linux/phy/phy-mipi-dphy.h | 2 -- 1 file changed, 2 deletions(-)
diff --git a/include/linux/phy/phy-mipi-dphy.h b/include/linux/phy/phy-mipi-dphy.h index c08aacc0ac35..9cf97cd1d303 100644 --- a/include/linux/phy/phy-mipi-dphy.h +++ b/include/linux/phy/phy-mipi-dphy.h @@ -6,8 +6,6 @@ #ifndef __PHY_MIPI_DPHY_H_ #define __PHY_MIPI_DPHY_H_
-#include <video/videomode.h> - /** * struct phy_configure_opts_mipi_dphy - MIPI D-PHY configuration set *
The Init and wakeup D-PHY parameters are in the micro/milliseconds range, putting the values real close to the types limits if they were in picoseconds.
Move them to microseconds which should be better fit.
Suggested-by: Sakari Ailus sakari.ailus@iki.fi Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com --- drivers/phy/phy-core-mipi-dphy.c | 8 ++++---- include/linux/phy/phy-mipi-dphy.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/drivers/phy/phy-core-mipi-dphy.c b/drivers/phy/phy-core-mipi-dphy.c index 465fa1b91a5f..14e0551cd319 100644 --- a/drivers/phy/phy-core-mipi-dphy.c +++ b/drivers/phy/phy-core-mipi-dphy.c @@ -65,12 +65,12 @@ int phy_mipi_dphy_get_default_config(unsigned long pixel_clock, */ cfg->hs_trail = max(4 * 8 * ui, 60000 + 4 * 4 * ui);
- cfg->init = 100000000; + cfg->init = 100; cfg->lpx = 60000; cfg->ta_get = 5 * cfg->lpx; cfg->ta_go = 4 * cfg->lpx; cfg->ta_sure = 2 * cfg->lpx; - cfg->wakeup = 1000000000; + cfg->wakeup = 1000;
cfg->hs_clk_rate = hs_clk_rate; cfg->lanes = lanes; @@ -143,7 +143,7 @@ int phy_mipi_dphy_config_validate(struct phy_configure_opts_mipi_dphy *cfg) if (cfg->hs_trail < max(8 * ui, 60000 + 4 * ui)) return -EINVAL;
- if (cfg->init < 100000000) + if (cfg->init < 100) return -EINVAL;
if (cfg->lpx < 50000) @@ -158,7 +158,7 @@ int phy_mipi_dphy_config_validate(struct phy_configure_opts_mipi_dphy *cfg) if (cfg->ta_sure < cfg->lpx || cfg->ta_sure > (2 * cfg->lpx)) return -EINVAL;
- if (cfg->wakeup < 1000000000) + if (cfg->wakeup < 1000) return -EINVAL;
return 0; diff --git a/include/linux/phy/phy-mipi-dphy.h b/include/linux/phy/phy-mipi-dphy.h index 9cf97cd1d303..627d28080d3a 100644 --- a/include/linux/phy/phy-mipi-dphy.h +++ b/include/linux/phy/phy-mipi-dphy.h @@ -190,10 +190,10 @@ struct phy_configure_opts_mipi_dphy { /** * @init: * - * Time, in picoseconds for the initialization period to + * Time, in microseconds for the initialization period to * complete. * - * Minimum value: 100000000 ps + * Minimum value: 100 us */ unsigned int init;
@@ -244,11 +244,11 @@ struct phy_configure_opts_mipi_dphy { /** * @wakeup: * - * Time, in picoseconds, that a transmitter drives a Mark-1 + * Time, in microseconds, that a transmitter drives a Mark-1 * state prior to a Stop state in order to initiate an exit * from ULPS. * - * Minimum value: 1000000000 ps + * Minimum value: 1000 us */ unsigned int wakeup;
The lanes parameter is not solely about the number of lanes, but it also carries the fact that those are the first lanes in use during the transmission.
It was implicit so far, so make sure it's explicit now.
Suggested-by: Sakari Ailus sakari.ailus@iki.fi Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com --- include/linux/phy/phy-mipi-dphy.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/include/linux/phy/phy-mipi-dphy.h b/include/linux/phy/phy-mipi-dphy.h index 627d28080d3a..a877ffee845d 100644 --- a/include/linux/phy/phy-mipi-dphy.h +++ b/include/linux/phy/phy-mipi-dphy.h @@ -269,7 +269,8 @@ struct phy_configure_opts_mipi_dphy { /** * @lanes: * - * Number of active data lanes used for the transmissions. + * Number of active, consecutive, data lanes, starting from + * lane 0, used for the transmissions. */ unsigned char lanes; };
Now that we have everything in place in the PHY framework to deal in a generic way with MIPI D-PHY phys, let's convert our PHY driver and its associated DSI driver to that new API.
Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com --- drivers/gpu/drm/sun4i/Kconfig | 11 +- drivers/gpu/drm/sun4i/Makefile | 6 +- drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c | 164 ++++++++++++++----------- drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c | 31 ++--- drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h | 17 +--- 5 files changed, 126 insertions(+), 103 deletions(-)
diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index c2c042287c19..2b8db82c4bab 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -45,10 +45,19 @@ config DRM_SUN6I_DSI default MACH_SUN8I select CRC_CCITT select DRM_MIPI_DSI + select DRM_SUN6I_DPHY help Choose this option if you want have an Allwinner SoC with MIPI-DSI support. If M is selected the module will be called - sun6i-dsi + sun6i_mipi_dsi. + +config DRM_SUN6I_DPHY + tristate "Allwinner A31 MIPI D-PHY Support" + select GENERIC_PHY_MIPI_DPHY + help + Choose this option if you have an Allwinner SoC with + MIPI-DSI support. If M is selected, the module will be + called sun6i_mipi_dphy.
config DRM_SUN8I_DW_HDMI tristate "Support for Allwinner version of DesignWare HDMI" diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 0eb38ac8e86e..1e2320d824b5 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -24,9 +24,6 @@ sun4i-tcon-y += sun4i_lvds.o sun4i-tcon-y += sun4i_tcon.o sun4i-tcon-y += sun4i_rgb.o
-sun6i-dsi-y += sun6i_mipi_dphy.o -sun6i-dsi-y += sun6i_mipi_dsi.o - obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o obj-$(CONFIG_DRM_SUN4I) += sun4i-tcon.o obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o @@ -37,7 +34,8 @@ ifdef CONFIG_DRM_SUN4I_BACKEND obj-$(CONFIG_DRM_SUN4I) += sun4i-frontend.o endif obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o -obj-$(CONFIG_DRM_SUN6I_DSI) += sun6i-dsi.o +obj-$(CONFIG_DRM_SUN6I_DPHY) += sun6i_mipi_dphy.o +obj-$(CONFIG_DRM_SUN6I_DSI) += sun6i_mipi_dsi.o obj-$(CONFIG_DRM_SUN8I_DW_HDMI) += sun8i-drm-hdmi.o obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o obj-$(CONFIG_DRM_SUN8I_TCON_TOP) += sun8i_tcon_top.o diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c index e4d19431fa0e..79c8af5c7c1d 100644 --- a/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c @@ -8,11 +8,14 @@
#include <linux/bitops.h> #include <linux/clk.h> +#include <linux/module.h> #include <linux/of_address.h> +#include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/reset.h>
-#include "sun6i_mipi_dsi.h" +#include <linux/phy/phy.h> +#include <linux/phy/phy-mipi-dphy.h>
#define SUN6I_DPHY_GCTL_REG 0x00 #define SUN6I_DPHY_GCTL_LANE_NUM(n) ((((n) - 1) & 3) << 4) @@ -81,12 +84,46 @@
#define SUN6I_DPHY_DBG5_REG 0xf4
-int sun6i_dphy_init(struct sun6i_dphy *dphy, unsigned int lanes) +struct sun6i_dphy { + struct clk *bus_clk; + struct clk *mod_clk; + struct regmap *regs; + struct reset_control *reset; + + struct phy *phy; + struct phy_configure_opts_mipi_dphy config; +}; + +static int sun6i_dphy_init(struct phy *phy) { + struct sun6i_dphy *dphy = phy_get_drvdata(phy); + reset_control_deassert(dphy->reset); clk_prepare_enable(dphy->mod_clk); clk_set_rate_exclusive(dphy->mod_clk, 150000000);
+ return 0; +} + +static int sun6i_dphy_configure(struct phy *phy, union phy_configure_opts *opts) +{ + struct sun6i_dphy *dphy = phy_get_drvdata(phy); + int ret; + + ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy); + if (ret) + return ret; + + memcpy(&dphy->config, opts, sizeof(dphy->config)); + + return 0; +} + +static int sun6i_dphy_power_on(struct phy *phy) +{ + struct sun6i_dphy *dphy = phy_get_drvdata(phy); + u8 lanes_mask = GENMASK(dphy->config.lanes - 1, 0); + regmap_write(dphy->regs, SUN6I_DPHY_TX_CTL_REG, SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT);
@@ -111,16 +148,9 @@ int sun6i_dphy_init(struct sun6i_dphy *dphy, unsigned int lanes) SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(3));
regmap_write(dphy->regs, SUN6I_DPHY_GCTL_REG, - SUN6I_DPHY_GCTL_LANE_NUM(lanes) | + SUN6I_DPHY_GCTL_LANE_NUM(dphy->config.lanes) | SUN6I_DPHY_GCTL_EN);
- return 0; -} - -int sun6i_dphy_power_on(struct sun6i_dphy *dphy, unsigned int lanes) -{ - u8 lanes_mask = GENMASK(lanes - 1, 0); - regmap_write(dphy->regs, SUN6I_DPHY_ANA0_REG, SUN6I_DPHY_ANA0_REG_PWS | SUN6I_DPHY_ANA0_REG_DMPC | @@ -181,16 +211,20 @@ int sun6i_dphy_power_on(struct sun6i_dphy *dphy, unsigned int lanes) return 0; }
-int sun6i_dphy_power_off(struct sun6i_dphy *dphy) +static int sun6i_dphy_power_off(struct phy *phy) { + struct sun6i_dphy *dphy = phy_get_drvdata(phy); + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG, SUN6I_DPHY_ANA1_REG_VTTMODE, 0);
return 0; }
-int sun6i_dphy_exit(struct sun6i_dphy *dphy) +static int sun6i_dphy_exit(struct phy *phy) { + struct sun6i_dphy *dphy = phy_get_drvdata(phy); + clk_rate_exclusive_put(dphy->mod_clk); clk_disable_unprepare(dphy->mod_clk); reset_control_assert(dphy->reset); @@ -198,6 +232,15 @@ int sun6i_dphy_exit(struct sun6i_dphy *dphy) return 0; }
+ +static struct phy_ops sun6i_dphy_ops = { + .configure = sun6i_dphy_configure, + .power_on = sun6i_dphy_power_on, + .power_off = sun6i_dphy_power_off, + .init = sun6i_dphy_init, + .exit = sun6i_dphy_exit, +}; + static struct regmap_config sun6i_dphy_regmap_config = { .reg_bits = 32, .val_bits = 32, @@ -206,87 +249,70 @@ static struct regmap_config sun6i_dphy_regmap_config = { .name = "mipi-dphy", };
-static const struct of_device_id sun6i_dphy_of_table[] = { - { .compatible = "allwinner,sun6i-a31-mipi-dphy" }, - { } -}; - -int sun6i_dphy_probe(struct sun6i_dsi *dsi, struct device_node *node) +static int sun6i_dphy_probe(struct platform_device *pdev) { + struct phy_provider *phy_provider; struct sun6i_dphy *dphy; - struct resource res; + struct resource *res; void __iomem *regs; - int ret; - - if (!of_match_node(sun6i_dphy_of_table, node)) { - dev_err(dsi->dev, "Incompatible D-PHY\n"); - return -EINVAL; - }
- dphy = devm_kzalloc(dsi->dev, sizeof(*dphy), GFP_KERNEL); + dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL); if (!dphy) return -ENOMEM;
- ret = of_address_to_resource(node, 0, &res); - if (ret) { - dev_err(dsi->dev, "phy: Couldn't get our resources\n"); - return ret; - } - - regs = devm_ioremap_resource(dsi->dev, &res); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(regs)) { - dev_err(dsi->dev, "Couldn't map the DPHY encoder registers\n"); + dev_err(&pdev->dev, "Couldn't map the DPHY encoder registers\n"); return PTR_ERR(regs); }
- dphy->regs = devm_regmap_init_mmio(dsi->dev, regs, - &sun6i_dphy_regmap_config); + dphy->regs = devm_regmap_init_mmio_clk(&pdev->dev, "bus", + regs, &sun6i_dphy_regmap_config); if (IS_ERR(dphy->regs)) { - dev_err(dsi->dev, "Couldn't create the DPHY encoder regmap\n"); + dev_err(&pdev->dev, "Couldn't create the DPHY encoder regmap\n"); return PTR_ERR(dphy->regs); }
- dphy->reset = of_reset_control_get_shared(node, NULL); + dphy->reset = devm_reset_control_get_shared(&pdev->dev, NULL); if (IS_ERR(dphy->reset)) { - dev_err(dsi->dev, "Couldn't get our reset line\n"); + dev_err(&pdev->dev, "Couldn't get our reset line\n"); return PTR_ERR(dphy->reset); }
- dphy->bus_clk = of_clk_get_by_name(node, "bus"); - if (IS_ERR(dphy->bus_clk)) { - dev_err(dsi->dev, "Couldn't get the DPHY bus clock\n"); - ret = PTR_ERR(dphy->bus_clk); - goto err_free_reset; - } - regmap_mmio_attach_clk(dphy->regs, dphy->bus_clk); - - dphy->mod_clk = of_clk_get_by_name(node, "mod"); + dphy->mod_clk = devm_clk_get(&pdev->dev, "mod"); if (IS_ERR(dphy->mod_clk)) { - dev_err(dsi->dev, "Couldn't get the DPHY mod clock\n"); - ret = PTR_ERR(dphy->mod_clk); - goto err_free_bus; + dev_err(&pdev->dev, "Couldn't get the DPHY mod clock\n"); + return PTR_ERR(dphy->mod_clk); }
- dsi->dphy = dphy; + dphy->phy = devm_phy_create(&pdev->dev, NULL, &sun6i_dphy_ops); + if (IS_ERR(dphy->phy)) { + dev_err(&pdev->dev, "failed to create PHY\n"); + return PTR_ERR(dphy->phy); + }
- return 0; + phy_set_drvdata(dphy->phy, dphy); + phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
-err_free_bus: - regmap_mmio_detach_clk(dphy->regs); - clk_put(dphy->bus_clk); -err_free_reset: - reset_control_put(dphy->reset); - return ret; + return PTR_ERR_OR_ZERO(phy_provider); }
-int sun6i_dphy_remove(struct sun6i_dsi *dsi) -{ - struct sun6i_dphy *dphy = dsi->dphy; - - regmap_mmio_detach_clk(dphy->regs); - clk_put(dphy->mod_clk); - clk_put(dphy->bus_clk); - reset_control_put(dphy->reset); +static const struct of_device_id sun6i_dphy_of_table[] = { + { .compatible = "allwinner,sun6i-a31-mipi-dphy" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun6i_dphy_of_table); + +static struct platform_driver sun6i_dphy_platform_driver = { + .probe = sun6i_dphy_probe, + .driver = { + .name = "sun6i-mipi-dphy", + .of_match_table = sun6i_dphy_of_table, + }, +}; +module_platform_driver(sun6i_dphy_platform_driver);
- return 0; -} +MODULE_AUTHOR("Maxime Ripard maxime.ripard@bootlin"); +MODULE_DESCRIPTION("Allwinner A31 MIPI D-PHY Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c index e3b34a345546..7bbce7708265 100644 --- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c @@ -16,6 +16,7 @@ #include <linux/slab.h>
#include <linux/phy/phy.h> +#include <linux/phy/phy-mipi-dphy.h>
#include <drm/drmP.h> #include <drm/drm_atomic_helper.h> @@ -616,6 +617,8 @@ static void sun6i_dsi_encoder_enable(struct drm_encoder *encoder) struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; struct sun6i_dsi *dsi = encoder_to_sun6i_dsi(encoder); struct mipi_dsi_device *device = dsi->device; + union phy_configure_opts opts = { 0 }; + struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy; u16 delay;
DRM_DEBUG_DRIVER("Enabling DSI output\n"); @@ -634,8 +637,15 @@ static void sun6i_dsi_encoder_enable(struct drm_encoder *encoder) sun6i_dsi_setup_format(dsi, mode); sun6i_dsi_setup_timings(dsi, mode);
- sun6i_dphy_init(dsi->dphy, device->lanes); - sun6i_dphy_power_on(dsi->dphy, device->lanes); + phy_init(dsi->dphy); + + phy_mipi_dphy_get_default_config(mode->clock * 1000, + mipi_dsi_pixel_format_to_bpp(device->format), + device->lanes, cfg); + + phy_set_mode(dsi->dphy, PHY_MODE_MIPI_DPHY); + phy_configure(dsi->dphy, &opts); + phy_power_on(dsi->dphy);
if (!IS_ERR(dsi->panel)) drm_panel_prepare(dsi->panel); @@ -673,8 +683,8 @@ static void sun6i_dsi_encoder_disable(struct drm_encoder *encoder) drm_panel_unprepare(dsi->panel); }
- sun6i_dphy_power_off(dsi->dphy); - sun6i_dphy_exit(dsi->dphy); + phy_power_off(dsi->dphy); + phy_exit(dsi->dphy);
pm_runtime_put(dsi->dev); } @@ -967,7 +977,6 @@ static const struct component_ops sun6i_dsi_ops = { static int sun6i_dsi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *dphy_node; struct sun6i_dsi *dsi; struct resource *res; void __iomem *base; @@ -1013,10 +1022,8 @@ static int sun6i_dsi_probe(struct platform_device *pdev) */ clk_set_rate_exclusive(dsi->mod_clk, 297000000);
- dphy_node = of_parse_phandle(dev->of_node, "phys", 0); - ret = sun6i_dphy_probe(dsi, dphy_node); - of_node_put(dphy_node); - if (ret) { + dsi->dphy = devm_phy_get(dev, "dphy"); + if (IS_ERR(dsi->dphy)) { dev_err(dev, "Couldn't get the MIPI D-PHY\n"); goto err_unprotect_clk; } @@ -1026,7 +1033,7 @@ static int sun6i_dsi_probe(struct platform_device *pdev) ret = mipi_dsi_host_register(&dsi->host); if (ret) { dev_err(dev, "Couldn't register MIPI-DSI host\n"); - goto err_remove_phy; + goto err_pm_disable; }
ret = component_add(&pdev->dev, &sun6i_dsi_ops); @@ -1039,9 +1046,8 @@ static int sun6i_dsi_probe(struct platform_device *pdev)
err_remove_dsi_host: mipi_dsi_host_unregister(&dsi->host); -err_remove_phy: +err_pm_disable: pm_runtime_disable(dev); - sun6i_dphy_remove(dsi); err_unprotect_clk: clk_rate_exclusive_put(dsi->mod_clk); return ret; @@ -1055,7 +1061,6 @@ static int sun6i_dsi_remove(struct platform_device *pdev) component_del(&pdev->dev, &sun6i_dsi_ops); mipi_dsi_host_unregister(&dsi->host); pm_runtime_disable(dev); - sun6i_dphy_remove(dsi); clk_rate_exclusive_put(dsi->mod_clk);
return 0; diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h index dbbc5b3ecbda..a07090579f84 100644 --- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h @@ -13,13 +13,6 @@ #include <drm/drm_encoder.h> #include <drm/drm_mipi_dsi.h>
-struct sun6i_dphy { - struct clk *bus_clk; - struct clk *mod_clk; - struct regmap *regs; - struct reset_control *reset; -}; - struct sun6i_dsi { struct drm_connector connector; struct drm_encoder encoder; @@ -29,7 +22,7 @@ struct sun6i_dsi { struct clk *mod_clk; struct regmap *regs; struct reset_control *reset; - struct sun6i_dphy *dphy; + struct phy *dphy;
struct device *dev; struct sun4i_drv *drv; @@ -52,12 +45,4 @@ static inline struct sun6i_dsi *encoder_to_sun6i_dsi(const struct drm_encoder *e return container_of(encoder, struct sun6i_dsi, encoder); };
-int sun6i_dphy_probe(struct sun6i_dsi *dsi, struct device_node *node); -int sun6i_dphy_remove(struct sun6i_dsi *dsi); - -int sun6i_dphy_init(struct sun6i_dphy *dphy, unsigned int lanes); -int sun6i_dphy_power_on(struct sun6i_dphy *dphy, unsigned int lanes); -int sun6i_dphy_power_off(struct sun6i_dphy *dphy); -int sun6i_dphy_exit(struct sun6i_dphy *dphy); - #endif /* _SUN6I_MIPI_DSI_H_ */
Hi,
On Wed, 2019-01-09 at 10:33 +0100, Maxime Ripard wrote:
Now that we have everything in place in the PHY framework to deal in a generic way with MIPI D-PHY phys, let's convert our PHY driver and its associated DSI driver to that new API.
Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com
Reviewed-by: Paul Kocialkowski paul.kocialkowski@bootlin.com
Cheers,
Paul
drivers/gpu/drm/sun4i/Kconfig | 11 +- drivers/gpu/drm/sun4i/Makefile | 6 +- drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c | 164 ++++++++++++++----------- drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c | 31 ++--- drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h | 17 +--- 5 files changed, 126 insertions(+), 103 deletions(-)
diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index c2c042287c19..2b8db82c4bab 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -45,10 +45,19 @@ config DRM_SUN6I_DSI default MACH_SUN8I select CRC_CCITT select DRM_MIPI_DSI
- select DRM_SUN6I_DPHY help Choose this option if you want have an Allwinner SoC with MIPI-DSI support. If M is selected the module will be called
sun6i-dsi
sun6i_mipi_dsi.
+config DRM_SUN6I_DPHY
- tristate "Allwinner A31 MIPI D-PHY Support"
- select GENERIC_PHY_MIPI_DPHY
- help
Choose this option if you have an Allwinner SoC with
MIPI-DSI support. If M is selected, the module will be
called sun6i_mipi_dphy.
config DRM_SUN8I_DW_HDMI tristate "Support for Allwinner version of DesignWare HDMI" diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 0eb38ac8e86e..1e2320d824b5 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -24,9 +24,6 @@ sun4i-tcon-y += sun4i_lvds.o sun4i-tcon-y += sun4i_tcon.o sun4i-tcon-y += sun4i_rgb.o
-sun6i-dsi-y += sun6i_mipi_dphy.o -sun6i-dsi-y += sun6i_mipi_dsi.o
obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o obj-$(CONFIG_DRM_SUN4I) += sun4i-tcon.o obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o @@ -37,7 +34,8 @@ ifdef CONFIG_DRM_SUN4I_BACKEND obj-$(CONFIG_DRM_SUN4I) += sun4i-frontend.o endif obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o -obj-$(CONFIG_DRM_SUN6I_DSI) += sun6i-dsi.o +obj-$(CONFIG_DRM_SUN6I_DPHY) += sun6i_mipi_dphy.o +obj-$(CONFIG_DRM_SUN6I_DSI) += sun6i_mipi_dsi.o obj-$(CONFIG_DRM_SUN8I_DW_HDMI) += sun8i-drm-hdmi.o obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o obj-$(CONFIG_DRM_SUN8I_TCON_TOP) += sun8i_tcon_top.o diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c index e4d19431fa0e..79c8af5c7c1d 100644 --- a/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c @@ -8,11 +8,14 @@
#include <linux/bitops.h> #include <linux/clk.h> +#include <linux/module.h> #include <linux/of_address.h> +#include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/reset.h>
-#include "sun6i_mipi_dsi.h" +#include <linux/phy/phy.h> +#include <linux/phy/phy-mipi-dphy.h>
#define SUN6I_DPHY_GCTL_REG 0x00 #define SUN6I_DPHY_GCTL_LANE_NUM(n) ((((n) - 1) & 3) << 4) @@ -81,12 +84,46 @@
#define SUN6I_DPHY_DBG5_REG 0xf4
-int sun6i_dphy_init(struct sun6i_dphy *dphy, unsigned int lanes) +struct sun6i_dphy {
- struct clk *bus_clk;
- struct clk *mod_clk;
- struct regmap *regs;
- struct reset_control *reset;
- struct phy *phy;
- struct phy_configure_opts_mipi_dphy config;
+};
+static int sun6i_dphy_init(struct phy *phy) {
struct sun6i_dphy *dphy = phy_get_drvdata(phy);
reset_control_deassert(dphy->reset); clk_prepare_enable(dphy->mod_clk); clk_set_rate_exclusive(dphy->mod_clk, 150000000);
return 0;
+}
+static int sun6i_dphy_configure(struct phy *phy, union phy_configure_opts *opts) +{
- struct sun6i_dphy *dphy = phy_get_drvdata(phy);
- int ret;
- ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy);
- if (ret)
return ret;
- memcpy(&dphy->config, opts, sizeof(dphy->config));
- return 0;
+}
+static int sun6i_dphy_power_on(struct phy *phy) +{
- struct sun6i_dphy *dphy = phy_get_drvdata(phy);
- u8 lanes_mask = GENMASK(dphy->config.lanes - 1, 0);
- regmap_write(dphy->regs, SUN6I_DPHY_TX_CTL_REG, SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT);
@@ -111,16 +148,9 @@ int sun6i_dphy_init(struct sun6i_dphy *dphy, unsigned int lanes) SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(3));
regmap_write(dphy->regs, SUN6I_DPHY_GCTL_REG,
SUN6I_DPHY_GCTL_LANE_NUM(lanes) |
SUN6I_DPHY_GCTL_LANE_NUM(dphy->config.lanes) | SUN6I_DPHY_GCTL_EN);
- return 0;
-}
-int sun6i_dphy_power_on(struct sun6i_dphy *dphy, unsigned int lanes) -{
- u8 lanes_mask = GENMASK(lanes - 1, 0);
- regmap_write(dphy->regs, SUN6I_DPHY_ANA0_REG, SUN6I_DPHY_ANA0_REG_PWS | SUN6I_DPHY_ANA0_REG_DMPC |
@@ -181,16 +211,20 @@ int sun6i_dphy_power_on(struct sun6i_dphy *dphy, unsigned int lanes) return 0; }
-int sun6i_dphy_power_off(struct sun6i_dphy *dphy) +static int sun6i_dphy_power_off(struct phy *phy) {
struct sun6i_dphy *dphy = phy_get_drvdata(phy);
regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG, SUN6I_DPHY_ANA1_REG_VTTMODE, 0);
return 0;
}
-int sun6i_dphy_exit(struct sun6i_dphy *dphy) +static int sun6i_dphy_exit(struct phy *phy) {
- struct sun6i_dphy *dphy = phy_get_drvdata(phy);
- clk_rate_exclusive_put(dphy->mod_clk); clk_disable_unprepare(dphy->mod_clk); reset_control_assert(dphy->reset);
@@ -198,6 +232,15 @@ int sun6i_dphy_exit(struct sun6i_dphy *dphy) return 0; }
+static struct phy_ops sun6i_dphy_ops = {
- .configure = sun6i_dphy_configure,
- .power_on = sun6i_dphy_power_on,
- .power_off = sun6i_dphy_power_off,
- .init = sun6i_dphy_init,
- .exit = sun6i_dphy_exit,
+};
static struct regmap_config sun6i_dphy_regmap_config = { .reg_bits = 32, .val_bits = 32, @@ -206,87 +249,70 @@ static struct regmap_config sun6i_dphy_regmap_config = { .name = "mipi-dphy", };
-static const struct of_device_id sun6i_dphy_of_table[] = {
- { .compatible = "allwinner,sun6i-a31-mipi-dphy" },
- { }
-};
-int sun6i_dphy_probe(struct sun6i_dsi *dsi, struct device_node *node) +static int sun6i_dphy_probe(struct platform_device *pdev) {
- struct phy_provider *phy_provider; struct sun6i_dphy *dphy;
- struct resource res;
- struct resource *res; void __iomem *regs;
int ret;
if (!of_match_node(sun6i_dphy_of_table, node)) {
dev_err(dsi->dev, "Incompatible D-PHY\n");
return -EINVAL;
}
dphy = devm_kzalloc(dsi->dev, sizeof(*dphy), GFP_KERNEL);
- dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL); if (!dphy) return -ENOMEM;
- ret = of_address_to_resource(node, 0, &res);
- if (ret) {
dev_err(dsi->dev, "phy: Couldn't get our resources\n");
return ret;
- }
- regs = devm_ioremap_resource(dsi->dev, &res);
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(regs)) {
dev_err(dsi->dev, "Couldn't map the DPHY encoder registers\n");
return PTR_ERR(regs); }dev_err(&pdev->dev, "Couldn't map the DPHY encoder registers\n");
- dphy->regs = devm_regmap_init_mmio(dsi->dev, regs,
&sun6i_dphy_regmap_config);
- dphy->regs = devm_regmap_init_mmio_clk(&pdev->dev, "bus",
if (IS_ERR(dphy->regs)) {regs, &sun6i_dphy_regmap_config);
dev_err(dsi->dev, "Couldn't create the DPHY encoder regmap\n");
return PTR_ERR(dphy->regs); }dev_err(&pdev->dev, "Couldn't create the DPHY encoder regmap\n");
- dphy->reset = of_reset_control_get_shared(node, NULL);
- dphy->reset = devm_reset_control_get_shared(&pdev->dev, NULL); if (IS_ERR(dphy->reset)) {
dev_err(dsi->dev, "Couldn't get our reset line\n");
return PTR_ERR(dphy->reset); }dev_err(&pdev->dev, "Couldn't get our reset line\n");
- dphy->bus_clk = of_clk_get_by_name(node, "bus");
- if (IS_ERR(dphy->bus_clk)) {
dev_err(dsi->dev, "Couldn't get the DPHY bus clock\n");
ret = PTR_ERR(dphy->bus_clk);
goto err_free_reset;
- }
- regmap_mmio_attach_clk(dphy->regs, dphy->bus_clk);
- dphy->mod_clk = of_clk_get_by_name(node, "mod");
- dphy->mod_clk = devm_clk_get(&pdev->dev, "mod"); if (IS_ERR(dphy->mod_clk)) {
dev_err(dsi->dev, "Couldn't get the DPHY mod clock\n");
ret = PTR_ERR(dphy->mod_clk);
goto err_free_bus;
dev_err(&pdev->dev, "Couldn't get the DPHY mod clock\n");
}return PTR_ERR(dphy->mod_clk);
- dsi->dphy = dphy;
- dphy->phy = devm_phy_create(&pdev->dev, NULL, &sun6i_dphy_ops);
- if (IS_ERR(dphy->phy)) {
dev_err(&pdev->dev, "failed to create PHY\n");
return PTR_ERR(dphy->phy);
- }
- return 0;
- phy_set_drvdata(dphy->phy, dphy);
- phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
-err_free_bus:
- regmap_mmio_detach_clk(dphy->regs);
- clk_put(dphy->bus_clk);
-err_free_reset:
- reset_control_put(dphy->reset);
- return ret;
- return PTR_ERR_OR_ZERO(phy_provider);
}
-int sun6i_dphy_remove(struct sun6i_dsi *dsi) -{
- struct sun6i_dphy *dphy = dsi->dphy;
- regmap_mmio_detach_clk(dphy->regs);
- clk_put(dphy->mod_clk);
- clk_put(dphy->bus_clk);
- reset_control_put(dphy->reset);
+static const struct of_device_id sun6i_dphy_of_table[] = {
- { .compatible = "allwinner,sun6i-a31-mipi-dphy" },
- { }
+}; +MODULE_DEVICE_TABLE(of, sun6i_dphy_of_table);
+static struct platform_driver sun6i_dphy_platform_driver = {
- .probe = sun6i_dphy_probe,
- .driver = {
.name = "sun6i-mipi-dphy",
.of_match_table = sun6i_dphy_of_table,
- },
+}; +module_platform_driver(sun6i_dphy_platform_driver);
- return 0;
-} +MODULE_AUTHOR("Maxime Ripard maxime.ripard@bootlin"); +MODULE_DESCRIPTION("Allwinner A31 MIPI D-PHY Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c index e3b34a345546..7bbce7708265 100644 --- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c @@ -16,6 +16,7 @@ #include <linux/slab.h>
#include <linux/phy/phy.h> +#include <linux/phy/phy-mipi-dphy.h>
#include <drm/drmP.h> #include <drm/drm_atomic_helper.h> @@ -616,6 +617,8 @@ static void sun6i_dsi_encoder_enable(struct drm_encoder *encoder) struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; struct sun6i_dsi *dsi = encoder_to_sun6i_dsi(encoder); struct mipi_dsi_device *device = dsi->device;
union phy_configure_opts opts = { 0 };
struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy; u16 delay;
DRM_DEBUG_DRIVER("Enabling DSI output\n");
@@ -634,8 +637,15 @@ static void sun6i_dsi_encoder_enable(struct drm_encoder *encoder) sun6i_dsi_setup_format(dsi, mode); sun6i_dsi_setup_timings(dsi, mode);
- sun6i_dphy_init(dsi->dphy, device->lanes);
- sun6i_dphy_power_on(dsi->dphy, device->lanes);
phy_init(dsi->dphy);
phy_mipi_dphy_get_default_config(mode->clock * 1000,
mipi_dsi_pixel_format_to_bpp(device->format),
device->lanes, cfg);
phy_set_mode(dsi->dphy, PHY_MODE_MIPI_DPHY);
phy_configure(dsi->dphy, &opts);
phy_power_on(dsi->dphy);
if (!IS_ERR(dsi->panel)) drm_panel_prepare(dsi->panel);
@@ -673,8 +683,8 @@ static void sun6i_dsi_encoder_disable(struct drm_encoder *encoder) drm_panel_unprepare(dsi->panel); }
- sun6i_dphy_power_off(dsi->dphy);
- sun6i_dphy_exit(dsi->dphy);
phy_power_off(dsi->dphy);
phy_exit(dsi->dphy);
pm_runtime_put(dsi->dev);
} @@ -967,7 +977,6 @@ static const struct component_ops sun6i_dsi_ops = { static int sun6i_dsi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev;
- struct device_node *dphy_node; struct sun6i_dsi *dsi; struct resource *res; void __iomem *base;
@@ -1013,10 +1022,8 @@ static int sun6i_dsi_probe(struct platform_device *pdev) */ clk_set_rate_exclusive(dsi->mod_clk, 297000000);
- dphy_node = of_parse_phandle(dev->of_node, "phys", 0);
- ret = sun6i_dphy_probe(dsi, dphy_node);
- of_node_put(dphy_node);
- if (ret) {
- dsi->dphy = devm_phy_get(dev, "dphy");
- if (IS_ERR(dsi->dphy)) { dev_err(dev, "Couldn't get the MIPI D-PHY\n"); goto err_unprotect_clk; }
@@ -1026,7 +1033,7 @@ static int sun6i_dsi_probe(struct platform_device *pdev) ret = mipi_dsi_host_register(&dsi->host); if (ret) { dev_err(dev, "Couldn't register MIPI-DSI host\n");
goto err_remove_phy;
goto err_pm_disable;
}
ret = component_add(&pdev->dev, &sun6i_dsi_ops);
@@ -1039,9 +1046,8 @@ static int sun6i_dsi_probe(struct platform_device *pdev)
err_remove_dsi_host: mipi_dsi_host_unregister(&dsi->host); -err_remove_phy: +err_pm_disable: pm_runtime_disable(dev);
- sun6i_dphy_remove(dsi);
err_unprotect_clk: clk_rate_exclusive_put(dsi->mod_clk); return ret; @@ -1055,7 +1061,6 @@ static int sun6i_dsi_remove(struct platform_device *pdev) component_del(&pdev->dev, &sun6i_dsi_ops); mipi_dsi_host_unregister(&dsi->host); pm_runtime_disable(dev);
sun6i_dphy_remove(dsi); clk_rate_exclusive_put(dsi->mod_clk);
return 0;
diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h index dbbc5b3ecbda..a07090579f84 100644 --- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h @@ -13,13 +13,6 @@ #include <drm/drm_encoder.h> #include <drm/drm_mipi_dsi.h>
-struct sun6i_dphy {
- struct clk *bus_clk;
- struct clk *mod_clk;
- struct regmap *regs;
- struct reset_control *reset;
-};
struct sun6i_dsi { struct drm_connector connector; struct drm_encoder encoder; @@ -29,7 +22,7 @@ struct sun6i_dsi { struct clk *mod_clk; struct regmap *regs; struct reset_control *reset;
- struct sun6i_dphy *dphy;
struct phy *dphy;
struct device *dev; struct sun4i_drv *drv;
@@ -52,12 +45,4 @@ static inline struct sun6i_dsi *encoder_to_sun6i_dsi(const struct drm_encoder *e return container_of(encoder, struct sun6i_dsi, encoder); };
-int sun6i_dphy_probe(struct sun6i_dsi *dsi, struct device_node *node); -int sun6i_dphy_remove(struct sun6i_dsi *dsi);
-int sun6i_dphy_init(struct sun6i_dphy *dphy, unsigned int lanes); -int sun6i_dphy_power_on(struct sun6i_dphy *dphy, unsigned int lanes); -int sun6i_dphy_power_off(struct sun6i_dphy *dphy); -int sun6i_dphy_exit(struct sun6i_dphy *dphy);
#endif /* _SUN6I_MIPI_DSI_H_ */
Now that our MIPI D-PHY driver has been converted to the phy framework, let's move it into the drivers/phy directory.
Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com --- drivers/gpu/drm/sun4i/Kconfig | 10 +- drivers/gpu/drm/sun4i/Makefile | 1 +- drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c | 318 +--------------------- drivers/phy/allwinner/Kconfig | 12 +- drivers/phy/allwinner/Makefile | 1 +- drivers/phy/allwinner/phy-sun6i-mipi-dphy.c | 318 +++++++++++++++++++++- 6 files changed, 332 insertions(+), 328 deletions(-) delete mode 100644 drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c create mode 100644 drivers/phy/allwinner/phy-sun6i-mipi-dphy.c
diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index 2b8db82c4bab..1dbbc3a1b763 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -45,20 +45,12 @@ config DRM_SUN6I_DSI default MACH_SUN8I select CRC_CCITT select DRM_MIPI_DSI - select DRM_SUN6I_DPHY + select PHY_SUN6I_MIPI_DPHY help Choose this option if you want have an Allwinner SoC with MIPI-DSI support. If M is selected the module will be called sun6i_mipi_dsi.
-config DRM_SUN6I_DPHY - tristate "Allwinner A31 MIPI D-PHY Support" - select GENERIC_PHY_MIPI_DPHY - help - Choose this option if you have an Allwinner SoC with - MIPI-DSI support. If M is selected, the module will be - called sun6i_mipi_dphy. - config DRM_SUN8I_DW_HDMI tristate "Support for Allwinner version of DesignWare HDMI" depends on DRM_SUN4I diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 1e2320d824b5..0d04f2447b01 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -34,7 +34,6 @@ ifdef CONFIG_DRM_SUN4I_BACKEND obj-$(CONFIG_DRM_SUN4I) += sun4i-frontend.o endif obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o -obj-$(CONFIG_DRM_SUN6I_DPHY) += sun6i_mipi_dphy.o obj-$(CONFIG_DRM_SUN6I_DSI) += sun6i_mipi_dsi.o obj-$(CONFIG_DRM_SUN8I_DW_HDMI) += sun8i-drm-hdmi.o obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c deleted file mode 100644 index 79c8af5c7c1d..000000000000 --- a/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c +++ /dev/null @@ -1,318 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (c) 2016 Allwinnertech Co., Ltd. - * Copyright (C) 2017-2018 Bootlin - * - * Maxime Ripard maxime.ripard@free-electrons.com - */ - -#include <linux/bitops.h> -#include <linux/clk.h> -#include <linux/module.h> -#include <linux/of_address.h> -#include <linux/platform_device.h> -#include <linux/regmap.h> -#include <linux/reset.h> - -#include <linux/phy/phy.h> -#include <linux/phy/phy-mipi-dphy.h> - -#define SUN6I_DPHY_GCTL_REG 0x00 -#define SUN6I_DPHY_GCTL_LANE_NUM(n) ((((n) - 1) & 3) << 4) -#define SUN6I_DPHY_GCTL_EN BIT(0) - -#define SUN6I_DPHY_TX_CTL_REG 0x04 -#define SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT BIT(28) - -#define SUN6I_DPHY_TX_TIME0_REG 0x10 -#define SUN6I_DPHY_TX_TIME0_HS_TRAIL(n) (((n) & 0xff) << 24) -#define SUN6I_DPHY_TX_TIME0_HS_PREPARE(n) (((n) & 0xff) << 16) -#define SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(n) ((n) & 0xff) - -#define SUN6I_DPHY_TX_TIME1_REG 0x14 -#define SUN6I_DPHY_TX_TIME1_CLK_POST(n) (((n) & 0xff) << 24) -#define SUN6I_DPHY_TX_TIME1_CLK_PRE(n) (((n) & 0xff) << 16) -#define SUN6I_DPHY_TX_TIME1_CLK_ZERO(n) (((n) & 0xff) << 8) -#define SUN6I_DPHY_TX_TIME1_CLK_PREPARE(n) ((n) & 0xff) - -#define SUN6I_DPHY_TX_TIME2_REG 0x18 -#define SUN6I_DPHY_TX_TIME2_CLK_TRAIL(n) ((n) & 0xff) - -#define SUN6I_DPHY_TX_TIME3_REG 0x1c - -#define SUN6I_DPHY_TX_TIME4_REG 0x20 -#define SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(n) (((n) & 0xff) << 8) -#define SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(n) ((n) & 0xff) - -#define SUN6I_DPHY_ANA0_REG 0x4c -#define SUN6I_DPHY_ANA0_REG_PWS BIT(31) -#define SUN6I_DPHY_ANA0_REG_DMPC BIT(28) -#define SUN6I_DPHY_ANA0_REG_DMPD(n) (((n) & 0xf) << 24) -#define SUN6I_DPHY_ANA0_REG_SLV(n) (((n) & 7) << 12) -#define SUN6I_DPHY_ANA0_REG_DEN(n) (((n) & 0xf) << 8) - -#define SUN6I_DPHY_ANA1_REG 0x50 -#define SUN6I_DPHY_ANA1_REG_VTTMODE BIT(31) -#define SUN6I_DPHY_ANA1_REG_CSMPS(n) (((n) & 3) << 28) -#define SUN6I_DPHY_ANA1_REG_SVTT(n) (((n) & 0xf) << 24) - -#define SUN6I_DPHY_ANA2_REG 0x54 -#define SUN6I_DPHY_ANA2_EN_P2S_CPU(n) (((n) & 0xf) << 24) -#define SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK GENMASK(27, 24) -#define SUN6I_DPHY_ANA2_EN_CK_CPU BIT(4) -#define SUN6I_DPHY_ANA2_REG_ENIB BIT(1) - -#define SUN6I_DPHY_ANA3_REG 0x58 -#define SUN6I_DPHY_ANA3_EN_VTTD(n) (((n) & 0xf) << 28) -#define SUN6I_DPHY_ANA3_EN_VTTD_MASK GENMASK(31, 28) -#define SUN6I_DPHY_ANA3_EN_VTTC BIT(27) -#define SUN6I_DPHY_ANA3_EN_DIV BIT(26) -#define SUN6I_DPHY_ANA3_EN_LDOC BIT(25) -#define SUN6I_DPHY_ANA3_EN_LDOD BIT(24) -#define SUN6I_DPHY_ANA3_EN_LDOR BIT(18) - -#define SUN6I_DPHY_ANA4_REG 0x5c -#define SUN6I_DPHY_ANA4_REG_DMPLVC BIT(24) -#define SUN6I_DPHY_ANA4_REG_DMPLVD(n) (((n) & 0xf) << 20) -#define SUN6I_DPHY_ANA4_REG_CKDV(n) (((n) & 0x1f) << 12) -#define SUN6I_DPHY_ANA4_REG_TMSC(n) (((n) & 3) << 10) -#define SUN6I_DPHY_ANA4_REG_TMSD(n) (((n) & 3) << 8) -#define SUN6I_DPHY_ANA4_REG_TXDNSC(n) (((n) & 3) << 6) -#define SUN6I_DPHY_ANA4_REG_TXDNSD(n) (((n) & 3) << 4) -#define SUN6I_DPHY_ANA4_REG_TXPUSC(n) (((n) & 3) << 2) -#define SUN6I_DPHY_ANA4_REG_TXPUSD(n) ((n) & 3) - -#define SUN6I_DPHY_DBG5_REG 0xf4 - -struct sun6i_dphy { - struct clk *bus_clk; - struct clk *mod_clk; - struct regmap *regs; - struct reset_control *reset; - - struct phy *phy; - struct phy_configure_opts_mipi_dphy config; -}; - -static int sun6i_dphy_init(struct phy *phy) -{ - struct sun6i_dphy *dphy = phy_get_drvdata(phy); - - reset_control_deassert(dphy->reset); - clk_prepare_enable(dphy->mod_clk); - clk_set_rate_exclusive(dphy->mod_clk, 150000000); - - return 0; -} - -static int sun6i_dphy_configure(struct phy *phy, union phy_configure_opts *opts) -{ - struct sun6i_dphy *dphy = phy_get_drvdata(phy); - int ret; - - ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy); - if (ret) - return ret; - - memcpy(&dphy->config, opts, sizeof(dphy->config)); - - return 0; -} - -static int sun6i_dphy_power_on(struct phy *phy) -{ - struct sun6i_dphy *dphy = phy_get_drvdata(phy); - u8 lanes_mask = GENMASK(dphy->config.lanes - 1, 0); - - regmap_write(dphy->regs, SUN6I_DPHY_TX_CTL_REG, - SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT); - - regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME0_REG, - SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(14) | - SUN6I_DPHY_TX_TIME0_HS_PREPARE(6) | - SUN6I_DPHY_TX_TIME0_HS_TRAIL(10)); - - regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME1_REG, - SUN6I_DPHY_TX_TIME1_CLK_PREPARE(7) | - SUN6I_DPHY_TX_TIME1_CLK_ZERO(50) | - SUN6I_DPHY_TX_TIME1_CLK_PRE(3) | - SUN6I_DPHY_TX_TIME1_CLK_POST(10)); - - regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME2_REG, - SUN6I_DPHY_TX_TIME2_CLK_TRAIL(30)); - - regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME3_REG, 0); - - regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME4_REG, - SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(3) | - SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(3)); - - regmap_write(dphy->regs, SUN6I_DPHY_GCTL_REG, - SUN6I_DPHY_GCTL_LANE_NUM(dphy->config.lanes) | - SUN6I_DPHY_GCTL_EN); - - regmap_write(dphy->regs, SUN6I_DPHY_ANA0_REG, - SUN6I_DPHY_ANA0_REG_PWS | - SUN6I_DPHY_ANA0_REG_DMPC | - SUN6I_DPHY_ANA0_REG_SLV(7) | - SUN6I_DPHY_ANA0_REG_DMPD(lanes_mask) | - SUN6I_DPHY_ANA0_REG_DEN(lanes_mask)); - - regmap_write(dphy->regs, SUN6I_DPHY_ANA1_REG, - SUN6I_DPHY_ANA1_REG_CSMPS(1) | - SUN6I_DPHY_ANA1_REG_SVTT(7)); - - regmap_write(dphy->regs, SUN6I_DPHY_ANA4_REG, - SUN6I_DPHY_ANA4_REG_CKDV(1) | - SUN6I_DPHY_ANA4_REG_TMSC(1) | - SUN6I_DPHY_ANA4_REG_TMSD(1) | - SUN6I_DPHY_ANA4_REG_TXDNSC(1) | - SUN6I_DPHY_ANA4_REG_TXDNSD(1) | - SUN6I_DPHY_ANA4_REG_TXPUSC(1) | - SUN6I_DPHY_ANA4_REG_TXPUSD(1) | - SUN6I_DPHY_ANA4_REG_DMPLVC | - SUN6I_DPHY_ANA4_REG_DMPLVD(lanes_mask)); - - regmap_write(dphy->regs, SUN6I_DPHY_ANA2_REG, - SUN6I_DPHY_ANA2_REG_ENIB); - udelay(5); - - regmap_write(dphy->regs, SUN6I_DPHY_ANA3_REG, - SUN6I_DPHY_ANA3_EN_LDOR | - SUN6I_DPHY_ANA3_EN_LDOC | - SUN6I_DPHY_ANA3_EN_LDOD); - udelay(1); - - regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG, - SUN6I_DPHY_ANA3_EN_VTTC | - SUN6I_DPHY_ANA3_EN_VTTD_MASK, - SUN6I_DPHY_ANA3_EN_VTTC | - SUN6I_DPHY_ANA3_EN_VTTD(lanes_mask)); - udelay(1); - - regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG, - SUN6I_DPHY_ANA3_EN_DIV, - SUN6I_DPHY_ANA3_EN_DIV); - udelay(1); - - regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG, - SUN6I_DPHY_ANA2_EN_CK_CPU, - SUN6I_DPHY_ANA2_EN_CK_CPU); - udelay(1); - - regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG, - SUN6I_DPHY_ANA1_REG_VTTMODE, - SUN6I_DPHY_ANA1_REG_VTTMODE); - - regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG, - SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK, - SUN6I_DPHY_ANA2_EN_P2S_CPU(lanes_mask)); - - return 0; -} - -static int sun6i_dphy_power_off(struct phy *phy) -{ - struct sun6i_dphy *dphy = phy_get_drvdata(phy); - - regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG, - SUN6I_DPHY_ANA1_REG_VTTMODE, 0); - - return 0; -} - -static int sun6i_dphy_exit(struct phy *phy) -{ - struct sun6i_dphy *dphy = phy_get_drvdata(phy); - - clk_rate_exclusive_put(dphy->mod_clk); - clk_disable_unprepare(dphy->mod_clk); - reset_control_assert(dphy->reset); - - return 0; -} - - -static struct phy_ops sun6i_dphy_ops = { - .configure = sun6i_dphy_configure, - .power_on = sun6i_dphy_power_on, - .power_off = sun6i_dphy_power_off, - .init = sun6i_dphy_init, - .exit = sun6i_dphy_exit, -}; - -static struct regmap_config sun6i_dphy_regmap_config = { - .reg_bits = 32, - .val_bits = 32, - .reg_stride = 4, - .max_register = SUN6I_DPHY_DBG5_REG, - .name = "mipi-dphy", -}; - -static int sun6i_dphy_probe(struct platform_device *pdev) -{ - struct phy_provider *phy_provider; - struct sun6i_dphy *dphy; - struct resource *res; - void __iomem *regs; - - dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL); - if (!dphy) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - regs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(regs)) { - dev_err(&pdev->dev, "Couldn't map the DPHY encoder registers\n"); - return PTR_ERR(regs); - } - - dphy->regs = devm_regmap_init_mmio_clk(&pdev->dev, "bus", - regs, &sun6i_dphy_regmap_config); - if (IS_ERR(dphy->regs)) { - dev_err(&pdev->dev, "Couldn't create the DPHY encoder regmap\n"); - return PTR_ERR(dphy->regs); - } - - dphy->reset = devm_reset_control_get_shared(&pdev->dev, NULL); - if (IS_ERR(dphy->reset)) { - dev_err(&pdev->dev, "Couldn't get our reset line\n"); - return PTR_ERR(dphy->reset); - } - - dphy->mod_clk = devm_clk_get(&pdev->dev, "mod"); - if (IS_ERR(dphy->mod_clk)) { - dev_err(&pdev->dev, "Couldn't get the DPHY mod clock\n"); - return PTR_ERR(dphy->mod_clk); - } - - dphy->phy = devm_phy_create(&pdev->dev, NULL, &sun6i_dphy_ops); - if (IS_ERR(dphy->phy)) { - dev_err(&pdev->dev, "failed to create PHY\n"); - return PTR_ERR(dphy->phy); - } - - phy_set_drvdata(dphy->phy, dphy); - phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); - - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id sun6i_dphy_of_table[] = { - { .compatible = "allwinner,sun6i-a31-mipi-dphy" }, - { } -}; -MODULE_DEVICE_TABLE(of, sun6i_dphy_of_table); - -static struct platform_driver sun6i_dphy_platform_driver = { - .probe = sun6i_dphy_probe, - .driver = { - .name = "sun6i-mipi-dphy", - .of_match_table = sun6i_dphy_of_table, - }, -}; -module_platform_driver(sun6i_dphy_platform_driver); - -MODULE_AUTHOR("Maxime Ripard maxime.ripard@bootlin"); -MODULE_DESCRIPTION("Allwinner A31 MIPI D-PHY Driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/phy/allwinner/Kconfig b/drivers/phy/allwinner/Kconfig index cdc1e745ba47..fb1204bcc454 100644 --- a/drivers/phy/allwinner/Kconfig +++ b/drivers/phy/allwinner/Kconfig @@ -17,6 +17,18 @@ config PHY_SUN4I_USB This driver controls the entire USB PHY block, both the USB OTG parts, as well as the 2 regular USB 2 host PHYs.
+config PHY_SUN6I_MIPI_DPHY + tristate "Allwinner A31 MIPI D-PHY Support" + depends on ARCH_SUNXI && HAS_IOMEM && OF + depends on RESET_CONTROLLER + select GENERIC_PHY + select GENERIC_PHY_MIPI_DPHY + select REGMAP_MMIO + help + Choose this option if you have an Allwinner SoC with + MIPI-DSI support. If M is selected, the module will be + called sun6i_mipi_dphy. + config PHY_SUN9I_USB tristate "Allwinner sun9i SoC USB PHY driver" depends on ARCH_SUNXI && HAS_IOMEM && OF diff --git a/drivers/phy/allwinner/Makefile b/drivers/phy/allwinner/Makefile index 8605529c01a1..7d0053efbfaa 100644 --- a/drivers/phy/allwinner/Makefile +++ b/drivers/phy/allwinner/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o +obj-$(CONFIG_PHY_SUN6I_MIPI_DPHY) += phy-sun6i-mipi-dphy.o obj-$(CONFIG_PHY_SUN9I_USB) += phy-sun9i-usb.o diff --git a/drivers/phy/allwinner/phy-sun6i-mipi-dphy.c b/drivers/phy/allwinner/phy-sun6i-mipi-dphy.c new file mode 100644 index 000000000000..79c8af5c7c1d --- /dev/null +++ b/drivers/phy/allwinner/phy-sun6i-mipi-dphy.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2016 Allwinnertech Co., Ltd. + * Copyright (C) 2017-2018 Bootlin + * + * Maxime Ripard maxime.ripard@free-electrons.com + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include <linux/phy/phy.h> +#include <linux/phy/phy-mipi-dphy.h> + +#define SUN6I_DPHY_GCTL_REG 0x00 +#define SUN6I_DPHY_GCTL_LANE_NUM(n) ((((n) - 1) & 3) << 4) +#define SUN6I_DPHY_GCTL_EN BIT(0) + +#define SUN6I_DPHY_TX_CTL_REG 0x04 +#define SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT BIT(28) + +#define SUN6I_DPHY_TX_TIME0_REG 0x10 +#define SUN6I_DPHY_TX_TIME0_HS_TRAIL(n) (((n) & 0xff) << 24) +#define SUN6I_DPHY_TX_TIME0_HS_PREPARE(n) (((n) & 0xff) << 16) +#define SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(n) ((n) & 0xff) + +#define SUN6I_DPHY_TX_TIME1_REG 0x14 +#define SUN6I_DPHY_TX_TIME1_CLK_POST(n) (((n) & 0xff) << 24) +#define SUN6I_DPHY_TX_TIME1_CLK_PRE(n) (((n) & 0xff) << 16) +#define SUN6I_DPHY_TX_TIME1_CLK_ZERO(n) (((n) & 0xff) << 8) +#define SUN6I_DPHY_TX_TIME1_CLK_PREPARE(n) ((n) & 0xff) + +#define SUN6I_DPHY_TX_TIME2_REG 0x18 +#define SUN6I_DPHY_TX_TIME2_CLK_TRAIL(n) ((n) & 0xff) + +#define SUN6I_DPHY_TX_TIME3_REG 0x1c + +#define SUN6I_DPHY_TX_TIME4_REG 0x20 +#define SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(n) (((n) & 0xff) << 8) +#define SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(n) ((n) & 0xff) + +#define SUN6I_DPHY_ANA0_REG 0x4c +#define SUN6I_DPHY_ANA0_REG_PWS BIT(31) +#define SUN6I_DPHY_ANA0_REG_DMPC BIT(28) +#define SUN6I_DPHY_ANA0_REG_DMPD(n) (((n) & 0xf) << 24) +#define SUN6I_DPHY_ANA0_REG_SLV(n) (((n) & 7) << 12) +#define SUN6I_DPHY_ANA0_REG_DEN(n) (((n) & 0xf) << 8) + +#define SUN6I_DPHY_ANA1_REG 0x50 +#define SUN6I_DPHY_ANA1_REG_VTTMODE BIT(31) +#define SUN6I_DPHY_ANA1_REG_CSMPS(n) (((n) & 3) << 28) +#define SUN6I_DPHY_ANA1_REG_SVTT(n) (((n) & 0xf) << 24) + +#define SUN6I_DPHY_ANA2_REG 0x54 +#define SUN6I_DPHY_ANA2_EN_P2S_CPU(n) (((n) & 0xf) << 24) +#define SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK GENMASK(27, 24) +#define SUN6I_DPHY_ANA2_EN_CK_CPU BIT(4) +#define SUN6I_DPHY_ANA2_REG_ENIB BIT(1) + +#define SUN6I_DPHY_ANA3_REG 0x58 +#define SUN6I_DPHY_ANA3_EN_VTTD(n) (((n) & 0xf) << 28) +#define SUN6I_DPHY_ANA3_EN_VTTD_MASK GENMASK(31, 28) +#define SUN6I_DPHY_ANA3_EN_VTTC BIT(27) +#define SUN6I_DPHY_ANA3_EN_DIV BIT(26) +#define SUN6I_DPHY_ANA3_EN_LDOC BIT(25) +#define SUN6I_DPHY_ANA3_EN_LDOD BIT(24) +#define SUN6I_DPHY_ANA3_EN_LDOR BIT(18) + +#define SUN6I_DPHY_ANA4_REG 0x5c +#define SUN6I_DPHY_ANA4_REG_DMPLVC BIT(24) +#define SUN6I_DPHY_ANA4_REG_DMPLVD(n) (((n) & 0xf) << 20) +#define SUN6I_DPHY_ANA4_REG_CKDV(n) (((n) & 0x1f) << 12) +#define SUN6I_DPHY_ANA4_REG_TMSC(n) (((n) & 3) << 10) +#define SUN6I_DPHY_ANA4_REG_TMSD(n) (((n) & 3) << 8) +#define SUN6I_DPHY_ANA4_REG_TXDNSC(n) (((n) & 3) << 6) +#define SUN6I_DPHY_ANA4_REG_TXDNSD(n) (((n) & 3) << 4) +#define SUN6I_DPHY_ANA4_REG_TXPUSC(n) (((n) & 3) << 2) +#define SUN6I_DPHY_ANA4_REG_TXPUSD(n) ((n) & 3) + +#define SUN6I_DPHY_DBG5_REG 0xf4 + +struct sun6i_dphy { + struct clk *bus_clk; + struct clk *mod_clk; + struct regmap *regs; + struct reset_control *reset; + + struct phy *phy; + struct phy_configure_opts_mipi_dphy config; +}; + +static int sun6i_dphy_init(struct phy *phy) +{ + struct sun6i_dphy *dphy = phy_get_drvdata(phy); + + reset_control_deassert(dphy->reset); + clk_prepare_enable(dphy->mod_clk); + clk_set_rate_exclusive(dphy->mod_clk, 150000000); + + return 0; +} + +static int sun6i_dphy_configure(struct phy *phy, union phy_configure_opts *opts) +{ + struct sun6i_dphy *dphy = phy_get_drvdata(phy); + int ret; + + ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy); + if (ret) + return ret; + + memcpy(&dphy->config, opts, sizeof(dphy->config)); + + return 0; +} + +static int sun6i_dphy_power_on(struct phy *phy) +{ + struct sun6i_dphy *dphy = phy_get_drvdata(phy); + u8 lanes_mask = GENMASK(dphy->config.lanes - 1, 0); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_CTL_REG, + SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME0_REG, + SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(14) | + SUN6I_DPHY_TX_TIME0_HS_PREPARE(6) | + SUN6I_DPHY_TX_TIME0_HS_TRAIL(10)); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME1_REG, + SUN6I_DPHY_TX_TIME1_CLK_PREPARE(7) | + SUN6I_DPHY_TX_TIME1_CLK_ZERO(50) | + SUN6I_DPHY_TX_TIME1_CLK_PRE(3) | + SUN6I_DPHY_TX_TIME1_CLK_POST(10)); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME2_REG, + SUN6I_DPHY_TX_TIME2_CLK_TRAIL(30)); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME3_REG, 0); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME4_REG, + SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(3) | + SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(3)); + + regmap_write(dphy->regs, SUN6I_DPHY_GCTL_REG, + SUN6I_DPHY_GCTL_LANE_NUM(dphy->config.lanes) | + SUN6I_DPHY_GCTL_EN); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA0_REG, + SUN6I_DPHY_ANA0_REG_PWS | + SUN6I_DPHY_ANA0_REG_DMPC | + SUN6I_DPHY_ANA0_REG_SLV(7) | + SUN6I_DPHY_ANA0_REG_DMPD(lanes_mask) | + SUN6I_DPHY_ANA0_REG_DEN(lanes_mask)); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA1_REG, + SUN6I_DPHY_ANA1_REG_CSMPS(1) | + SUN6I_DPHY_ANA1_REG_SVTT(7)); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA4_REG, + SUN6I_DPHY_ANA4_REG_CKDV(1) | + SUN6I_DPHY_ANA4_REG_TMSC(1) | + SUN6I_DPHY_ANA4_REG_TMSD(1) | + SUN6I_DPHY_ANA4_REG_TXDNSC(1) | + SUN6I_DPHY_ANA4_REG_TXDNSD(1) | + SUN6I_DPHY_ANA4_REG_TXPUSC(1) | + SUN6I_DPHY_ANA4_REG_TXPUSD(1) | + SUN6I_DPHY_ANA4_REG_DMPLVC | + SUN6I_DPHY_ANA4_REG_DMPLVD(lanes_mask)); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA2_REG, + SUN6I_DPHY_ANA2_REG_ENIB); + udelay(5); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA3_REG, + SUN6I_DPHY_ANA3_EN_LDOR | + SUN6I_DPHY_ANA3_EN_LDOC | + SUN6I_DPHY_ANA3_EN_LDOD); + udelay(1); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG, + SUN6I_DPHY_ANA3_EN_VTTC | + SUN6I_DPHY_ANA3_EN_VTTD_MASK, + SUN6I_DPHY_ANA3_EN_VTTC | + SUN6I_DPHY_ANA3_EN_VTTD(lanes_mask)); + udelay(1); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG, + SUN6I_DPHY_ANA3_EN_DIV, + SUN6I_DPHY_ANA3_EN_DIV); + udelay(1); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG, + SUN6I_DPHY_ANA2_EN_CK_CPU, + SUN6I_DPHY_ANA2_EN_CK_CPU); + udelay(1); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG, + SUN6I_DPHY_ANA1_REG_VTTMODE, + SUN6I_DPHY_ANA1_REG_VTTMODE); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG, + SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK, + SUN6I_DPHY_ANA2_EN_P2S_CPU(lanes_mask)); + + return 0; +} + +static int sun6i_dphy_power_off(struct phy *phy) +{ + struct sun6i_dphy *dphy = phy_get_drvdata(phy); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG, + SUN6I_DPHY_ANA1_REG_VTTMODE, 0); + + return 0; +} + +static int sun6i_dphy_exit(struct phy *phy) +{ + struct sun6i_dphy *dphy = phy_get_drvdata(phy); + + clk_rate_exclusive_put(dphy->mod_clk); + clk_disable_unprepare(dphy->mod_clk); + reset_control_assert(dphy->reset); + + return 0; +} + + +static struct phy_ops sun6i_dphy_ops = { + .configure = sun6i_dphy_configure, + .power_on = sun6i_dphy_power_on, + .power_off = sun6i_dphy_power_off, + .init = sun6i_dphy_init, + .exit = sun6i_dphy_exit, +}; + +static struct regmap_config sun6i_dphy_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = SUN6I_DPHY_DBG5_REG, + .name = "mipi-dphy", +}; + +static int sun6i_dphy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct sun6i_dphy *dphy; + struct resource *res; + void __iomem *regs; + + dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL); + if (!dphy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) { + dev_err(&pdev->dev, "Couldn't map the DPHY encoder registers\n"); + return PTR_ERR(regs); + } + + dphy->regs = devm_regmap_init_mmio_clk(&pdev->dev, "bus", + regs, &sun6i_dphy_regmap_config); + if (IS_ERR(dphy->regs)) { + dev_err(&pdev->dev, "Couldn't create the DPHY encoder regmap\n"); + return PTR_ERR(dphy->regs); + } + + dphy->reset = devm_reset_control_get_shared(&pdev->dev, NULL); + if (IS_ERR(dphy->reset)) { + dev_err(&pdev->dev, "Couldn't get our reset line\n"); + return PTR_ERR(dphy->reset); + } + + dphy->mod_clk = devm_clk_get(&pdev->dev, "mod"); + if (IS_ERR(dphy->mod_clk)) { + dev_err(&pdev->dev, "Couldn't get the DPHY mod clock\n"); + return PTR_ERR(dphy->mod_clk); + } + + dphy->phy = devm_phy_create(&pdev->dev, NULL, &sun6i_dphy_ops); + if (IS_ERR(dphy->phy)) { + dev_err(&pdev->dev, "failed to create PHY\n"); + return PTR_ERR(dphy->phy); + } + + phy_set_drvdata(dphy->phy, dphy); + phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id sun6i_dphy_of_table[] = { + { .compatible = "allwinner,sun6i-a31-mipi-dphy" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun6i_dphy_of_table); + +static struct platform_driver sun6i_dphy_platform_driver = { + .probe = sun6i_dphy_probe, + .driver = { + .name = "sun6i-mipi-dphy", + .of_match_table = sun6i_dphy_of_table, + }, +}; +module_platform_driver(sun6i_dphy_platform_driver); + +MODULE_AUTHOR("Maxime Ripard maxime.ripard@bootlin"); +MODULE_DESCRIPTION("Allwinner A31 MIPI D-PHY Driver"); +MODULE_LICENSE("GPL");
The current configuration of the DSI bridge and its associated D-PHY is intertwined. In order to ease the future conversion to the phy framework for the D-PHY part, let's split the configuration in two.
Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com --- drivers/gpu/drm/bridge/cdns-dsi.c | 96 ++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 28 deletions(-)
diff --git a/drivers/gpu/drm/bridge/cdns-dsi.c b/drivers/gpu/drm/bridge/cdns-dsi.c index ce9496d13986..3ac6dd524b6d 100644 --- a/drivers/gpu/drm/bridge/cdns-dsi.c +++ b/drivers/gpu/drm/bridge/cdns-dsi.c @@ -545,6 +545,11 @@ bridge_to_cdns_dsi_input(struct drm_bridge *bridge) return container_of(bridge, struct cdns_dsi_input, bridge); }
+static unsigned int mode_to_dpi_hfp(const struct drm_display_mode *mode) +{ + return mode->crtc_hsync_start - mode->crtc_hdisplay; +} + static int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy, struct cdns_dphy_cfg *cfg, unsigned int dpi_htotal, @@ -731,14 +736,12 @@ static unsigned int dpi_to_dsi_timing(unsigned int dpi_timing, static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi, const struct drm_display_mode *mode, struct cdns_dsi_cfg *dsi_cfg, - struct cdns_dphy_cfg *dphy_cfg, bool mode_valid_check) { - unsigned long dsi_htotal = 0, dsi_hss_hsa_hse_hbp = 0; struct cdns_dsi_output *output = &dsi->output; - unsigned int dsi_hfp_ext = 0, dpi_hfp, tmp; + unsigned int tmp; bool sync_pulse = false; - int bpp, nlanes, ret; + int bpp, nlanes;
memset(dsi_cfg, 0, sizeof(*dsi_cfg));
@@ -757,8 +760,6 @@ static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi, mode->crtc_hsync_end : mode->crtc_hsync_start);
dsi_cfg->hbp = dpi_to_dsi_timing(tmp, bpp, DSI_HBP_FRAME_OVERHEAD); - dsi_htotal += dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD; - dsi_hss_hsa_hse_hbp += dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD;
if (sync_pulse) { if (mode_valid_check) @@ -768,49 +769,90 @@ static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi,
dsi_cfg->hsa = dpi_to_dsi_timing(tmp, bpp, DSI_HSA_FRAME_OVERHEAD); - dsi_htotal += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD; - dsi_hss_hsa_hse_hbp += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD; }
dsi_cfg->hact = dpi_to_dsi_timing(mode_valid_check ? mode->hdisplay : mode->crtc_hdisplay, bpp, 0); - dsi_htotal += dsi_cfg->hact; + dsi_cfg->hfp = dpi_to_dsi_timing(mode_to_dpi_hfp(mode), bpp, + DSI_HFP_FRAME_OVERHEAD);
- if (mode_valid_check) - dpi_hfp = mode->hsync_start - mode->hdisplay; - else - dpi_hfp = mode->crtc_hsync_start - mode->crtc_hdisplay; + return 0; +} + +static int cdns_dphy_validate(struct cdns_dsi *dsi, + struct cdns_dsi_cfg *dsi_cfg, + struct cdns_dphy_cfg *dphy_cfg, + const struct drm_display_mode *mode, + bool mode_valid_check) +{ + struct cdns_dsi_output *output = &dsi->output; + unsigned long dsi_htotal; + unsigned int dsi_hfp_ext = 0; + + int ret;
- dsi_cfg->hfp = dpi_to_dsi_timing(dpi_hfp, bpp, DSI_HFP_FRAME_OVERHEAD); + dsi_htotal = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD; + if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + dsi_htotal += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD; + + dsi_htotal += dsi_cfg->hact; dsi_htotal += dsi_cfg->hfp + DSI_HFP_FRAME_OVERHEAD;
if (mode_valid_check) ret = cdns_dsi_get_dphy_pll_cfg(dsi->dphy, dphy_cfg, - mode->htotal, bpp, + mode->htotal, mode->clock * 1000, - dsi_htotal, nlanes, + mipi_dsi_pixel_format_to_bpp(output->dev->format), + dsi_htotal, + output->dev->lanes, &dsi_hfp_ext); else ret = cdns_dsi_get_dphy_pll_cfg(dsi->dphy, dphy_cfg, - mode->crtc_htotal, bpp, + mode->crtc_htotal, + mipi_dsi_pixel_format_to_bpp(output->dev->format), mode->crtc_clock * 1000, - dsi_htotal, nlanes, + dsi_htotal, + output->dev->lanes, &dsi_hfp_ext); - if (ret) return ret;
dsi_cfg->hfp += dsi_hfp_ext; - dsi_htotal += dsi_hfp_ext; - dsi_cfg->htotal = dsi_htotal; + dsi_cfg->htotal = dsi_htotal + dsi_hfp_ext; + + return 0; +} + +static int cdns_dsi_check_conf(struct cdns_dsi *dsi, + const struct drm_display_mode *mode, + struct cdns_dsi_cfg *dsi_cfg, + struct cdns_dphy_cfg *dphy_cfg, + bool mode_valid_check) +{ + struct cdns_dsi_output *output = &dsi->output; + unsigned long dsi_hss_hsa_hse_hbp; + unsigned int nlanes = output->dev->lanes; + int ret; + + ret = cdns_dsi_mode2cfg(dsi, mode, dsi_cfg, mode_valid_check); + if (ret) + return ret; + + ret = cdns_dphy_validate(dsi, dsi_cfg, dphy_cfg, mode, mode_valid_check); + if (ret) + return ret; + + dsi_hss_hsa_hse_hbp = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD; + if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + dsi_hss_hsa_hse_hbp += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
/* * Make sure DPI(HFP) > DSI(HSS+HSA+HSE+HBP) to guarantee that the FIFO * is empty before we start a receiving a new line on the DPI * interface. */ - if ((u64)dphy_cfg->lane_bps * dpi_hfp * nlanes < + if ((u64)dphy_cfg->lane_bps * mode_to_dpi_hfp(mode) * nlanes < (u64)dsi_hss_hsa_hse_hbp * (mode_valid_check ? mode->clock : mode->crtc_clock) * 1000) return -EINVAL; @@ -842,7 +884,7 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge, struct cdns_dsi_output *output = &dsi->output; struct cdns_dphy_cfg dphy_cfg; struct cdns_dsi_cfg dsi_cfg; - int bpp, nlanes, ret; + int bpp, ret;
/* * VFP_DSI should be less than VFP_DPI and VFP_DSI should be at @@ -860,11 +902,9 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge, if ((mode->hdisplay * bpp) % 32) return MODE_H_ILLEGAL;
- nlanes = output->dev->lanes; - - ret = cdns_dsi_mode2cfg(dsi, mode, &dsi_cfg, &dphy_cfg, true); + ret = cdns_dsi_check_conf(dsi, mode, &dsi_cfg, &dphy_cfg, true); if (ret) - return MODE_CLOCK_RANGE; + return MODE_BAD;
return MODE_OK; } @@ -990,7 +1030,7 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge) bpp = mipi_dsi_pixel_format_to_bpp(output->dev->format); nlanes = output->dev->lanes;
- WARN_ON_ONCE(cdns_dsi_mode2cfg(dsi, mode, &dsi_cfg, &dphy_cfg, false)); + WARN_ON_ONCE(cdns_dsi_check_conf(dsi, mode, &dsi_cfg, &dphy_cfg, false));
cdns_dsi_hs_init(dsi, &dphy_cfg); cdns_dsi_init_link(dsi);
Hi Maxime,
On 09/01/19 3:03 PM, Maxime Ripard wrote:
The current configuration of the DSI bridge and its associated D-PHY is intertwined. In order to ease the future conversion to the phy framework for the D-PHY part, let's split the configuration in two.
Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com
Can we get Ack from DRM MAINTAINERS?
Thanks Kishon
drivers/gpu/drm/bridge/cdns-dsi.c | 96 ++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 28 deletions(-)
diff --git a/drivers/gpu/drm/bridge/cdns-dsi.c b/drivers/gpu/drm/bridge/cdns-dsi.c index ce9496d13986..3ac6dd524b6d 100644 --- a/drivers/gpu/drm/bridge/cdns-dsi.c +++ b/drivers/gpu/drm/bridge/cdns-dsi.c @@ -545,6 +545,11 @@ bridge_to_cdns_dsi_input(struct drm_bridge *bridge) return container_of(bridge, struct cdns_dsi_input, bridge); }
+static unsigned int mode_to_dpi_hfp(const struct drm_display_mode *mode) +{
- return mode->crtc_hsync_start - mode->crtc_hdisplay;
+}
static int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy, struct cdns_dphy_cfg *cfg, unsigned int dpi_htotal, @@ -731,14 +736,12 @@ static unsigned int dpi_to_dsi_timing(unsigned int dpi_timing, static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi, const struct drm_display_mode *mode, struct cdns_dsi_cfg *dsi_cfg,
struct cdns_dphy_cfg *dphy_cfg, bool mode_valid_check)
{
- unsigned long dsi_htotal = 0, dsi_hss_hsa_hse_hbp = 0; struct cdns_dsi_output *output = &dsi->output;
- unsigned int dsi_hfp_ext = 0, dpi_hfp, tmp;
- unsigned int tmp; bool sync_pulse = false;
- int bpp, nlanes, ret;
int bpp, nlanes;
memset(dsi_cfg, 0, sizeof(*dsi_cfg));
@@ -757,8 +760,6 @@ static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi, mode->crtc_hsync_end : mode->crtc_hsync_start);
dsi_cfg->hbp = dpi_to_dsi_timing(tmp, bpp, DSI_HBP_FRAME_OVERHEAD);
dsi_htotal += dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD;
dsi_hss_hsa_hse_hbp += dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD;
if (sync_pulse) { if (mode_valid_check)
@@ -768,49 +769,90 @@ static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi,
dsi_cfg->hsa = dpi_to_dsi_timing(tmp, bpp, DSI_HSA_FRAME_OVERHEAD);
dsi_htotal += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
dsi_hss_hsa_hse_hbp += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
}
dsi_cfg->hact = dpi_to_dsi_timing(mode_valid_check ? mode->hdisplay : mode->crtc_hdisplay, bpp, 0);
dsi_htotal += dsi_cfg->hact;
- dsi_cfg->hfp = dpi_to_dsi_timing(mode_to_dpi_hfp(mode), bpp,
DSI_HFP_FRAME_OVERHEAD);
- if (mode_valid_check)
dpi_hfp = mode->hsync_start - mode->hdisplay;
- else
dpi_hfp = mode->crtc_hsync_start - mode->crtc_hdisplay;
- return 0;
+}
+static int cdns_dphy_validate(struct cdns_dsi *dsi,
struct cdns_dsi_cfg *dsi_cfg,
struct cdns_dphy_cfg *dphy_cfg,
const struct drm_display_mode *mode,
bool mode_valid_check)
+{
- struct cdns_dsi_output *output = &dsi->output;
- unsigned long dsi_htotal;
- unsigned int dsi_hfp_ext = 0;
- int ret;
- dsi_cfg->hfp = dpi_to_dsi_timing(dpi_hfp, bpp, DSI_HFP_FRAME_OVERHEAD);
dsi_htotal = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD;
if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
dsi_htotal += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
dsi_htotal += dsi_cfg->hact; dsi_htotal += dsi_cfg->hfp + DSI_HFP_FRAME_OVERHEAD;
if (mode_valid_check) ret = cdns_dsi_get_dphy_pll_cfg(dsi->dphy, dphy_cfg,
mode->htotal, bpp,
mode->htotal, mode->clock * 1000,
dsi_htotal, nlanes,
mipi_dsi_pixel_format_to_bpp(output->dev->format),
dsi_htotal,
else ret = cdns_dsi_get_dphy_pll_cfg(dsi->dphy, dphy_cfg,output->dev->lanes, &dsi_hfp_ext);
mode->crtc_htotal, bpp,
mode->crtc_htotal,
mipi_dsi_pixel_format_to_bpp(output->dev->format), mode->crtc_clock * 1000,
dsi_htotal, nlanes,
dsi_htotal,
output->dev->lanes, &dsi_hfp_ext);
if (ret) return ret;
dsi_cfg->hfp += dsi_hfp_ext;
dsi_htotal += dsi_hfp_ext;
dsi_cfg->htotal = dsi_htotal;
- dsi_cfg->htotal = dsi_htotal + dsi_hfp_ext;
- return 0;
+}
+static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
const struct drm_display_mode *mode,
struct cdns_dsi_cfg *dsi_cfg,
struct cdns_dphy_cfg *dphy_cfg,
bool mode_valid_check)
+{
struct cdns_dsi_output *output = &dsi->output;
unsigned long dsi_hss_hsa_hse_hbp;
unsigned int nlanes = output->dev->lanes;
int ret;
ret = cdns_dsi_mode2cfg(dsi, mode, dsi_cfg, mode_valid_check);
if (ret)
return ret;
ret = cdns_dphy_validate(dsi, dsi_cfg, dphy_cfg, mode, mode_valid_check);
if (ret)
return ret;
dsi_hss_hsa_hse_hbp = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD;
if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
dsi_hss_hsa_hse_hbp += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
/*
- Make sure DPI(HFP) > DSI(HSS+HSA+HSE+HBP) to guarantee that the FIFO
- is empty before we start a receiving a new line on the DPI
- interface.
*/
- if ((u64)dphy_cfg->lane_bps * dpi_hfp * nlanes <
- if ((u64)dphy_cfg->lane_bps * mode_to_dpi_hfp(mode) * nlanes < (u64)dsi_hss_hsa_hse_hbp * (mode_valid_check ? mode->clock : mode->crtc_clock) * 1000) return -EINVAL;
@@ -842,7 +884,7 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge, struct cdns_dsi_output *output = &dsi->output; struct cdns_dphy_cfg dphy_cfg; struct cdns_dsi_cfg dsi_cfg;
- int bpp, nlanes, ret;
int bpp, ret;
/*
- VFP_DSI should be less than VFP_DPI and VFP_DSI should be at
@@ -860,11 +902,9 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge, if ((mode->hdisplay * bpp) % 32) return MODE_H_ILLEGAL;
- nlanes = output->dev->lanes;
- ret = cdns_dsi_mode2cfg(dsi, mode, &dsi_cfg, &dphy_cfg, true);
- ret = cdns_dsi_check_conf(dsi, mode, &dsi_cfg, &dphy_cfg, true); if (ret)
return MODE_CLOCK_RANGE;
return MODE_BAD;
return MODE_OK;
} @@ -990,7 +1030,7 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge) bpp = mipi_dsi_pixel_format_to_bpp(output->dev->format); nlanes = output->dev->lanes;
- WARN_ON_ONCE(cdns_dsi_mode2cfg(dsi, mode, &dsi_cfg, &dphy_cfg, false));
WARN_ON_ONCE(cdns_dsi_check_conf(dsi, mode, &dsi_cfg, &dphy_cfg, false));
cdns_dsi_hs_init(dsi, &dphy_cfg); cdns_dsi_init_link(dsi);
On Wed, Jan 09, 2019 at 10:33:23AM +0100, Maxime Ripard wrote:
The current configuration of the DSI bridge and its associated D-PHY is intertwined. In order to ease the future conversion to the phy framework for the D-PHY part, let's split the configuration in two.
Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com
drivers/gpu/drm/bridge/cdns-dsi.c | 96 ++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 28 deletions(-)
diff --git a/drivers/gpu/drm/bridge/cdns-dsi.c b/drivers/gpu/drm/bridge/cdns-dsi.c index ce9496d13986..3ac6dd524b6d 100644 --- a/drivers/gpu/drm/bridge/cdns-dsi.c +++ b/drivers/gpu/drm/bridge/cdns-dsi.c @@ -545,6 +545,11 @@ bridge_to_cdns_dsi_input(struct drm_bridge *bridge) return container_of(bridge, struct cdns_dsi_input, bridge); }
+static unsigned int mode_to_dpi_hfp(const struct drm_display_mode *mode) +{
- return mode->crtc_hsync_start - mode->crtc_hdisplay;
+}
static int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy, struct cdns_dphy_cfg *cfg, unsigned int dpi_htotal, @@ -731,14 +736,12 @@ static unsigned int dpi_to_dsi_timing(unsigned int dpi_timing, static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi, const struct drm_display_mode *mode, struct cdns_dsi_cfg *dsi_cfg,
struct cdns_dphy_cfg *dphy_cfg, bool mode_valid_check)
{
- unsigned long dsi_htotal = 0, dsi_hss_hsa_hse_hbp = 0; struct cdns_dsi_output *output = &dsi->output;
- unsigned int dsi_hfp_ext = 0, dpi_hfp, tmp;
- unsigned int tmp; bool sync_pulse = false;
- int bpp, nlanes, ret;
int bpp, nlanes;
memset(dsi_cfg, 0, sizeof(*dsi_cfg));
@@ -757,8 +760,6 @@ static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi, mode->crtc_hsync_end : mode->crtc_hsync_start);
dsi_cfg->hbp = dpi_to_dsi_timing(tmp, bpp, DSI_HBP_FRAME_OVERHEAD);
dsi_htotal += dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD;
dsi_hss_hsa_hse_hbp += dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD;
if (sync_pulse) { if (mode_valid_check)
@@ -768,49 +769,90 @@ static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi,
dsi_cfg->hsa = dpi_to_dsi_timing(tmp, bpp, DSI_HSA_FRAME_OVERHEAD);
dsi_htotal += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
dsi_hss_hsa_hse_hbp += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
}
dsi_cfg->hact = dpi_to_dsi_timing(mode_valid_check ? mode->hdisplay : mode->crtc_hdisplay, bpp, 0);
dsi_htotal += dsi_cfg->hact;
- dsi_cfg->hfp = dpi_to_dsi_timing(mode_to_dpi_hfp(mode), bpp,
DSI_HFP_FRAME_OVERHEAD);
We're throwing away the mode_valid_check switch here to flip between crtc_h* value and h* value. Is that intentional? We're using it above for hdisplay, so it's a bit confusing.
- if (mode_valid_check)
dpi_hfp = mode->hsync_start - mode->hdisplay;
- else
dpi_hfp = mode->crtc_hsync_start - mode->crtc_hdisplay;
- return 0;
+}
+static int cdns_dphy_validate(struct cdns_dsi *dsi,
struct cdns_dsi_cfg *dsi_cfg,
struct cdns_dphy_cfg *dphy_cfg,
const struct drm_display_mode *mode,
bool mode_valid_check)
+{
- struct cdns_dsi_output *output = &dsi->output;
- unsigned long dsi_htotal;
- unsigned int dsi_hfp_ext = 0;
- int ret;
- dsi_cfg->hfp = dpi_to_dsi_timing(dpi_hfp, bpp, DSI_HFP_FRAME_OVERHEAD);
dsi_htotal = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD;
if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
dsi_htotal += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
dsi_htotal += dsi_cfg->hact; dsi_htotal += dsi_cfg->hfp + DSI_HFP_FRAME_OVERHEAD;
if (mode_valid_check) ret = cdns_dsi_get_dphy_pll_cfg(dsi->dphy, dphy_cfg,
mode->htotal, bpp,
mode->htotal, mode->clock * 1000,
dsi_htotal, nlanes,
mipi_dsi_pixel_format_to_bpp(output->dev->format),
dsi_htotal,
else ret = cdns_dsi_get_dphy_pll_cfg(dsi->dphy, dphy_cfg,output->dev->lanes, &dsi_hfp_ext);
mode->crtc_htotal, bpp,
mode->crtc_htotal,
mipi_dsi_pixel_format_to_bpp(output->dev->format), mode->crtc_clock * 1000,
dsi_htotal, nlanes,
dsi_htotal,
output->dev->lanes, &dsi_hfp_ext);
if (ret) return ret;
dsi_cfg->hfp += dsi_hfp_ext;
dsi_htotal += dsi_hfp_ext;
dsi_cfg->htotal = dsi_htotal;
- dsi_cfg->htotal = dsi_htotal + dsi_hfp_ext;
- return 0;
+}
+static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
const struct drm_display_mode *mode,
struct cdns_dsi_cfg *dsi_cfg,
struct cdns_dphy_cfg *dphy_cfg,
bool mode_valid_check)
+{
struct cdns_dsi_output *output = &dsi->output;
unsigned long dsi_hss_hsa_hse_hbp;
unsigned int nlanes = output->dev->lanes;
int ret;
ret = cdns_dsi_mode2cfg(dsi, mode, dsi_cfg, mode_valid_check);
if (ret)
return ret;
ret = cdns_dphy_validate(dsi, dsi_cfg, dphy_cfg, mode, mode_valid_check);
if (ret)
return ret;
dsi_hss_hsa_hse_hbp = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD;
if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
dsi_hss_hsa_hse_hbp += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
/*
- Make sure DPI(HFP) > DSI(HSS+HSA+HSE+HBP) to guarantee that the FIFO
- is empty before we start a receiving a new line on the DPI
- interface.
*/
- if ((u64)dphy_cfg->lane_bps * dpi_hfp * nlanes <
- if ((u64)dphy_cfg->lane_bps * mode_to_dpi_hfp(mode) * nlanes < (u64)dsi_hss_hsa_hse_hbp * (mode_valid_check ? mode->clock : mode->crtc_clock) * 1000) return -EINVAL;
@@ -842,7 +884,7 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge, struct cdns_dsi_output *output = &dsi->output; struct cdns_dphy_cfg dphy_cfg; struct cdns_dsi_cfg dsi_cfg;
- int bpp, nlanes, ret;
int bpp, ret;
/*
- VFP_DSI should be less than VFP_DPI and VFP_DSI should be at
@@ -860,11 +902,9 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge, if ((mode->hdisplay * bpp) % 32) return MODE_H_ILLEGAL;
- nlanes = output->dev->lanes;
- ret = cdns_dsi_mode2cfg(dsi, mode, &dsi_cfg, &dphy_cfg, true);
- ret = cdns_dsi_check_conf(dsi, mode, &dsi_cfg, &dphy_cfg, true); if (ret)
return MODE_CLOCK_RANGE;
return MODE_BAD;
return MODE_OK;
} @@ -990,7 +1030,7 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge) bpp = mipi_dsi_pixel_format_to_bpp(output->dev->format); nlanes = output->dev->lanes;
- WARN_ON_ONCE(cdns_dsi_mode2cfg(dsi, mode, &dsi_cfg, &dphy_cfg, false));
WARN_ON_ONCE(cdns_dsi_check_conf(dsi, mode, &dsi_cfg, &dphy_cfg, false));
cdns_dsi_hs_init(dsi, &dphy_cfg); cdns_dsi_init_link(dsi);
-- git-series 0.9.1
On Thu, Jan 17, 2019 at 08:33:38AM -0500, Sean Paul wrote:
@@ -768,49 +769,90 @@ static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi,
dsi_cfg->hsa = dpi_to_dsi_timing(tmp, bpp, DSI_HSA_FRAME_OVERHEAD);
dsi_htotal += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
dsi_hss_hsa_hse_hbp += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
}
dsi_cfg->hact = dpi_to_dsi_timing(mode_valid_check ? mode->hdisplay : mode->crtc_hdisplay, bpp, 0);
dsi_htotal += dsi_cfg->hact;
- dsi_cfg->hfp = dpi_to_dsi_timing(mode_to_dpi_hfp(mode), bpp,
DSI_HFP_FRAME_OVERHEAD);
We're throwing away the mode_valid_check switch here to flip between crtc_h* value and h* value. Is that intentional? We're using it above for hdisplay, so it's a bit confusing.
ah, right, thanks for spotting this.
I'll resend a version with those changes, thanks! Maxime
The Cadence D-PHY bindings was defined as part of the DSI block so far. However, since it's now going to be a separate driver, we need to move the binding to a file of its own.
Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com --- Documentation/devicetree/bindings/display/bridge/cdns,dsi.txt | 21 +------- Documentation/devicetree/bindings/phy/cdns,dphy.txt | 20 +++++++- 2 files changed, 20 insertions(+), 21 deletions(-) create mode 100644 Documentation/devicetree/bindings/phy/cdns,dphy.txt
diff --git a/Documentation/devicetree/bindings/display/bridge/cdns,dsi.txt b/Documentation/devicetree/bindings/display/bridge/cdns,dsi.txt index f5725bb6c61c..525a4bfd8634 100644 --- a/Documentation/devicetree/bindings/display/bridge/cdns,dsi.txt +++ b/Documentation/devicetree/bindings/display/bridge/cdns,dsi.txt @@ -31,28 +31,7 @@ Required subnodes: - one subnode per DSI device connected on the DSI bus. Each DSI device should contain a reg property encoding its virtual channel.
-Cadence DPHY -============ - -Cadence DPHY block. - -Required properties: -- compatible: should be set to "cdns,dphy". -- reg: physical base address and length of the DPHY registers. -- clocks: DPHY reference clocks. -- clock-names: must contain "psm" and "pll_ref". -- #phy-cells: must be set to 0. - - Example: - dphy0: dphy@fd0e0000{ - compatible = "cdns,dphy"; - reg = <0x0 0xfd0e0000 0x0 0x1000>; - clocks = <&psm_clk>, <&pll_ref_clk>; - clock-names = "psm", "pll_ref"; - #phy-cells = <0>; - }; - dsi0: dsi@fd0c0000 { compatible = "cdns,dsi"; reg = <0x0 0xfd0c0000 0x0 0x1000>; diff --git a/Documentation/devicetree/bindings/phy/cdns,dphy.txt b/Documentation/devicetree/bindings/phy/cdns,dphy.txt new file mode 100644 index 000000000000..1095bc4e72d9 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/cdns,dphy.txt @@ -0,0 +1,20 @@ +Cadence DPHY +============ + +Cadence DPHY block. + +Required properties: +- compatible: should be set to "cdns,dphy". +- reg: physical base address and length of the DPHY registers. +- clocks: DPHY reference clocks. +- clock-names: must contain "psm" and "pll_ref". +- #phy-cells: must be set to 0. + +Example: + dphy0: dphy@fd0e0000{ + compatible = "cdns,dphy"; + reg = <0x0 0xfd0e0000 0x0 0x1000>; + clocks = <&psm_clk>, <&pll_ref_clk>; + clock-names = "psm", "pll_ref"; + #phy-cells = <0>; + };
Hi Maxime,
On Wed, Jan 09, 2019 at 10:33:24AM +0100, Maxime Ripard wrote:
The Cadence D-PHY bindings was defined as part of the DSI block so far. However, since it's now going to be a separate driver, we need to move the binding to a file of its own.
Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com
Reviewed-by: Sakari Ailus sakari.ailus@linux.intel.com
Could you also send this to the devicetree list, please?
Documentation/devicetree/bindings/display/bridge/cdns,dsi.txt | 21 +------- Documentation/devicetree/bindings/phy/cdns,dphy.txt | 20 +++++++- 2 files changed, 20 insertions(+), 21 deletions(-) create mode 100644 Documentation/devicetree/bindings/phy/cdns,dphy.txt
diff --git a/Documentation/devicetree/bindings/display/bridge/cdns,dsi.txt b/Documentation/devicetree/bindings/display/bridge/cdns,dsi.txt index f5725bb6c61c..525a4bfd8634 100644 --- a/Documentation/devicetree/bindings/display/bridge/cdns,dsi.txt +++ b/Documentation/devicetree/bindings/display/bridge/cdns,dsi.txt @@ -31,28 +31,7 @@ Required subnodes:
- one subnode per DSI device connected on the DSI bus. Each DSI device should contain a reg property encoding its virtual channel.
-Cadence DPHY
-Cadence DPHY block.
-Required properties: -- compatible: should be set to "cdns,dphy". -- reg: physical base address and length of the DPHY registers. -- clocks: DPHY reference clocks. -- clock-names: must contain "psm" and "pll_ref". -- #phy-cells: must be set to 0.
Example:
- dphy0: dphy@fd0e0000{
compatible = "cdns,dphy";
reg = <0x0 0xfd0e0000 0x0 0x1000>;
clocks = <&psm_clk>, <&pll_ref_clk>;
clock-names = "psm", "pll_ref";
#phy-cells = <0>;
- };
- dsi0: dsi@fd0c0000 { compatible = "cdns,dsi"; reg = <0x0 0xfd0c0000 0x0 0x1000>;
diff --git a/Documentation/devicetree/bindings/phy/cdns,dphy.txt b/Documentation/devicetree/bindings/phy/cdns,dphy.txt new file mode 100644 index 000000000000..1095bc4e72d9 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/cdns,dphy.txt @@ -0,0 +1,20 @@ +Cadence DPHY +============
+Cadence DPHY block.
+Required properties: +- compatible: should be set to "cdns,dphy". +- reg: physical base address and length of the DPHY registers. +- clocks: DPHY reference clocks. +- clock-names: must contain "psm" and "pll_ref". +- #phy-cells: must be set to 0.
+Example:
- dphy0: dphy@fd0e0000{
compatible = "cdns,dphy";
reg = <0x0 0xfd0e0000 0x0 0x1000>;
clocks = <&psm_clk>, <&pll_ref_clk>;
clock-names = "psm", "pll_ref";
#phy-cells = <0>;
- };
-- git-series 0.9.1
Cadence has designed a D-PHY that can be used by the, currently in tree, DSI bridge (DRM), CSI Transceiver and CSI Receiver (v4l2) drivers.
Only the DSI driver has an ad-hoc driver for that phy at the moment, while the v4l2 drivers are completely missing any phy support. In order to make that phy support available to all these drivers, without having to duplicate that code three times, let's create a generic phy framework driver.
Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com --- drivers/phy/cadence/Kconfig | 13 +- drivers/phy/cadence/Makefile | 1 +- drivers/phy/cadence/cdns-dphy.c | 389 +++++++++++++++++++++++++++++++++- 3 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 drivers/phy/cadence/cdns-dphy.c
diff --git a/drivers/phy/cadence/Kconfig b/drivers/phy/cadence/Kconfig index 2b8c0851ff33..31f18b67dd7c 100644 --- a/drivers/phy/cadence/Kconfig +++ b/drivers/phy/cadence/Kconfig @@ -1,6 +1,7 @@ # # Phy drivers for Cadence PHYs # + config PHY_CADENCE_DP tristate "Cadence MHDP DisplayPort PHY driver" depends on OF @@ -9,9 +10,19 @@ config PHY_CADENCE_DP help Support for Cadence MHDP DisplayPort PHY.
+config PHY_CADENCE_DPHY + tristate "Cadence D-PHY Support" + depends on HAS_IOMEM && OF + select GENERIC_PHY + select GENERIC_PHY_MIPI_DPHY + help + Choose this option if you have a Cadence D-PHY in your + system. If M is selected, the module will be called + cdns-dphy. + config PHY_CADENCE_SIERRA tristate "Cadence Sierra PHY Driver" depends on OF && HAS_IOMEM && RESET_CONTROLLER select GENERIC_PHY help - Enable this to support the Cadence Sierra PHY driver \ No newline at end of file + Enable this to support the Cadence Sierra PHY driver diff --git a/drivers/phy/cadence/Makefile b/drivers/phy/cadence/Makefile index 412349af0492..2f9e3457b954 100644 --- a/drivers/phy/cadence/Makefile +++ b/drivers/phy/cadence/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_PHY_CADENCE_DP) += phy-cadence-dp.o +obj-$(CONFIG_PHY_CADENCE_DPHY) += cdns-dphy.o obj-$(CONFIG_PHY_CADENCE_SIERRA) += phy-cadence-sierra.o diff --git a/drivers/phy/cadence/cdns-dphy.c b/drivers/phy/cadence/cdns-dphy.c new file mode 100644 index 000000000000..1d0abba03f37 --- /dev/null +++ b/drivers/phy/cadence/cdns-dphy.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright: 2017-2018 Cadence Design Systems, Inc. + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <linux/phy/phy.h> +#include <linux/phy/phy-mipi-dphy.h> + +#define REG_WAKEUP_TIME_NS 800 +#define DPHY_PLL_RATE_HZ 108000000 + +/* DPHY registers */ +#define DPHY_PMA_CMN(reg) (reg) +#define DPHY_PMA_LCLK(reg) (0x100 + (reg)) +#define DPHY_PMA_LDATA(lane, reg) (0x200 + ((lane) * 0x100) + (reg)) +#define DPHY_PMA_RCLK(reg) (0x600 + (reg)) +#define DPHY_PMA_RDATA(lane, reg) (0x700 + ((lane) * 0x100) + (reg)) +#define DPHY_PCS(reg) (0xb00 + (reg)) + +#define DPHY_CMN_SSM DPHY_PMA_CMN(0x20) +#define DPHY_CMN_SSM_EN BIT(0) +#define DPHY_CMN_TX_MODE_EN BIT(9) + +#define DPHY_CMN_PWM DPHY_PMA_CMN(0x40) +#define DPHY_CMN_PWM_DIV(x) ((x) << 20) +#define DPHY_CMN_PWM_LOW(x) ((x) << 10) +#define DPHY_CMN_PWM_HIGH(x) (x) + +#define DPHY_CMN_FBDIV DPHY_PMA_CMN(0x4c) +#define DPHY_CMN_FBDIV_VAL(low, high) (((high) << 11) | ((low) << 22)) +#define DPHY_CMN_FBDIV_FROM_REG (BIT(10) | BIT(21)) + +#define DPHY_CMN_OPIPDIV DPHY_PMA_CMN(0x50) +#define DPHY_CMN_IPDIV_FROM_REG BIT(0) +#define DPHY_CMN_IPDIV(x) ((x) << 1) +#define DPHY_CMN_OPDIV_FROM_REG BIT(6) +#define DPHY_CMN_OPDIV(x) ((x) << 7) + +#define DPHY_PSM_CFG DPHY_PCS(0x4) +#define DPHY_PSM_CFG_FROM_REG BIT(0) +#define DPHY_PSM_CLK_DIV(x) ((x) << 1) + +#define DSI_HBP_FRAME_OVERHEAD 12 +#define DSI_HSA_FRAME_OVERHEAD 14 +#define DSI_HFP_FRAME_OVERHEAD 6 +#define DSI_HSS_VSS_VSE_FRAME_OVERHEAD 4 +#define DSI_BLANKING_FRAME_OVERHEAD 6 +#define DSI_NULL_FRAME_OVERHEAD 6 +#define DSI_EOT_PKT_SIZE 4 + +struct cdns_dphy_cfg { + u8 pll_ipdiv; + u8 pll_opdiv; + u16 pll_fbdiv; + unsigned int nlanes; +}; + +enum cdns_dphy_clk_lane_cfg { + DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0, + DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1, + DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2, + DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3, +}; + +struct cdns_dphy; +struct cdns_dphy_ops { + int (*probe)(struct cdns_dphy *dphy); + void (*remove)(struct cdns_dphy *dphy); + void (*set_psm_div)(struct cdns_dphy *dphy, u8 div); + void (*set_clk_lane_cfg)(struct cdns_dphy *dphy, + enum cdns_dphy_clk_lane_cfg cfg); + void (*set_pll_cfg)(struct cdns_dphy *dphy, + const struct cdns_dphy_cfg *cfg); + unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy); +}; + +struct cdns_dphy { + struct cdns_dphy_cfg cfg; + void __iomem *regs; + struct clk *psm_clk; + struct clk *pll_ref_clk; + const struct cdns_dphy_ops *ops; + struct phy *phy; +}; + +static int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy, + struct cdns_dphy_cfg *cfg, + struct phy_configure_opts_mipi_dphy *opts, + unsigned int *dsi_hfp_ext) +{ + unsigned long pll_ref_hz = clk_get_rate(dphy->pll_ref_clk); + u64 dlane_bps; + + memset(cfg, 0, sizeof(*cfg)); + + if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000) + return -EINVAL; + else if (pll_ref_hz < 19200000) + cfg->pll_ipdiv = 1; + else if (pll_ref_hz < 38400000) + cfg->pll_ipdiv = 2; + else if (pll_ref_hz < 76800000) + cfg->pll_ipdiv = 4; + else + cfg->pll_ipdiv = 8; + + dlane_bps = opts->hs_clk_rate; + + if (dlane_bps > 2500000000UL || dlane_bps < 160000000UL) + return -EINVAL; + else if (dlane_bps >= 1250000000) + cfg->pll_opdiv = 1; + else if (dlane_bps >= 630000000) + cfg->pll_opdiv = 2; + else if (dlane_bps >= 320000000) + cfg->pll_opdiv = 4; + else if (dlane_bps >= 160000000) + cfg->pll_opdiv = 8; + + cfg->pll_fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv * + cfg->pll_ipdiv, + pll_ref_hz); + + return 0; +} + +static int cdns_dphy_setup_psm(struct cdns_dphy *dphy) +{ + unsigned long psm_clk_hz = clk_get_rate(dphy->psm_clk); + unsigned long psm_div; + + if (!psm_clk_hz || psm_clk_hz > 100000000) + return -EINVAL; + + psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000); + if (dphy->ops->set_psm_div) + dphy->ops->set_psm_div(dphy, psm_div); + + return 0; +} + +static void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy, + enum cdns_dphy_clk_lane_cfg cfg) +{ + if (dphy->ops->set_clk_lane_cfg) + dphy->ops->set_clk_lane_cfg(dphy, cfg); +} + +static void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy, + const struct cdns_dphy_cfg *cfg) +{ + if (dphy->ops->set_pll_cfg) + dphy->ops->set_pll_cfg(dphy, cfg); +} + +static unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy) +{ + return dphy->ops->get_wakeup_time_ns(dphy); +} + +static unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy) +{ + /* Default wakeup time is 800 ns (in a simulated environment). */ + return 800; +} + +static void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy, + const struct cdns_dphy_cfg *cfg) +{ + u32 fbdiv_low, fbdiv_high; + + fbdiv_low = (cfg->pll_fbdiv / 4) - 2; + fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2; + + writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG | + DPHY_CMN_IPDIV(cfg->pll_ipdiv) | + DPHY_CMN_OPDIV(cfg->pll_opdiv), + dphy->regs + DPHY_CMN_OPIPDIV); + writel(DPHY_CMN_FBDIV_FROM_REG | + DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high), + dphy->regs + DPHY_CMN_FBDIV); + writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) | + DPHY_CMN_PWM_DIV(0x8), + dphy->regs + DPHY_CMN_PWM); +} + +static void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div) +{ + writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div), + dphy->regs + DPHY_PSM_CFG); +} + +/* + * This is the reference implementation of DPHY hooks. Specific integration of + * this IP may have to re-implement some of them depending on how they decided + * to wire things in the SoC. + */ +static const struct cdns_dphy_ops ref_dphy_ops = { + .get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns, + .set_pll_cfg = cdns_dphy_ref_set_pll_cfg, + .set_psm_div = cdns_dphy_ref_set_psm_div, +}; + +static int cdns_dphy_config_from_opts(struct phy *phy, + struct phy_configure_opts_mipi_dphy *opts, + struct cdns_dphy_cfg *cfg) +{ + struct cdns_dphy *dphy = phy_get_drvdata(phy); + unsigned int dsi_hfp_ext = 0; + int ret; + + ret = phy_mipi_dphy_config_validate(opts); + if (ret) + return ret; + + ret = cdns_dsi_get_dphy_pll_cfg(dphy, cfg, + opts, &dsi_hfp_ext); + if (ret) + return ret; + + opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) * 1000; + + return 0; +} + +static int cdns_dphy_validate(struct phy *phy, enum phy_mode mode, int submode, + union phy_configure_opts *opts) +{ + struct cdns_dphy_cfg cfg = { 0 }; + + if (mode != PHY_MODE_MIPI_DPHY) + return -EINVAL; + + return cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg); +} + +static int cdns_dphy_configure(struct phy *phy, union phy_configure_opts *opts) +{ + struct cdns_dphy *dphy = phy_get_drvdata(phy); + struct cdns_dphy_cfg cfg = { 0 }; + int ret; + + ret = cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg); + if (ret) + return ret; + + /* + * Configure the internal PSM clk divider so that the DPHY has a + * 1MHz clk (or something close). + */ + ret = cdns_dphy_setup_psm(dphy); + if (ret) + return ret; + + /* + * Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes + * and 8 data lanes, each clk lane can be attache different set of + * data lanes. The 2 groups are named 'left' and 'right', so here we + * just say that we want the 'left' clk lane to drive the 'left' data + * lanes. + */ + cdns_dphy_set_clk_lane_cfg(dphy, DPHY_CLK_CFG_LEFT_DRIVES_LEFT); + + /* + * Configure the DPHY PLL that will be used to generate the TX byte + * clk. + */ + cdns_dphy_set_pll_cfg(dphy, &cfg); + + return 0; +} + +static int cdns_dphy_power_on(struct phy *phy) +{ + struct cdns_dphy *dphy = phy_get_drvdata(phy); + + clk_prepare_enable(dphy->psm_clk); + clk_prepare_enable(dphy->pll_ref_clk); + + /* Start TX state machine. */ + writel(DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN, dphy->regs + DPHY_CMN_SSM); + + return 0; +} + +static int cdns_dphy_power_off(struct phy *phy) +{ + struct cdns_dphy *dphy = phy_get_drvdata(phy); + + clk_disable_unprepare(dphy->pll_ref_clk); + clk_disable_unprepare(dphy->psm_clk); + + return 0; +} + +static const struct phy_ops cdns_dphy_ops = { + .configure = cdns_dphy_configure, + .validate = cdns_dphy_validate, + .power_on = cdns_dphy_power_on, + .power_off = cdns_dphy_power_off, +}; + +static int cdns_dphy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct cdns_dphy *dphy; + struct resource *res; + int ret; + + dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL); + if (!dphy) + return -ENOMEM; + dev_set_drvdata(&pdev->dev, dphy); + + dphy->ops = of_device_get_match_data(&pdev->dev); + if (!dphy->ops) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dphy->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dphy->regs)) + return PTR_ERR(dphy->regs); + + dphy->psm_clk = devm_clk_get(&pdev->dev, "psm"); + if (IS_ERR(dphy->psm_clk)) + return PTR_ERR(dphy->psm_clk); + + dphy->pll_ref_clk = devm_clk_get(&pdev->dev, "pll_ref"); + if (IS_ERR(dphy->pll_ref_clk)) + return PTR_ERR(dphy->pll_ref_clk); + + if (dphy->ops->probe) { + ret = dphy->ops->probe(dphy); + if (ret) + return ret; + } + + dphy->phy = devm_phy_create(&pdev->dev, NULL, &cdns_dphy_ops); + if (IS_ERR(dphy->phy)) { + dev_err(&pdev->dev, "failed to create PHY\n"); + if (dphy->ops->remove) + dphy->ops->remove(dphy); + return PTR_ERR(dphy->phy); + } + + phy_set_drvdata(dphy->phy, dphy); + phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static int cdns_dphy_remove(struct platform_device *pdev) +{ + struct cdns_dphy *dphy = dev_get_drvdata(&pdev->dev); + + if (dphy->ops->remove) + dphy->ops->remove(dphy); + + return 0; +} + +static const struct of_device_id cdns_dphy_of_match[] = { + { .compatible = "cdns,dphy", .data = &ref_dphy_ops }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, cdns_dphy_of_match); + +static struct platform_driver cdns_dphy_platform_driver = { + .probe = cdns_dphy_probe, + .remove = cdns_dphy_remove, + .driver = { + .name = "cdns-mipi-dphy", + .of_match_table = cdns_dphy_of_match, + }, +}; +module_platform_driver(cdns_dphy_platform_driver); + +MODULE_AUTHOR("Maxime Ripard maxime.ripard@bootlin.com"); +MODULE_DESCRIPTION("Cadence MIPI D-PHY Driver"); +MODULE_LICENSE("GPL");
On Wed, Jan 09, 2019 at 10:33:25AM +0100, Maxime Ripard wrote:
Cadence has designed a D-PHY that can be used by the, currently in tree, DSI bridge (DRM), CSI Transceiver and CSI Receiver (v4l2) drivers.
Only the DSI driver has an ad-hoc driver for that phy at the moment, while the v4l2 drivers are completely missing any phy support. In order to make that phy support available to all these drivers, without having to duplicate that code three times, let's create a generic phy framework driver.
Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com
Reviewed-by: Sakari Ailus sakari.ailus@linux.intel.com
On Wed, Jan 09, 2019 at 10:33:25AM +0100, Maxime Ripard wrote:
Cadence has designed a D-PHY that can be used by the, currently in tree, DSI bridge (DRM), CSI Transceiver and CSI Receiver (v4l2) drivers.
Only the DSI driver has an ad-hoc driver for that phy at the moment, while the v4l2 drivers are completely missing any phy support. In order to make that phy support available to all these drivers, without having to duplicate that code three times, let's create a generic phy framework driver.
Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com
drivers/phy/cadence/Kconfig | 13 +- drivers/phy/cadence/Makefile | 1 +- drivers/phy/cadence/cdns-dphy.c | 389 +++++++++++++++++++++++++++++++++- 3 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 drivers/phy/cadence/cdns-dphy.c
diff --git a/drivers/phy/cadence/Kconfig b/drivers/phy/cadence/Kconfig index 2b8c0851ff33..31f18b67dd7c 100644 --- a/drivers/phy/cadence/Kconfig +++ b/drivers/phy/cadence/Kconfig @@ -1,6 +1,7 @@ # # Phy drivers for Cadence PHYs #
config PHY_CADENCE_DP tristate "Cadence MHDP DisplayPort PHY driver" depends on OF @@ -9,9 +10,19 @@ config PHY_CADENCE_DP help Support for Cadence MHDP DisplayPort PHY.
+config PHY_CADENCE_DPHY
- tristate "Cadence D-PHY Support"
- depends on HAS_IOMEM && OF
- select GENERIC_PHY
- select GENERIC_PHY_MIPI_DPHY
- help
Choose this option if you have a Cadence D-PHY in your
system. If M is selected, the module will be called
cdns-dphy.
config PHY_CADENCE_SIERRA tristate "Cadence Sierra PHY Driver" depends on OF && HAS_IOMEM && RESET_CONTROLLER select GENERIC_PHY help
Enable this to support the Cadence Sierra PHY driver
\ No newline at end of file
Enable this to support the Cadence Sierra PHY driver
diff --git a/drivers/phy/cadence/Makefile b/drivers/phy/cadence/Makefile index 412349af0492..2f9e3457b954 100644 --- a/drivers/phy/cadence/Makefile +++ b/drivers/phy/cadence/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_PHY_CADENCE_DP) += phy-cadence-dp.o +obj-$(CONFIG_PHY_CADENCE_DPHY) += cdns-dphy.o obj-$(CONFIG_PHY_CADENCE_SIERRA) += phy-cadence-sierra.o diff --git a/drivers/phy/cadence/cdns-dphy.c b/drivers/phy/cadence/cdns-dphy.c new file mode 100644 index 000000000000..1d0abba03f37 --- /dev/null +++ b/drivers/phy/cadence/cdns-dphy.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- Copyright: 2017-2018 Cadence Design Systems, Inc.
- */
+#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/reset.h>
+#include <linux/phy/phy.h> +#include <linux/phy/phy-mipi-dphy.h>
+#define REG_WAKEUP_TIME_NS 800 +#define DPHY_PLL_RATE_HZ 108000000
+/* DPHY registers */ +#define DPHY_PMA_CMN(reg) (reg) +#define DPHY_PMA_LCLK(reg) (0x100 + (reg)) +#define DPHY_PMA_LDATA(lane, reg) (0x200 + ((lane) * 0x100) + (reg)) +#define DPHY_PMA_RCLK(reg) (0x600 + (reg)) +#define DPHY_PMA_RDATA(lane, reg) (0x700 + ((lane) * 0x100) + (reg)) +#define DPHY_PCS(reg) (0xb00 + (reg))
+#define DPHY_CMN_SSM DPHY_PMA_CMN(0x20) +#define DPHY_CMN_SSM_EN BIT(0) +#define DPHY_CMN_TX_MODE_EN BIT(9)
+#define DPHY_CMN_PWM DPHY_PMA_CMN(0x40) +#define DPHY_CMN_PWM_DIV(x) ((x) << 20) +#define DPHY_CMN_PWM_LOW(x) ((x) << 10) +#define DPHY_CMN_PWM_HIGH(x) (x)
+#define DPHY_CMN_FBDIV DPHY_PMA_CMN(0x4c) +#define DPHY_CMN_FBDIV_VAL(low, high) (((high) << 11) | ((low) << 22)) +#define DPHY_CMN_FBDIV_FROM_REG (BIT(10) | BIT(21))
+#define DPHY_CMN_OPIPDIV DPHY_PMA_CMN(0x50) +#define DPHY_CMN_IPDIV_FROM_REG BIT(0) +#define DPHY_CMN_IPDIV(x) ((x) << 1) +#define DPHY_CMN_OPDIV_FROM_REG BIT(6) +#define DPHY_CMN_OPDIV(x) ((x) << 7)
+#define DPHY_PSM_CFG DPHY_PCS(0x4) +#define DPHY_PSM_CFG_FROM_REG BIT(0) +#define DPHY_PSM_CLK_DIV(x) ((x) << 1)
+#define DSI_HBP_FRAME_OVERHEAD 12 +#define DSI_HSA_FRAME_OVERHEAD 14 +#define DSI_HFP_FRAME_OVERHEAD 6 +#define DSI_HSS_VSS_VSE_FRAME_OVERHEAD 4 +#define DSI_BLANKING_FRAME_OVERHEAD 6 +#define DSI_NULL_FRAME_OVERHEAD 6 +#define DSI_EOT_PKT_SIZE 4
+struct cdns_dphy_cfg {
- u8 pll_ipdiv;
- u8 pll_opdiv;
- u16 pll_fbdiv;
- unsigned int nlanes;
+};
+enum cdns_dphy_clk_lane_cfg {
- DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0,
- DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1,
- DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2,
- DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3,
+};
+struct cdns_dphy; +struct cdns_dphy_ops {
- int (*probe)(struct cdns_dphy *dphy);
- void (*remove)(struct cdns_dphy *dphy);
- void (*set_psm_div)(struct cdns_dphy *dphy, u8 div);
- void (*set_clk_lane_cfg)(struct cdns_dphy *dphy,
enum cdns_dphy_clk_lane_cfg cfg);
- void (*set_pll_cfg)(struct cdns_dphy *dphy,
const struct cdns_dphy_cfg *cfg);
- unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy);
+};
+struct cdns_dphy {
- struct cdns_dphy_cfg cfg;
- void __iomem *regs;
- struct clk *psm_clk;
- struct clk *pll_ref_clk;
- const struct cdns_dphy_ops *ops;
- struct phy *phy;
+};
+static int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy,
struct cdns_dphy_cfg *cfg,
struct phy_configure_opts_mipi_dphy *opts,
unsigned int *dsi_hfp_ext)
+{
- unsigned long pll_ref_hz = clk_get_rate(dphy->pll_ref_clk);
- u64 dlane_bps;
- memset(cfg, 0, sizeof(*cfg));
- if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000)
return -EINVAL;
- else if (pll_ref_hz < 19200000)
cfg->pll_ipdiv = 1;
- else if (pll_ref_hz < 38400000)
cfg->pll_ipdiv = 2;
- else if (pll_ref_hz < 76800000)
cfg->pll_ipdiv = 4;
- else
cfg->pll_ipdiv = 8;
- dlane_bps = opts->hs_clk_rate;
- if (dlane_bps > 2500000000UL || dlane_bps < 160000000UL)
return -EINVAL;
- else if (dlane_bps >= 1250000000)
cfg->pll_opdiv = 1;
- else if (dlane_bps >= 630000000)
cfg->pll_opdiv = 2;
- else if (dlane_bps >= 320000000)
cfg->pll_opdiv = 4;
- else if (dlane_bps >= 160000000)
cfg->pll_opdiv = 8;
- cfg->pll_fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv *
cfg->pll_ipdiv,
pll_ref_hz);
- return 0;
+}
+static int cdns_dphy_setup_psm(struct cdns_dphy *dphy) +{
- unsigned long psm_clk_hz = clk_get_rate(dphy->psm_clk);
- unsigned long psm_div;
- if (!psm_clk_hz || psm_clk_hz > 100000000)
return -EINVAL;
- psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000);
- if (dphy->ops->set_psm_div)
dphy->ops->set_psm_div(dphy, psm_div);
- return 0;
+}
+static void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy,
enum cdns_dphy_clk_lane_cfg cfg)
+{
- if (dphy->ops->set_clk_lane_cfg)
dphy->ops->set_clk_lane_cfg(dphy, cfg);
+}
+static void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy,
const struct cdns_dphy_cfg *cfg)
+{
- if (dphy->ops->set_pll_cfg)
dphy->ops->set_pll_cfg(dphy, cfg);
+}
+static unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy) +{
- return dphy->ops->get_wakeup_time_ns(dphy);
+}
+static unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy) +{
- /* Default wakeup time is 800 ns (in a simulated environment). */
- return 800;
+}
+static void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy,
const struct cdns_dphy_cfg *cfg)
+{
- u32 fbdiv_low, fbdiv_high;
- fbdiv_low = (cfg->pll_fbdiv / 4) - 2;
- fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2;
- writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG |
DPHY_CMN_IPDIV(cfg->pll_ipdiv) |
DPHY_CMN_OPDIV(cfg->pll_opdiv),
dphy->regs + DPHY_CMN_OPIPDIV);
- writel(DPHY_CMN_FBDIV_FROM_REG |
DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high),
dphy->regs + DPHY_CMN_FBDIV);
- writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) |
DPHY_CMN_PWM_DIV(0x8),
dphy->regs + DPHY_CMN_PWM);
+}
+static void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div) +{
- writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div),
dphy->regs + DPHY_PSM_CFG);
+}
+/*
- This is the reference implementation of DPHY hooks. Specific integration of
- this IP may have to re-implement some of them depending on how they decided
- to wire things in the SoC.
- */
+static const struct cdns_dphy_ops ref_dphy_ops = {
- .get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns,
- .set_pll_cfg = cdns_dphy_ref_set_pll_cfg,
- .set_psm_div = cdns_dphy_ref_set_psm_div,
+};
+static int cdns_dphy_config_from_opts(struct phy *phy,
struct phy_configure_opts_mipi_dphy *opts,
struct cdns_dphy_cfg *cfg)
+{
- struct cdns_dphy *dphy = phy_get_drvdata(phy);
- unsigned int dsi_hfp_ext = 0;
- int ret;
- ret = phy_mipi_dphy_config_validate(opts);
- if (ret)
return ret;
- ret = cdns_dsi_get_dphy_pll_cfg(dphy, cfg,
opts, &dsi_hfp_ext);
- if (ret)
return ret;
- opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) * 1000;
This should be "/ 1000" since the units of wakeup is us now (thanks to patch 2). You've made this change in patch 9, so I think it's just a misplaced fixup.
- return 0;
+}
+static int cdns_dphy_validate(struct phy *phy, enum phy_mode mode, int submode,
union phy_configure_opts *opts)
+{
- struct cdns_dphy_cfg cfg = { 0 };
- if (mode != PHY_MODE_MIPI_DPHY)
return -EINVAL;
- return cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
+}
+static int cdns_dphy_configure(struct phy *phy, union phy_configure_opts *opts) +{
- struct cdns_dphy *dphy = phy_get_drvdata(phy);
- struct cdns_dphy_cfg cfg = { 0 };
- int ret;
- ret = cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
- if (ret)
return ret;
- /*
* Configure the internal PSM clk divider so that the DPHY has a
* 1MHz clk (or something close).
*/
- ret = cdns_dphy_setup_psm(dphy);
- if (ret)
return ret;
- /*
* Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes
* and 8 data lanes, each clk lane can be attache different set of
* data lanes. The 2 groups are named 'left' and 'right', so here we
* just say that we want the 'left' clk lane to drive the 'left' data
* lanes.
*/
- cdns_dphy_set_clk_lane_cfg(dphy, DPHY_CLK_CFG_LEFT_DRIVES_LEFT);
- /*
* Configure the DPHY PLL that will be used to generate the TX byte
* clk.
*/
- cdns_dphy_set_pll_cfg(dphy, &cfg);
- return 0;
+}
+static int cdns_dphy_power_on(struct phy *phy) +{
- struct cdns_dphy *dphy = phy_get_drvdata(phy);
- clk_prepare_enable(dphy->psm_clk);
- clk_prepare_enable(dphy->pll_ref_clk);
- /* Start TX state machine. */
- writel(DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN, dphy->regs + DPHY_CMN_SSM);
- return 0;
+}
+static int cdns_dphy_power_off(struct phy *phy) +{
- struct cdns_dphy *dphy = phy_get_drvdata(phy);
- clk_disable_unprepare(dphy->pll_ref_clk);
- clk_disable_unprepare(dphy->psm_clk);
- return 0;
+}
+static const struct phy_ops cdns_dphy_ops = {
- .configure = cdns_dphy_configure,
- .validate = cdns_dphy_validate,
- .power_on = cdns_dphy_power_on,
- .power_off = cdns_dphy_power_off,
+};
+static int cdns_dphy_probe(struct platform_device *pdev) +{
- struct phy_provider *phy_provider;
- struct cdns_dphy *dphy;
- struct resource *res;
- int ret;
- dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
- if (!dphy)
return -ENOMEM;
- dev_set_drvdata(&pdev->dev, dphy);
- dphy->ops = of_device_get_match_data(&pdev->dev);
- if (!dphy->ops)
return -EINVAL;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- dphy->regs = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(dphy->regs))
return PTR_ERR(dphy->regs);
- dphy->psm_clk = devm_clk_get(&pdev->dev, "psm");
- if (IS_ERR(dphy->psm_clk))
return PTR_ERR(dphy->psm_clk);
- dphy->pll_ref_clk = devm_clk_get(&pdev->dev, "pll_ref");
- if (IS_ERR(dphy->pll_ref_clk))
return PTR_ERR(dphy->pll_ref_clk);
- if (dphy->ops->probe) {
ret = dphy->ops->probe(dphy);
if (ret)
return ret;
- }
- dphy->phy = devm_phy_create(&pdev->dev, NULL, &cdns_dphy_ops);
- if (IS_ERR(dphy->phy)) {
dev_err(&pdev->dev, "failed to create PHY\n");
if (dphy->ops->remove)
dphy->ops->remove(dphy);
return PTR_ERR(dphy->phy);
- }
- phy_set_drvdata(dphy->phy, dphy);
- phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
- return PTR_ERR_OR_ZERO(phy_provider);
+}
+static int cdns_dphy_remove(struct platform_device *pdev) +{
- struct cdns_dphy *dphy = dev_get_drvdata(&pdev->dev);
- if (dphy->ops->remove)
dphy->ops->remove(dphy);
- return 0;
+}
+static const struct of_device_id cdns_dphy_of_match[] = {
- { .compatible = "cdns,dphy", .data = &ref_dphy_ops },
- { /* sentinel */ },
+}; +MODULE_DEVICE_TABLE(of, cdns_dphy_of_match);
+static struct platform_driver cdns_dphy_platform_driver = {
- .probe = cdns_dphy_probe,
- .remove = cdns_dphy_remove,
- .driver = {
.name = "cdns-mipi-dphy",
.of_match_table = cdns_dphy_of_match,
- },
+}; +module_platform_driver(cdns_dphy_platform_driver);
+MODULE_AUTHOR("Maxime Ripard maxime.ripard@bootlin.com"); +MODULE_DESCRIPTION("Cadence MIPI D-PHY Driver");
+MODULE_LICENSE("GPL");
git-series 0.9.1 _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
Hi Sean,
On Thu, Jan 17, 2019 at 08:53:22AM -0500, Sean Paul wrote:
On Wed, Jan 09, 2019 at 10:33:25AM +0100, Maxime Ripard wrote:
- opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) * 1000;
This should be "/ 1000" since the units of wakeup is us now (thanks to patch 2). You've made this change in patch 9, so I think it's just a misplaced fixup.
Good catch, this was fixed in patch 9, but it definitely belongs there.
Thanks! Maxime
Now that we have everything we need in the phy framework to allow to tune the phy parameters, let's convert the Cadence DSI bridge to that API instead of creating a ad-hoc driver for its phy.
Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com --- drivers/gpu/drm/bridge/Kconfig | 1 +- drivers/gpu/drm/bridge/cdns-dsi.c | 485 +++---------------------------- drivers/phy/cadence/cdns-dphy.c | 2 +- 3 files changed, 61 insertions(+), 427 deletions(-)
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 2fee47b0d50b..8840f396a7b6 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -30,6 +30,7 @@ config DRM_CDNS_DSI select DRM_KMS_HELPER select DRM_MIPI_DSI select DRM_PANEL_BRIDGE + select GENERIC_PHY_MIPI_DPHY depends on OF help Support Cadence DPI to DSI bridge. This is an internal diff --git a/drivers/gpu/drm/bridge/cdns-dsi.c b/drivers/gpu/drm/bridge/cdns-dsi.c index 3ac6dd524b6d..7b432257ffbe 100644 --- a/drivers/gpu/drm/bridge/cdns-dsi.c +++ b/drivers/gpu/drm/bridge/cdns-dsi.c @@ -21,6 +21,9 @@ #include <linux/pm_runtime.h> #include <linux/reset.h>
+#include <linux/phy/phy.h> +#include <linux/phy/phy-mipi-dphy.h> + #define IP_CONF 0x0 #define SP_HS_FIFO_DEPTH(x) (((x) & GENMASK(30, 26)) >> 26) #define SP_LP_FIFO_DEPTH(x) (((x) & GENMASK(25, 21)) >> 21) @@ -419,44 +422,11 @@ #define DSI_NULL_FRAME_OVERHEAD 6 #define DSI_EOT_PKT_SIZE 4
-#define REG_WAKEUP_TIME_NS 800 -#define DPHY_PLL_RATE_HZ 108000000 - -/* DPHY registers */ -#define DPHY_PMA_CMN(reg) (reg) -#define DPHY_PMA_LCLK(reg) (0x100 + (reg)) -#define DPHY_PMA_LDATA(lane, reg) (0x200 + ((lane) * 0x100) + (reg)) -#define DPHY_PMA_RCLK(reg) (0x600 + (reg)) -#define DPHY_PMA_RDATA(lane, reg) (0x700 + ((lane) * 0x100) + (reg)) -#define DPHY_PCS(reg) (0xb00 + (reg)) - -#define DPHY_CMN_SSM DPHY_PMA_CMN(0x20) -#define DPHY_CMN_SSM_EN BIT(0) -#define DPHY_CMN_TX_MODE_EN BIT(9) - -#define DPHY_CMN_PWM DPHY_PMA_CMN(0x40) -#define DPHY_CMN_PWM_DIV(x) ((x) << 20) -#define DPHY_CMN_PWM_LOW(x) ((x) << 10) -#define DPHY_CMN_PWM_HIGH(x) (x) - -#define DPHY_CMN_FBDIV DPHY_PMA_CMN(0x4c) -#define DPHY_CMN_FBDIV_VAL(low, high) (((high) << 11) | ((low) << 22)) -#define DPHY_CMN_FBDIV_FROM_REG (BIT(10) | BIT(21)) - -#define DPHY_CMN_OPIPDIV DPHY_PMA_CMN(0x50) -#define DPHY_CMN_IPDIV_FROM_REG BIT(0) -#define DPHY_CMN_IPDIV(x) ((x) << 1) -#define DPHY_CMN_OPDIV_FROM_REG BIT(6) -#define DPHY_CMN_OPDIV(x) ((x) << 7) - -#define DPHY_PSM_CFG DPHY_PCS(0x4) -#define DPHY_PSM_CFG_FROM_REG BIT(0) -#define DPHY_PSM_CLK_DIV(x) ((x) << 1) - struct cdns_dsi_output { struct mipi_dsi_device *dev; struct drm_panel *panel; struct drm_bridge *bridge; + union phy_configure_opts phy_opts; };
enum cdns_dsi_input_id { @@ -465,14 +435,6 @@ enum cdns_dsi_input_id { CDNS_DSC_INPUT, };
-struct cdns_dphy_cfg { - u8 pll_ipdiv; - u8 pll_opdiv; - u16 pll_fbdiv; - unsigned long lane_bps; - unsigned int nlanes; -}; - struct cdns_dsi_cfg { unsigned int hfp; unsigned int hsa; @@ -481,34 +443,6 @@ struct cdns_dsi_cfg { unsigned int htotal; };
-struct cdns_dphy; - -enum cdns_dphy_clk_lane_cfg { - DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0, - DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1, - DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2, - DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3, -}; - -struct cdns_dphy_ops { - int (*probe)(struct cdns_dphy *dphy); - void (*remove)(struct cdns_dphy *dphy); - void (*set_psm_div)(struct cdns_dphy *dphy, u8 div); - void (*set_clk_lane_cfg)(struct cdns_dphy *dphy, - enum cdns_dphy_clk_lane_cfg cfg); - void (*set_pll_cfg)(struct cdns_dphy *dphy, - const struct cdns_dphy_cfg *cfg); - unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy); -}; - -struct cdns_dphy { - struct cdns_dphy_cfg cfg; - void __iomem *regs; - struct clk *psm_clk; - struct clk *pll_ref_clk; - const struct cdns_dphy_ops *ops; -}; - struct cdns_dsi_input { enum cdns_dsi_input_id id; struct drm_bridge bridge; @@ -526,7 +460,7 @@ struct cdns_dsi { struct reset_control *dsi_p_rst; struct clk *dsi_sys_clk; bool link_initialized; - struct cdns_dphy *dphy; + struct phy *dphy; };
static inline struct cdns_dsi *input_to_dsi(struct cdns_dsi_input *input) @@ -550,175 +484,6 @@ static unsigned int mode_to_dpi_hfp(const struct drm_display_mode *mode) return mode->crtc_hsync_start - mode->crtc_hdisplay; }
-static int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy, - struct cdns_dphy_cfg *cfg, - unsigned int dpi_htotal, - unsigned int dpi_bpp, - unsigned int dpi_hz, - unsigned int dsi_htotal, - unsigned int dsi_nlanes, - unsigned int *dsi_hfp_ext) -{ - u64 dlane_bps, dlane_bps_max, fbdiv, fbdiv_max, adj_dsi_htotal; - unsigned long pll_ref_hz = clk_get_rate(dphy->pll_ref_clk); - - memset(cfg, 0, sizeof(*cfg)); - - cfg->nlanes = dsi_nlanes; - - if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000) - return -EINVAL; - else if (pll_ref_hz < 19200000) - cfg->pll_ipdiv = 1; - else if (pll_ref_hz < 38400000) - cfg->pll_ipdiv = 2; - else if (pll_ref_hz < 76800000) - cfg->pll_ipdiv = 4; - else - cfg->pll_ipdiv = 8; - - /* - * Make sure DSI htotal is aligned on a lane boundary when calculating - * the expected data rate. This is done by extending HFP in case of - * misalignment. - */ - adj_dsi_htotal = dsi_htotal; - if (dsi_htotal % dsi_nlanes) - adj_dsi_htotal += dsi_nlanes - (dsi_htotal % dsi_nlanes); - - dlane_bps = (u64)dpi_hz * adj_dsi_htotal; - - /* data rate in bytes/sec is not an integer, refuse the mode. */ - if (do_div(dlane_bps, dsi_nlanes * dpi_htotal)) - return -EINVAL; - - /* data rate was in bytes/sec, convert to bits/sec. */ - dlane_bps *= 8; - - if (dlane_bps > 2500000000UL || dlane_bps < 160000000UL) - return -EINVAL; - else if (dlane_bps >= 1250000000) - cfg->pll_opdiv = 1; - else if (dlane_bps >= 630000000) - cfg->pll_opdiv = 2; - else if (dlane_bps >= 320000000) - cfg->pll_opdiv = 4; - else if (dlane_bps >= 160000000) - cfg->pll_opdiv = 8; - - /* - * Allow a deviation of 0.2% on the per-lane data rate to try to - * recover a potential mismatch between DPI and PPI clks. - */ - dlane_bps_max = dlane_bps + DIV_ROUND_DOWN_ULL(dlane_bps, 500); - fbdiv_max = DIV_ROUND_DOWN_ULL(dlane_bps_max * 2 * - cfg->pll_opdiv * cfg->pll_ipdiv, - pll_ref_hz); - fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv * - cfg->pll_ipdiv, - pll_ref_hz); - - /* - * Iterate over all acceptable fbdiv and try to find an adjusted DSI - * htotal length providing an exact match. - * - * Note that we could do something even trickier by relying on the fact - * that a new line is not necessarily aligned on a lane boundary, so, - * by making adj_dsi_htotal non aligned on a dsi_lanes we can improve a - * bit the precision. With this, the step would be - * - * pll_ref_hz / (2 * opdiv * ipdiv * nlanes) - * - * instead of - * - * pll_ref_hz / (2 * opdiv * ipdiv) - * - * The drawback of this approach is that we would need to make sure the - * number or lines is a multiple of the realignment periodicity which is - * a function of the number of lanes and the original misalignment. For - * example, for NLANES = 4 and HTOTAL % NLANES = 3, it takes 4 lines - * to realign on a lane: - * LINE 0: expected number of bytes, starts emitting first byte of - * LINE 1 on LANE 3 - * LINE 1: expected number of bytes, starts emitting first 2 bytes of - * LINE 2 on LANES 2 and 3 - * LINE 2: expected number of bytes, starts emitting first 3 bytes of - * of LINE 3 on LANES 1, 2 and 3 - * LINE 3: one byte less, now things are realigned on LANE 0 for LINE 4 - * - * I figured this extra complexity was not worth the benefit, but if - * someone really has unfixable mismatch, that would be something to - * investigate. - */ - for (; fbdiv <= fbdiv_max; fbdiv++) { - u32 rem; - - adj_dsi_htotal = (u64)fbdiv * pll_ref_hz * dsi_nlanes * - dpi_htotal; - - /* - * Do the division in 2 steps to avoid an overflow on the - * divider. - */ - rem = do_div(adj_dsi_htotal, dpi_hz); - if (rem) - continue; - - rem = do_div(adj_dsi_htotal, - cfg->pll_opdiv * cfg->pll_ipdiv * 2 * 8); - if (rem) - continue; - - cfg->pll_fbdiv = fbdiv; - *dsi_hfp_ext = adj_dsi_htotal - dsi_htotal; - break; - } - - /* No match, let's just reject the display mode. */ - if (!cfg->pll_fbdiv) - return -EINVAL; - - dlane_bps = DIV_ROUND_DOWN_ULL((u64)dpi_hz * adj_dsi_htotal * 8, - dsi_nlanes * dpi_htotal); - cfg->lane_bps = dlane_bps; - - return 0; -} - -static int cdns_dphy_setup_psm(struct cdns_dphy *dphy) -{ - unsigned long psm_clk_hz = clk_get_rate(dphy->psm_clk); - unsigned long psm_div; - - if (!psm_clk_hz || psm_clk_hz > 100000000) - return -EINVAL; - - psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000); - if (dphy->ops->set_psm_div) - dphy->ops->set_psm_div(dphy, psm_div); - - return 0; -} - -static void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy, - enum cdns_dphy_clk_lane_cfg cfg) -{ - if (dphy->ops->set_clk_lane_cfg) - dphy->ops->set_clk_lane_cfg(dphy, cfg); -} - -static void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy, - const struct cdns_dphy_cfg *cfg) -{ - if (dphy->ops->set_pll_cfg) - dphy->ops->set_pll_cfg(dphy, cfg); -} - -static unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy) -{ - return dphy->ops->get_wakeup_time_ns(dphy); -} - static unsigned int dpi_to_dsi_timing(unsigned int dpi_timing, unsigned int dpi_bpp, unsigned int dsi_pkt_overhead) @@ -780,17 +545,20 @@ static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi, return 0; }
-static int cdns_dphy_validate(struct cdns_dsi *dsi, +static int cdns_dsi_adjust_phy_config(struct cdns_dsi *dsi, struct cdns_dsi_cfg *dsi_cfg, - struct cdns_dphy_cfg *dphy_cfg, + struct phy_configure_opts_mipi_dphy *phy_cfg, const struct drm_display_mode *mode, bool mode_valid_check) { struct cdns_dsi_output *output = &dsi->output; + unsigned long long dlane_bps; + unsigned long adj_dsi_htotal; unsigned long dsi_htotal; - unsigned int dsi_hfp_ext = 0; - - int ret; + unsigned long dpi_htotal; + unsigned long dpi_hz; + unsigned int dsi_hfp_ext; + unsigned int lanes = output->dev->lanes;
dsi_htotal = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD; if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) @@ -799,25 +567,27 @@ static int cdns_dphy_validate(struct cdns_dsi *dsi, dsi_htotal += dsi_cfg->hact; dsi_htotal += dsi_cfg->hfp + DSI_HFP_FRAME_OVERHEAD;
- if (mode_valid_check) - ret = cdns_dsi_get_dphy_pll_cfg(dsi->dphy, dphy_cfg, - mode->htotal, - mode->clock * 1000, - mipi_dsi_pixel_format_to_bpp(output->dev->format), - dsi_htotal, - output->dev->lanes, - &dsi_hfp_ext); - else - ret = cdns_dsi_get_dphy_pll_cfg(dsi->dphy, dphy_cfg, - mode->crtc_htotal, - mipi_dsi_pixel_format_to_bpp(output->dev->format), - mode->crtc_clock * 1000, - dsi_htotal, - output->dev->lanes, - &dsi_hfp_ext); - if (ret) - return ret; + /* + * Make sure DSI htotal is aligned on a lane boundary when calculating + * the expected data rate. This is done by extending HFP in case of + * misalignment. + */ + adj_dsi_htotal = dsi_htotal; + if (dsi_htotal % lanes) + adj_dsi_htotal += lanes - (dsi_htotal % lanes); + + dpi_hz = (mode_valid_check ? mode->clock : mode->crtc_clock) * 1000; + dlane_bps = (unsigned long long)dpi_hz * adj_dsi_htotal; + + /* data rate in bytes/sec is not an integer, refuse the mode. */ + dpi_htotal = mode_valid_check ? mode->htotal : mode->crtc_htotal; + if (do_div(dlane_bps, lanes * dpi_htotal)) + return -EINVAL;
+ /* data rate was in bytes/sec, convert to bits/sec. */ + phy_cfg->hs_clk_rate = dlane_bps * 8; + + dsi_hfp_ext = adj_dsi_htotal - dsi_htotal; dsi_cfg->hfp += dsi_hfp_ext; dsi_cfg->htotal = dsi_htotal + dsi_hfp_ext;
@@ -827,10 +597,10 @@ static int cdns_dphy_validate(struct cdns_dsi *dsi, static int cdns_dsi_check_conf(struct cdns_dsi *dsi, const struct drm_display_mode *mode, struct cdns_dsi_cfg *dsi_cfg, - struct cdns_dphy_cfg *dphy_cfg, bool mode_valid_check) { struct cdns_dsi_output *output = &dsi->output; + struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy; unsigned long dsi_hss_hsa_hse_hbp; unsigned int nlanes = output->dev->lanes; int ret; @@ -839,7 +609,15 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi, if (ret) return ret;
- ret = cdns_dphy_validate(dsi, dsi_cfg, dphy_cfg, mode, mode_valid_check); + phy_mipi_dphy_get_default_config(mode->crtc_clock * 1000, + mipi_dsi_pixel_format_to_bpp(output->dev->format), + nlanes, phy_cfg); + + ret = cdns_dsi_adjust_phy_config(dsi, dsi_cfg, phy_cfg, mode, mode_valid_check); + if (ret) + return ret; + + ret = phy_validate(dsi->dphy, PHY_MODE_MIPI_DPHY, 0, &output->phy_opts); if (ret) return ret;
@@ -852,7 +630,7 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi, * is empty before we start a receiving a new line on the DPI * interface. */ - if ((u64)dphy_cfg->lane_bps * mode_to_dpi_hfp(mode) * nlanes < + if ((u64)phy_cfg->hs_clk_rate * mode_to_dpi_hfp(mode) * nlanes < (u64)dsi_hss_hsa_hse_hbp * (mode_valid_check ? mode->clock : mode->crtc_clock) * 1000) return -EINVAL; @@ -882,7 +660,6 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge, struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge); struct cdns_dsi *dsi = input_to_dsi(input); struct cdns_dsi_output *output = &dsi->output; - struct cdns_dphy_cfg dphy_cfg; struct cdns_dsi_cfg dsi_cfg; int bpp, ret;
@@ -902,7 +679,7 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge, if ((mode->hdisplay * bpp) % 32) return MODE_H_ILLEGAL;
- ret = cdns_dsi_check_conf(dsi, mode, &dsi_cfg, &dphy_cfg, true); + ret = cdns_dsi_check_conf(dsi, mode, &dsi_cfg, true); if (ret) return MODE_BAD;
@@ -925,9 +702,9 @@ static void cdns_dsi_bridge_disable(struct drm_bridge *bridge) pm_runtime_put(dsi->base.dev); }
-static void cdns_dsi_hs_init(struct cdns_dsi *dsi, - const struct cdns_dphy_cfg *dphy_cfg) +static void cdns_dsi_hs_init(struct cdns_dsi *dsi) { + struct cdns_dsi_output *output = &dsi->output; u32 status;
/* @@ -938,30 +715,10 @@ static void cdns_dsi_hs_init(struct cdns_dsi *dsi, DPHY_CMN_PDN | DPHY_PLL_PDN, dsi->regs + MCTL_DPHY_CFG0);
- /* - * Configure the internal PSM clk divider so that the DPHY has a - * 1MHz clk (or something close). - */ - WARN_ON_ONCE(cdns_dphy_setup_psm(dsi->dphy)); - - /* - * Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes - * and 8 data lanes, each clk lane can be attache different set of - * data lanes. The 2 groups are named 'left' and 'right', so here we - * just say that we want the 'left' clk lane to drive the 'left' data - * lanes. - */ - cdns_dphy_set_clk_lane_cfg(dsi->dphy, DPHY_CLK_CFG_LEFT_DRIVES_LEFT); - - /* - * Configure the DPHY PLL that will be used to generate the TX byte - * clk. - */ - cdns_dphy_set_pll_cfg(dsi->dphy, dphy_cfg); - - /* Start TX state machine. */ - writel(DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN, - dsi->dphy->regs + DPHY_CMN_SSM); + phy_init(dsi->dphy); + phy_set_mode(dsi->dphy, PHY_MODE_MIPI_DPHY); + phy_configure(dsi->dphy, &output->phy_opts); + phy_power_on(dsi->dphy);
/* Activate the PLL and wait until it's locked. */ writel(PLL_LOCKED, dsi->regs + MCTL_MAIN_STS_CLR); @@ -971,7 +728,7 @@ static void cdns_dsi_hs_init(struct cdns_dsi *dsi, status & PLL_LOCKED, 100, 100)); /* De-assert data and clock reset lines. */ writel(DPHY_CMN_PSO | DPHY_ALL_D_PDN | DPHY_C_PDN | DPHY_CMN_PDN | - DPHY_D_RSTB(dphy_cfg->nlanes) | DPHY_C_RSTB, + DPHY_D_RSTB(output->dev->lanes) | DPHY_C_RSTB, dsi->regs + MCTL_DPHY_CFG0); }
@@ -1017,7 +774,7 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge) struct cdns_dsi *dsi = input_to_dsi(input); struct cdns_dsi_output *output = &dsi->output; struct drm_display_mode *mode; - struct cdns_dphy_cfg dphy_cfg; + struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy; unsigned long tx_byte_period; struct cdns_dsi_cfg dsi_cfg; u32 tmp, reg_wakeup, div; @@ -1030,9 +787,9 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge) bpp = mipi_dsi_pixel_format_to_bpp(output->dev->format); nlanes = output->dev->lanes;
- WARN_ON_ONCE(cdns_dsi_check_conf(dsi, mode, &dsi_cfg, &dphy_cfg, false)); + WARN_ON_ONCE(cdns_dsi_check_conf(dsi, mode, &dsi_cfg, false));
- cdns_dsi_hs_init(dsi, &dphy_cfg); + cdns_dsi_hs_init(dsi); cdns_dsi_init_link(dsi);
writel(HBP_LEN(dsi_cfg.hbp) | HSA_LEN(dsi_cfg.hsa), @@ -1068,9 +825,8 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge) tmp -= DIV_ROUND_UP(DSI_EOT_PKT_SIZE, nlanes);
tx_byte_period = DIV_ROUND_DOWN_ULL((u64)NSEC_PER_SEC * 8, - dphy_cfg.lane_bps); - reg_wakeup = cdns_dphy_get_wakeup_time_ns(dsi->dphy) / - tx_byte_period; + phy_cfg->hs_clk_rate); + reg_wakeup = (phy_cfg->hs_prepare + phy_cfg->hs_zero) / tx_byte_period; writel(REG_WAKEUP_TIME(reg_wakeup) | REG_LINE_DURATION(tmp), dsi->regs + VID_DPHY_TIME);
@@ -1384,8 +1140,6 @@ static int __maybe_unused cdns_dsi_resume(struct device *dev) reset_control_deassert(dsi->dsi_p_rst); clk_prepare_enable(dsi->dsi_p_clk); clk_prepare_enable(dsi->dsi_sys_clk); - clk_prepare_enable(dsi->dphy->psm_clk); - clk_prepare_enable(dsi->dphy->pll_ref_clk);
return 0; } @@ -1394,8 +1148,6 @@ static int __maybe_unused cdns_dsi_suspend(struct device *dev) { struct cdns_dsi *dsi = dev_get_drvdata(dev);
- clk_disable_unprepare(dsi->dphy->pll_ref_clk); - clk_disable_unprepare(dsi->dphy->psm_clk); clk_disable_unprepare(dsi->dsi_sys_clk); clk_disable_unprepare(dsi->dsi_p_clk); reset_control_assert(dsi->dsi_p_rst); @@ -1406,121 +1158,6 @@ static int __maybe_unused cdns_dsi_suspend(struct device *dev) static UNIVERSAL_DEV_PM_OPS(cdns_dsi_pm_ops, cdns_dsi_suspend, cdns_dsi_resume, NULL);
-static unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy) -{ - /* Default wakeup time is 800 ns (in a simulated environment). */ - return 800; -} - -static void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy, - const struct cdns_dphy_cfg *cfg) -{ - u32 fbdiv_low, fbdiv_high; - - fbdiv_low = (cfg->pll_fbdiv / 4) - 2; - fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2; - - writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG | - DPHY_CMN_IPDIV(cfg->pll_ipdiv) | - DPHY_CMN_OPDIV(cfg->pll_opdiv), - dphy->regs + DPHY_CMN_OPIPDIV); - writel(DPHY_CMN_FBDIV_FROM_REG | - DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high), - dphy->regs + DPHY_CMN_FBDIV); - writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) | - DPHY_CMN_PWM_DIV(0x8), - dphy->regs + DPHY_CMN_PWM); -} - -static void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div) -{ - writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div), - dphy->regs + DPHY_PSM_CFG); -} - -/* - * This is the reference implementation of DPHY hooks. Specific integration of - * this IP may have to re-implement some of them depending on how they decided - * to wire things in the SoC. - */ -static const struct cdns_dphy_ops ref_dphy_ops = { - .get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns, - .set_pll_cfg = cdns_dphy_ref_set_pll_cfg, - .set_psm_div = cdns_dphy_ref_set_psm_div, -}; - -static const struct of_device_id cdns_dphy_of_match[] = { - { .compatible = "cdns,dphy", .data = &ref_dphy_ops }, - { /* sentinel */ }, -}; - -static struct cdns_dphy *cdns_dphy_probe(struct platform_device *pdev) -{ - const struct of_device_id *match; - struct cdns_dphy *dphy; - struct of_phandle_args args; - struct resource res; - int ret; - - ret = of_parse_phandle_with_args(pdev->dev.of_node, "phys", - "#phy-cells", 0, &args); - if (ret) - return ERR_PTR(-ENOENT); - - match = of_match_node(cdns_dphy_of_match, args.np); - if (!match || !match->data) - return ERR_PTR(-EINVAL); - - dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL); - if (!dphy) - return ERR_PTR(-ENOMEM); - - dphy->ops = match->data; - - ret = of_address_to_resource(args.np, 0, &res); - if (ret) - return ERR_PTR(ret); - - dphy->regs = devm_ioremap_resource(&pdev->dev, &res); - if (IS_ERR(dphy->regs)) - return ERR_CAST(dphy->regs); - - dphy->psm_clk = of_clk_get_by_name(args.np, "psm"); - if (IS_ERR(dphy->psm_clk)) - return ERR_CAST(dphy->psm_clk); - - dphy->pll_ref_clk = of_clk_get_by_name(args.np, "pll_ref"); - if (IS_ERR(dphy->pll_ref_clk)) { - ret = PTR_ERR(dphy->pll_ref_clk); - goto err_put_psm_clk; - } - - if (dphy->ops->probe) { - ret = dphy->ops->probe(dphy); - if (ret) - goto err_put_pll_ref_clk; - } - - return dphy; - -err_put_pll_ref_clk: - clk_put(dphy->pll_ref_clk); - -err_put_psm_clk: - clk_put(dphy->psm_clk); - - return ERR_PTR(ret); -} - -static void cdns_dphy_remove(struct cdns_dphy *dphy) -{ - if (dphy->ops->remove) - dphy->ops->remove(dphy); - - clk_put(dphy->pll_ref_clk); - clk_put(dphy->psm_clk); -} - static int cdns_dsi_drm_probe(struct platform_device *pdev) { struct cdns_dsi *dsi; @@ -1559,13 +1196,13 @@ static int cdns_dsi_drm_probe(struct platform_device *pdev) if (irq < 0) return irq;
- dsi->dphy = cdns_dphy_probe(pdev); + dsi->dphy = devm_phy_get(&pdev->dev, "dphy"); if (IS_ERR(dsi->dphy)) return PTR_ERR(dsi->dphy);
ret = clk_prepare_enable(dsi->dsi_p_clk); if (ret) - goto err_remove_dphy; + return ret;
val = readl(dsi->regs + ID_REG); if (REV_VENDOR_ID(val) != 0xcad) { @@ -1623,9 +1260,6 @@ static int cdns_dsi_drm_probe(struct platform_device *pdev) err_disable_pclk: clk_disable_unprepare(dsi->dsi_p_clk);
-err_remove_dphy: - cdns_dphy_remove(dsi->dphy); - return ret; }
@@ -1635,7 +1269,6 @@ static int cdns_dsi_drm_remove(struct platform_device *pdev)
mipi_dsi_host_unregister(&dsi->base); pm_runtime_disable(&pdev->dev); - cdns_dphy_remove(dsi->dphy);
return 0; } diff --git a/drivers/phy/cadence/cdns-dphy.c b/drivers/phy/cadence/cdns-dphy.c index 1d0abba03f37..cde12b3aa4d4 100644 --- a/drivers/phy/cadence/cdns-dphy.c +++ b/drivers/phy/cadence/cdns-dphy.c @@ -227,7 +227,7 @@ static int cdns_dphy_config_from_opts(struct phy *phy, if (ret) return ret;
- opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) * 1000; + opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) / 1000;
return 0; }
Hi,
On 09/01/19 3:03 PM, Maxime Ripard wrote:
Now that we have everything we need in the phy framework to allow to tune the phy parameters, let's convert the Cadence DSI bridge to that API instead of creating a ad-hoc driver for its phy.
Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com
For this too, need ACKs from DRM MAINTAINERS.
Thanks Kishon
drivers/gpu/drm/bridge/Kconfig | 1 +- drivers/gpu/drm/bridge/cdns-dsi.c | 485 +++---------------------------- drivers/phy/cadence/cdns-dphy.c | 2 +- 3 files changed, 61 insertions(+), 427 deletions(-)
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 2fee47b0d50b..8840f396a7b6 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -30,6 +30,7 @@ config DRM_CDNS_DSI select DRM_KMS_HELPER select DRM_MIPI_DSI select DRM_PANEL_BRIDGE
- select GENERIC_PHY_MIPI_DPHY depends on OF help Support Cadence DPI to DSI bridge. This is an internal
diff --git a/drivers/gpu/drm/bridge/cdns-dsi.c b/drivers/gpu/drm/bridge/cdns-dsi.c index 3ac6dd524b6d..7b432257ffbe 100644 --- a/drivers/gpu/drm/bridge/cdns-dsi.c +++ b/drivers/gpu/drm/bridge/cdns-dsi.c @@ -21,6 +21,9 @@ #include <linux/pm_runtime.h> #include <linux/reset.h>
+#include <linux/phy/phy.h> +#include <linux/phy/phy-mipi-dphy.h>
#define IP_CONF 0x0 #define SP_HS_FIFO_DEPTH(x) (((x) & GENMASK(30, 26)) >> 26) #define SP_LP_FIFO_DEPTH(x) (((x) & GENMASK(25, 21)) >> 21) @@ -419,44 +422,11 @@ #define DSI_NULL_FRAME_OVERHEAD 6 #define DSI_EOT_PKT_SIZE 4
-#define REG_WAKEUP_TIME_NS 800 -#define DPHY_PLL_RATE_HZ 108000000
-/* DPHY registers */ -#define DPHY_PMA_CMN(reg) (reg) -#define DPHY_PMA_LCLK(reg) (0x100 + (reg)) -#define DPHY_PMA_LDATA(lane, reg) (0x200 + ((lane) * 0x100) + (reg)) -#define DPHY_PMA_RCLK(reg) (0x600 + (reg)) -#define DPHY_PMA_RDATA(lane, reg) (0x700 + ((lane) * 0x100) + (reg)) -#define DPHY_PCS(reg) (0xb00 + (reg))
-#define DPHY_CMN_SSM DPHY_PMA_CMN(0x20) -#define DPHY_CMN_SSM_EN BIT(0) -#define DPHY_CMN_TX_MODE_EN BIT(9)
-#define DPHY_CMN_PWM DPHY_PMA_CMN(0x40) -#define DPHY_CMN_PWM_DIV(x) ((x) << 20) -#define DPHY_CMN_PWM_LOW(x) ((x) << 10) -#define DPHY_CMN_PWM_HIGH(x) (x)
-#define DPHY_CMN_FBDIV DPHY_PMA_CMN(0x4c) -#define DPHY_CMN_FBDIV_VAL(low, high) (((high) << 11) | ((low) << 22)) -#define DPHY_CMN_FBDIV_FROM_REG (BIT(10) | BIT(21))
-#define DPHY_CMN_OPIPDIV DPHY_PMA_CMN(0x50) -#define DPHY_CMN_IPDIV_FROM_REG BIT(0) -#define DPHY_CMN_IPDIV(x) ((x) << 1) -#define DPHY_CMN_OPDIV_FROM_REG BIT(6) -#define DPHY_CMN_OPDIV(x) ((x) << 7)
-#define DPHY_PSM_CFG DPHY_PCS(0x4) -#define DPHY_PSM_CFG_FROM_REG BIT(0) -#define DPHY_PSM_CLK_DIV(x) ((x) << 1)
struct cdns_dsi_output { struct mipi_dsi_device *dev; struct drm_panel *panel; struct drm_bridge *bridge;
- union phy_configure_opts phy_opts;
};
enum cdns_dsi_input_id { @@ -465,14 +435,6 @@ enum cdns_dsi_input_id { CDNS_DSC_INPUT, };
-struct cdns_dphy_cfg {
- u8 pll_ipdiv;
- u8 pll_opdiv;
- u16 pll_fbdiv;
- unsigned long lane_bps;
- unsigned int nlanes;
-};
struct cdns_dsi_cfg { unsigned int hfp; unsigned int hsa; @@ -481,34 +443,6 @@ struct cdns_dsi_cfg { unsigned int htotal; };
-struct cdns_dphy;
-enum cdns_dphy_clk_lane_cfg {
- DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0,
- DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1,
- DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2,
- DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3,
-};
-struct cdns_dphy_ops {
- int (*probe)(struct cdns_dphy *dphy);
- void (*remove)(struct cdns_dphy *dphy);
- void (*set_psm_div)(struct cdns_dphy *dphy, u8 div);
- void (*set_clk_lane_cfg)(struct cdns_dphy *dphy,
enum cdns_dphy_clk_lane_cfg cfg);
- void (*set_pll_cfg)(struct cdns_dphy *dphy,
const struct cdns_dphy_cfg *cfg);
- unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy);
-};
-struct cdns_dphy {
- struct cdns_dphy_cfg cfg;
- void __iomem *regs;
- struct clk *psm_clk;
- struct clk *pll_ref_clk;
- const struct cdns_dphy_ops *ops;
-};
struct cdns_dsi_input { enum cdns_dsi_input_id id; struct drm_bridge bridge; @@ -526,7 +460,7 @@ struct cdns_dsi { struct reset_control *dsi_p_rst; struct clk *dsi_sys_clk; bool link_initialized;
- struct cdns_dphy *dphy;
- struct phy *dphy;
};
static inline struct cdns_dsi *input_to_dsi(struct cdns_dsi_input *input) @@ -550,175 +484,6 @@ static unsigned int mode_to_dpi_hfp(const struct drm_display_mode *mode) return mode->crtc_hsync_start - mode->crtc_hdisplay; }
-static int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy,
struct cdns_dphy_cfg *cfg,
unsigned int dpi_htotal,
unsigned int dpi_bpp,
unsigned int dpi_hz,
unsigned int dsi_htotal,
unsigned int dsi_nlanes,
unsigned int *dsi_hfp_ext)
-{
- u64 dlane_bps, dlane_bps_max, fbdiv, fbdiv_max, adj_dsi_htotal;
- unsigned long pll_ref_hz = clk_get_rate(dphy->pll_ref_clk);
- memset(cfg, 0, sizeof(*cfg));
- cfg->nlanes = dsi_nlanes;
- if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000)
return -EINVAL;
- else if (pll_ref_hz < 19200000)
cfg->pll_ipdiv = 1;
- else if (pll_ref_hz < 38400000)
cfg->pll_ipdiv = 2;
- else if (pll_ref_hz < 76800000)
cfg->pll_ipdiv = 4;
- else
cfg->pll_ipdiv = 8;
- /*
* Make sure DSI htotal is aligned on a lane boundary when calculating
* the expected data rate. This is done by extending HFP in case of
* misalignment.
*/
- adj_dsi_htotal = dsi_htotal;
- if (dsi_htotal % dsi_nlanes)
adj_dsi_htotal += dsi_nlanes - (dsi_htotal % dsi_nlanes);
- dlane_bps = (u64)dpi_hz * adj_dsi_htotal;
- /* data rate in bytes/sec is not an integer, refuse the mode. */
- if (do_div(dlane_bps, dsi_nlanes * dpi_htotal))
return -EINVAL;
- /* data rate was in bytes/sec, convert to bits/sec. */
- dlane_bps *= 8;
- if (dlane_bps > 2500000000UL || dlane_bps < 160000000UL)
return -EINVAL;
- else if (dlane_bps >= 1250000000)
cfg->pll_opdiv = 1;
- else if (dlane_bps >= 630000000)
cfg->pll_opdiv = 2;
- else if (dlane_bps >= 320000000)
cfg->pll_opdiv = 4;
- else if (dlane_bps >= 160000000)
cfg->pll_opdiv = 8;
- /*
* Allow a deviation of 0.2% on the per-lane data rate to try to
* recover a potential mismatch between DPI and PPI clks.
*/
- dlane_bps_max = dlane_bps + DIV_ROUND_DOWN_ULL(dlane_bps, 500);
- fbdiv_max = DIV_ROUND_DOWN_ULL(dlane_bps_max * 2 *
cfg->pll_opdiv * cfg->pll_ipdiv,
pll_ref_hz);
- fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv *
cfg->pll_ipdiv,
pll_ref_hz);
- /*
* Iterate over all acceptable fbdiv and try to find an adjusted DSI
* htotal length providing an exact match.
*
* Note that we could do something even trickier by relying on the fact
* that a new line is not necessarily aligned on a lane boundary, so,
* by making adj_dsi_htotal non aligned on a dsi_lanes we can improve a
* bit the precision. With this, the step would be
*
* pll_ref_hz / (2 * opdiv * ipdiv * nlanes)
*
* instead of
*
* pll_ref_hz / (2 * opdiv * ipdiv)
*
* The drawback of this approach is that we would need to make sure the
* number or lines is a multiple of the realignment periodicity which is
* a function of the number of lanes and the original misalignment. For
* example, for NLANES = 4 and HTOTAL % NLANES = 3, it takes 4 lines
* to realign on a lane:
* LINE 0: expected number of bytes, starts emitting first byte of
* LINE 1 on LANE 3
* LINE 1: expected number of bytes, starts emitting first 2 bytes of
* LINE 2 on LANES 2 and 3
* LINE 2: expected number of bytes, starts emitting first 3 bytes of
* of LINE 3 on LANES 1, 2 and 3
* LINE 3: one byte less, now things are realigned on LANE 0 for LINE 4
*
* I figured this extra complexity was not worth the benefit, but if
* someone really has unfixable mismatch, that would be something to
* investigate.
*/
- for (; fbdiv <= fbdiv_max; fbdiv++) {
u32 rem;
adj_dsi_htotal = (u64)fbdiv * pll_ref_hz * dsi_nlanes *
dpi_htotal;
/*
* Do the division in 2 steps to avoid an overflow on the
* divider.
*/
rem = do_div(adj_dsi_htotal, dpi_hz);
if (rem)
continue;
rem = do_div(adj_dsi_htotal,
cfg->pll_opdiv * cfg->pll_ipdiv * 2 * 8);
if (rem)
continue;
cfg->pll_fbdiv = fbdiv;
*dsi_hfp_ext = adj_dsi_htotal - dsi_htotal;
break;
- }
- /* No match, let's just reject the display mode. */
- if (!cfg->pll_fbdiv)
return -EINVAL;
- dlane_bps = DIV_ROUND_DOWN_ULL((u64)dpi_hz * adj_dsi_htotal * 8,
dsi_nlanes * dpi_htotal);
- cfg->lane_bps = dlane_bps;
- return 0;
-}
-static int cdns_dphy_setup_psm(struct cdns_dphy *dphy) -{
- unsigned long psm_clk_hz = clk_get_rate(dphy->psm_clk);
- unsigned long psm_div;
- if (!psm_clk_hz || psm_clk_hz > 100000000)
return -EINVAL;
- psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000);
- if (dphy->ops->set_psm_div)
dphy->ops->set_psm_div(dphy, psm_div);
- return 0;
-}
-static void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy,
enum cdns_dphy_clk_lane_cfg cfg)
-{
- if (dphy->ops->set_clk_lane_cfg)
dphy->ops->set_clk_lane_cfg(dphy, cfg);
-}
-static void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy,
const struct cdns_dphy_cfg *cfg)
-{
- if (dphy->ops->set_pll_cfg)
dphy->ops->set_pll_cfg(dphy, cfg);
-}
-static unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy) -{
- return dphy->ops->get_wakeup_time_ns(dphy);
-}
static unsigned int dpi_to_dsi_timing(unsigned int dpi_timing, unsigned int dpi_bpp, unsigned int dsi_pkt_overhead) @@ -780,17 +545,20 @@ static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi, return 0; }
-static int cdns_dphy_validate(struct cdns_dsi *dsi, +static int cdns_dsi_adjust_phy_config(struct cdns_dsi *dsi, struct cdns_dsi_cfg *dsi_cfg,
struct cdns_dphy_cfg *dphy_cfg,
struct phy_configure_opts_mipi_dphy *phy_cfg, const struct drm_display_mode *mode, bool mode_valid_check)
{ struct cdns_dsi_output *output = &dsi->output;
- unsigned long long dlane_bps;
- unsigned long adj_dsi_htotal; unsigned long dsi_htotal;
- unsigned int dsi_hfp_ext = 0;
- int ret;
unsigned long dpi_htotal;
unsigned long dpi_hz;
unsigned int dsi_hfp_ext;
unsigned int lanes = output->dev->lanes;
dsi_htotal = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD; if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
@@ -799,25 +567,27 @@ static int cdns_dphy_validate(struct cdns_dsi *dsi, dsi_htotal += dsi_cfg->hact; dsi_htotal += dsi_cfg->hfp + DSI_HFP_FRAME_OVERHEAD;
- if (mode_valid_check)
ret = cdns_dsi_get_dphy_pll_cfg(dsi->dphy, dphy_cfg,
mode->htotal,
mode->clock * 1000,
mipi_dsi_pixel_format_to_bpp(output->dev->format),
dsi_htotal,
output->dev->lanes,
&dsi_hfp_ext);
- else
ret = cdns_dsi_get_dphy_pll_cfg(dsi->dphy, dphy_cfg,
mode->crtc_htotal,
mipi_dsi_pixel_format_to_bpp(output->dev->format),
mode->crtc_clock * 1000,
dsi_htotal,
output->dev->lanes,
&dsi_hfp_ext);
- if (ret)
return ret;
/*
* Make sure DSI htotal is aligned on a lane boundary when calculating
* the expected data rate. This is done by extending HFP in case of
* misalignment.
*/
adj_dsi_htotal = dsi_htotal;
if (dsi_htotal % lanes)
adj_dsi_htotal += lanes - (dsi_htotal % lanes);
dpi_hz = (mode_valid_check ? mode->clock : mode->crtc_clock) * 1000;
dlane_bps = (unsigned long long)dpi_hz * adj_dsi_htotal;
/* data rate in bytes/sec is not an integer, refuse the mode. */
dpi_htotal = mode_valid_check ? mode->htotal : mode->crtc_htotal;
if (do_div(dlane_bps, lanes * dpi_htotal))
return -EINVAL;
/* data rate was in bytes/sec, convert to bits/sec. */
phy_cfg->hs_clk_rate = dlane_bps * 8;
dsi_hfp_ext = adj_dsi_htotal - dsi_htotal; dsi_cfg->hfp += dsi_hfp_ext; dsi_cfg->htotal = dsi_htotal + dsi_hfp_ext;
@@ -827,10 +597,10 @@ static int cdns_dphy_validate(struct cdns_dsi *dsi, static int cdns_dsi_check_conf(struct cdns_dsi *dsi, const struct drm_display_mode *mode, struct cdns_dsi_cfg *dsi_cfg,
struct cdns_dphy_cfg *dphy_cfg, bool mode_valid_check)
{ struct cdns_dsi_output *output = &dsi->output;
- struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy; unsigned long dsi_hss_hsa_hse_hbp; unsigned int nlanes = output->dev->lanes; int ret;
@@ -839,7 +609,15 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi, if (ret) return ret;
- ret = cdns_dphy_validate(dsi, dsi_cfg, dphy_cfg, mode, mode_valid_check);
- phy_mipi_dphy_get_default_config(mode->crtc_clock * 1000,
mipi_dsi_pixel_format_to_bpp(output->dev->format),
nlanes, phy_cfg);
- ret = cdns_dsi_adjust_phy_config(dsi, dsi_cfg, phy_cfg, mode, mode_valid_check);
- if (ret)
return ret;
- ret = phy_validate(dsi->dphy, PHY_MODE_MIPI_DPHY, 0, &output->phy_opts); if (ret) return ret;
@@ -852,7 +630,7 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi, * is empty before we start a receiving a new line on the DPI * interface. */
- if ((u64)dphy_cfg->lane_bps * mode_to_dpi_hfp(mode) * nlanes <
- if ((u64)phy_cfg->hs_clk_rate * mode_to_dpi_hfp(mode) * nlanes < (u64)dsi_hss_hsa_hse_hbp * (mode_valid_check ? mode->clock : mode->crtc_clock) * 1000) return -EINVAL;
@@ -882,7 +660,6 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge, struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge); struct cdns_dsi *dsi = input_to_dsi(input); struct cdns_dsi_output *output = &dsi->output;
- struct cdns_dphy_cfg dphy_cfg; struct cdns_dsi_cfg dsi_cfg; int bpp, ret;
@@ -902,7 +679,7 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge, if ((mode->hdisplay * bpp) % 32) return MODE_H_ILLEGAL;
- ret = cdns_dsi_check_conf(dsi, mode, &dsi_cfg, &dphy_cfg, true);
- ret = cdns_dsi_check_conf(dsi, mode, &dsi_cfg, true); if (ret) return MODE_BAD;
@@ -925,9 +702,9 @@ static void cdns_dsi_bridge_disable(struct drm_bridge *bridge) pm_runtime_put(dsi->base.dev); }
-static void cdns_dsi_hs_init(struct cdns_dsi *dsi,
const struct cdns_dphy_cfg *dphy_cfg)
+static void cdns_dsi_hs_init(struct cdns_dsi *dsi) {
struct cdns_dsi_output *output = &dsi->output; u32 status;
/*
@@ -938,30 +715,10 @@ static void cdns_dsi_hs_init(struct cdns_dsi *dsi, DPHY_CMN_PDN | DPHY_PLL_PDN, dsi->regs + MCTL_DPHY_CFG0);
- /*
* Configure the internal PSM clk divider so that the DPHY has a
* 1MHz clk (or something close).
*/
- WARN_ON_ONCE(cdns_dphy_setup_psm(dsi->dphy));
- /*
* Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes
* and 8 data lanes, each clk lane can be attache different set of
* data lanes. The 2 groups are named 'left' and 'right', so here we
* just say that we want the 'left' clk lane to drive the 'left' data
* lanes.
*/
- cdns_dphy_set_clk_lane_cfg(dsi->dphy, DPHY_CLK_CFG_LEFT_DRIVES_LEFT);
- /*
* Configure the DPHY PLL that will be used to generate the TX byte
* clk.
*/
- cdns_dphy_set_pll_cfg(dsi->dphy, dphy_cfg);
- /* Start TX state machine. */
- writel(DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN,
dsi->dphy->regs + DPHY_CMN_SSM);
phy_init(dsi->dphy);
phy_set_mode(dsi->dphy, PHY_MODE_MIPI_DPHY);
phy_configure(dsi->dphy, &output->phy_opts);
phy_power_on(dsi->dphy);
/* Activate the PLL and wait until it's locked. */ writel(PLL_LOCKED, dsi->regs + MCTL_MAIN_STS_CLR);
@@ -971,7 +728,7 @@ static void cdns_dsi_hs_init(struct cdns_dsi *dsi, status & PLL_LOCKED, 100, 100)); /* De-assert data and clock reset lines. */ writel(DPHY_CMN_PSO | DPHY_ALL_D_PDN | DPHY_C_PDN | DPHY_CMN_PDN |
DPHY_D_RSTB(dphy_cfg->nlanes) | DPHY_C_RSTB,
DPHY_D_RSTB(output->dev->lanes) | DPHY_C_RSTB, dsi->regs + MCTL_DPHY_CFG0);
}
@@ -1017,7 +774,7 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge) struct cdns_dsi *dsi = input_to_dsi(input); struct cdns_dsi_output *output = &dsi->output; struct drm_display_mode *mode;
- struct cdns_dphy_cfg dphy_cfg;
- struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy; unsigned long tx_byte_period; struct cdns_dsi_cfg dsi_cfg; u32 tmp, reg_wakeup, div;
@@ -1030,9 +787,9 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge) bpp = mipi_dsi_pixel_format_to_bpp(output->dev->format); nlanes = output->dev->lanes;
- WARN_ON_ONCE(cdns_dsi_check_conf(dsi, mode, &dsi_cfg, &dphy_cfg, false));
- WARN_ON_ONCE(cdns_dsi_check_conf(dsi, mode, &dsi_cfg, false));
- cdns_dsi_hs_init(dsi, &dphy_cfg);
cdns_dsi_hs_init(dsi); cdns_dsi_init_link(dsi);
writel(HBP_LEN(dsi_cfg.hbp) | HSA_LEN(dsi_cfg.hsa),
@@ -1068,9 +825,8 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge) tmp -= DIV_ROUND_UP(DSI_EOT_PKT_SIZE, nlanes);
tx_byte_period = DIV_ROUND_DOWN_ULL((u64)NSEC_PER_SEC * 8,
dphy_cfg.lane_bps);
- reg_wakeup = cdns_dphy_get_wakeup_time_ns(dsi->dphy) /
tx_byte_period;
phy_cfg->hs_clk_rate);
- reg_wakeup = (phy_cfg->hs_prepare + phy_cfg->hs_zero) / tx_byte_period; writel(REG_WAKEUP_TIME(reg_wakeup) | REG_LINE_DURATION(tmp), dsi->regs + VID_DPHY_TIME);
@@ -1384,8 +1140,6 @@ static int __maybe_unused cdns_dsi_resume(struct device *dev) reset_control_deassert(dsi->dsi_p_rst); clk_prepare_enable(dsi->dsi_p_clk); clk_prepare_enable(dsi->dsi_sys_clk);
clk_prepare_enable(dsi->dphy->psm_clk);
clk_prepare_enable(dsi->dphy->pll_ref_clk);
return 0;
} @@ -1394,8 +1148,6 @@ static int __maybe_unused cdns_dsi_suspend(struct device *dev) { struct cdns_dsi *dsi = dev_get_drvdata(dev);
- clk_disable_unprepare(dsi->dphy->pll_ref_clk);
- clk_disable_unprepare(dsi->dphy->psm_clk); clk_disable_unprepare(dsi->dsi_sys_clk); clk_disable_unprepare(dsi->dsi_p_clk); reset_control_assert(dsi->dsi_p_rst);
@@ -1406,121 +1158,6 @@ static int __maybe_unused cdns_dsi_suspend(struct device *dev) static UNIVERSAL_DEV_PM_OPS(cdns_dsi_pm_ops, cdns_dsi_suspend, cdns_dsi_resume, NULL);
-static unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy) -{
- /* Default wakeup time is 800 ns (in a simulated environment). */
- return 800;
-}
-static void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy,
const struct cdns_dphy_cfg *cfg)
-{
- u32 fbdiv_low, fbdiv_high;
- fbdiv_low = (cfg->pll_fbdiv / 4) - 2;
- fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2;
- writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG |
DPHY_CMN_IPDIV(cfg->pll_ipdiv) |
DPHY_CMN_OPDIV(cfg->pll_opdiv),
dphy->regs + DPHY_CMN_OPIPDIV);
- writel(DPHY_CMN_FBDIV_FROM_REG |
DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high),
dphy->regs + DPHY_CMN_FBDIV);
- writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) |
DPHY_CMN_PWM_DIV(0x8),
dphy->regs + DPHY_CMN_PWM);
-}
-static void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div) -{
- writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div),
dphy->regs + DPHY_PSM_CFG);
-}
-/*
- This is the reference implementation of DPHY hooks. Specific integration of
- this IP may have to re-implement some of them depending on how they decided
- to wire things in the SoC.
- */
-static const struct cdns_dphy_ops ref_dphy_ops = {
- .get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns,
- .set_pll_cfg = cdns_dphy_ref_set_pll_cfg,
- .set_psm_div = cdns_dphy_ref_set_psm_div,
-};
-static const struct of_device_id cdns_dphy_of_match[] = {
- { .compatible = "cdns,dphy", .data = &ref_dphy_ops },
- { /* sentinel */ },
-};
-static struct cdns_dphy *cdns_dphy_probe(struct platform_device *pdev) -{
- const struct of_device_id *match;
- struct cdns_dphy *dphy;
- struct of_phandle_args args;
- struct resource res;
- int ret;
- ret = of_parse_phandle_with_args(pdev->dev.of_node, "phys",
"#phy-cells", 0, &args);
- if (ret)
return ERR_PTR(-ENOENT);
- match = of_match_node(cdns_dphy_of_match, args.np);
- if (!match || !match->data)
return ERR_PTR(-EINVAL);
- dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
- if (!dphy)
return ERR_PTR(-ENOMEM);
- dphy->ops = match->data;
- ret = of_address_to_resource(args.np, 0, &res);
- if (ret)
return ERR_PTR(ret);
- dphy->regs = devm_ioremap_resource(&pdev->dev, &res);
- if (IS_ERR(dphy->regs))
return ERR_CAST(dphy->regs);
- dphy->psm_clk = of_clk_get_by_name(args.np, "psm");
- if (IS_ERR(dphy->psm_clk))
return ERR_CAST(dphy->psm_clk);
- dphy->pll_ref_clk = of_clk_get_by_name(args.np, "pll_ref");
- if (IS_ERR(dphy->pll_ref_clk)) {
ret = PTR_ERR(dphy->pll_ref_clk);
goto err_put_psm_clk;
- }
- if (dphy->ops->probe) {
ret = dphy->ops->probe(dphy);
if (ret)
goto err_put_pll_ref_clk;
- }
- return dphy;
-err_put_pll_ref_clk:
- clk_put(dphy->pll_ref_clk);
-err_put_psm_clk:
- clk_put(dphy->psm_clk);
- return ERR_PTR(ret);
-}
-static void cdns_dphy_remove(struct cdns_dphy *dphy) -{
- if (dphy->ops->remove)
dphy->ops->remove(dphy);
- clk_put(dphy->pll_ref_clk);
- clk_put(dphy->psm_clk);
-}
static int cdns_dsi_drm_probe(struct platform_device *pdev) { struct cdns_dsi *dsi; @@ -1559,13 +1196,13 @@ static int cdns_dsi_drm_probe(struct platform_device *pdev) if (irq < 0) return irq;
- dsi->dphy = cdns_dphy_probe(pdev);
dsi->dphy = devm_phy_get(&pdev->dev, "dphy"); if (IS_ERR(dsi->dphy)) return PTR_ERR(dsi->dphy);
ret = clk_prepare_enable(dsi->dsi_p_clk); if (ret)
goto err_remove_dphy;
return ret;
val = readl(dsi->regs + ID_REG); if (REV_VENDOR_ID(val) != 0xcad) {
@@ -1623,9 +1260,6 @@ static int cdns_dsi_drm_probe(struct platform_device *pdev) err_disable_pclk: clk_disable_unprepare(dsi->dsi_p_clk);
-err_remove_dphy:
- cdns_dphy_remove(dsi->dphy);
- return ret;
}
@@ -1635,7 +1269,6 @@ static int cdns_dsi_drm_remove(struct platform_device *pdev)
mipi_dsi_host_unregister(&dsi->base); pm_runtime_disable(&pdev->dev);
cdns_dphy_remove(dsi->dphy);
return 0;
} diff --git a/drivers/phy/cadence/cdns-dphy.c b/drivers/phy/cadence/cdns-dphy.c index 1d0abba03f37..cde12b3aa4d4 100644 --- a/drivers/phy/cadence/cdns-dphy.c +++ b/drivers/phy/cadence/cdns-dphy.c @@ -227,7 +227,7 @@ static int cdns_dphy_config_from_opts(struct phy *phy, if (ret) return ret;
- opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) * 1000;
opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) / 1000;
return 0;
}
On Wed, Jan 09, 2019 at 10:33:26AM +0100, Maxime Ripard wrote:
Now that we have everything we need in the phy framework to allow to tune the phy parameters, let's convert the Cadence DSI bridge to that API instead of creating a ad-hoc driver for its phy.
Signed-off-by: Maxime Ripard maxime.ripard@bootlin.com
Aside from the wakeup change mentioned in patch 8,
Acked-by: Sean Paul sean@poorly.run
drivers/gpu/drm/bridge/Kconfig | 1 +- drivers/gpu/drm/bridge/cdns-dsi.c | 485 +++---------------------------- drivers/phy/cadence/cdns-dphy.c | 2 +- 3 files changed, 61 insertions(+), 427 deletions(-)
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 2fee47b0d50b..8840f396a7b6 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -30,6 +30,7 @@ config DRM_CDNS_DSI select DRM_KMS_HELPER select DRM_MIPI_DSI select DRM_PANEL_BRIDGE
- select GENERIC_PHY_MIPI_DPHY depends on OF help Support Cadence DPI to DSI bridge. This is an internal
diff --git a/drivers/gpu/drm/bridge/cdns-dsi.c b/drivers/gpu/drm/bridge/cdns-dsi.c index 3ac6dd524b6d..7b432257ffbe 100644 --- a/drivers/gpu/drm/bridge/cdns-dsi.c +++ b/drivers/gpu/drm/bridge/cdns-dsi.c @@ -21,6 +21,9 @@ #include <linux/pm_runtime.h> #include <linux/reset.h>
+#include <linux/phy/phy.h> +#include <linux/phy/phy-mipi-dphy.h>
#define IP_CONF 0x0 #define SP_HS_FIFO_DEPTH(x) (((x) & GENMASK(30, 26)) >> 26) #define SP_LP_FIFO_DEPTH(x) (((x) & GENMASK(25, 21)) >> 21) @@ -419,44 +422,11 @@ #define DSI_NULL_FRAME_OVERHEAD 6 #define DSI_EOT_PKT_SIZE 4
-#define REG_WAKEUP_TIME_NS 800 -#define DPHY_PLL_RATE_HZ 108000000
-/* DPHY registers */ -#define DPHY_PMA_CMN(reg) (reg) -#define DPHY_PMA_LCLK(reg) (0x100 + (reg)) -#define DPHY_PMA_LDATA(lane, reg) (0x200 + ((lane) * 0x100) + (reg)) -#define DPHY_PMA_RCLK(reg) (0x600 + (reg)) -#define DPHY_PMA_RDATA(lane, reg) (0x700 + ((lane) * 0x100) + (reg)) -#define DPHY_PCS(reg) (0xb00 + (reg))
-#define DPHY_CMN_SSM DPHY_PMA_CMN(0x20) -#define DPHY_CMN_SSM_EN BIT(0) -#define DPHY_CMN_TX_MODE_EN BIT(9)
-#define DPHY_CMN_PWM DPHY_PMA_CMN(0x40) -#define DPHY_CMN_PWM_DIV(x) ((x) << 20) -#define DPHY_CMN_PWM_LOW(x) ((x) << 10) -#define DPHY_CMN_PWM_HIGH(x) (x)
-#define DPHY_CMN_FBDIV DPHY_PMA_CMN(0x4c) -#define DPHY_CMN_FBDIV_VAL(low, high) (((high) << 11) | ((low) << 22)) -#define DPHY_CMN_FBDIV_FROM_REG (BIT(10) | BIT(21))
-#define DPHY_CMN_OPIPDIV DPHY_PMA_CMN(0x50) -#define DPHY_CMN_IPDIV_FROM_REG BIT(0) -#define DPHY_CMN_IPDIV(x) ((x) << 1) -#define DPHY_CMN_OPDIV_FROM_REG BIT(6) -#define DPHY_CMN_OPDIV(x) ((x) << 7)
-#define DPHY_PSM_CFG DPHY_PCS(0x4) -#define DPHY_PSM_CFG_FROM_REG BIT(0) -#define DPHY_PSM_CLK_DIV(x) ((x) << 1)
struct cdns_dsi_output { struct mipi_dsi_device *dev; struct drm_panel *panel; struct drm_bridge *bridge;
- union phy_configure_opts phy_opts;
};
enum cdns_dsi_input_id { @@ -465,14 +435,6 @@ enum cdns_dsi_input_id { CDNS_DSC_INPUT, };
-struct cdns_dphy_cfg {
- u8 pll_ipdiv;
- u8 pll_opdiv;
- u16 pll_fbdiv;
- unsigned long lane_bps;
- unsigned int nlanes;
-};
struct cdns_dsi_cfg { unsigned int hfp; unsigned int hsa; @@ -481,34 +443,6 @@ struct cdns_dsi_cfg { unsigned int htotal; };
-struct cdns_dphy;
-enum cdns_dphy_clk_lane_cfg {
- DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0,
- DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1,
- DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2,
- DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3,
-};
-struct cdns_dphy_ops {
- int (*probe)(struct cdns_dphy *dphy);
- void (*remove)(struct cdns_dphy *dphy);
- void (*set_psm_div)(struct cdns_dphy *dphy, u8 div);
- void (*set_clk_lane_cfg)(struct cdns_dphy *dphy,
enum cdns_dphy_clk_lane_cfg cfg);
- void (*set_pll_cfg)(struct cdns_dphy *dphy,
const struct cdns_dphy_cfg *cfg);
- unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy);
-};
-struct cdns_dphy {
- struct cdns_dphy_cfg cfg;
- void __iomem *regs;
- struct clk *psm_clk;
- struct clk *pll_ref_clk;
- const struct cdns_dphy_ops *ops;
-};
struct cdns_dsi_input { enum cdns_dsi_input_id id; struct drm_bridge bridge; @@ -526,7 +460,7 @@ struct cdns_dsi { struct reset_control *dsi_p_rst; struct clk *dsi_sys_clk; bool link_initialized;
- struct cdns_dphy *dphy;
- struct phy *dphy;
};
static inline struct cdns_dsi *input_to_dsi(struct cdns_dsi_input *input) @@ -550,175 +484,6 @@ static unsigned int mode_to_dpi_hfp(const struct drm_display_mode *mode) return mode->crtc_hsync_start - mode->crtc_hdisplay; }
-static int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy,
struct cdns_dphy_cfg *cfg,
unsigned int dpi_htotal,
unsigned int dpi_bpp,
unsigned int dpi_hz,
unsigned int dsi_htotal,
unsigned int dsi_nlanes,
unsigned int *dsi_hfp_ext)
-{
- u64 dlane_bps, dlane_bps_max, fbdiv, fbdiv_max, adj_dsi_htotal;
- unsigned long pll_ref_hz = clk_get_rate(dphy->pll_ref_clk);
- memset(cfg, 0, sizeof(*cfg));
- cfg->nlanes = dsi_nlanes;
- if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000)
return -EINVAL;
- else if (pll_ref_hz < 19200000)
cfg->pll_ipdiv = 1;
- else if (pll_ref_hz < 38400000)
cfg->pll_ipdiv = 2;
- else if (pll_ref_hz < 76800000)
cfg->pll_ipdiv = 4;
- else
cfg->pll_ipdiv = 8;
- /*
* Make sure DSI htotal is aligned on a lane boundary when calculating
* the expected data rate. This is done by extending HFP in case of
* misalignment.
*/
- adj_dsi_htotal = dsi_htotal;
- if (dsi_htotal % dsi_nlanes)
adj_dsi_htotal += dsi_nlanes - (dsi_htotal % dsi_nlanes);
- dlane_bps = (u64)dpi_hz * adj_dsi_htotal;
- /* data rate in bytes/sec is not an integer, refuse the mode. */
- if (do_div(dlane_bps, dsi_nlanes * dpi_htotal))
return -EINVAL;
- /* data rate was in bytes/sec, convert to bits/sec. */
- dlane_bps *= 8;
- if (dlane_bps > 2500000000UL || dlane_bps < 160000000UL)
return -EINVAL;
- else if (dlane_bps >= 1250000000)
cfg->pll_opdiv = 1;
- else if (dlane_bps >= 630000000)
cfg->pll_opdiv = 2;
- else if (dlane_bps >= 320000000)
cfg->pll_opdiv = 4;
- else if (dlane_bps >= 160000000)
cfg->pll_opdiv = 8;
- /*
* Allow a deviation of 0.2% on the per-lane data rate to try to
* recover a potential mismatch between DPI and PPI clks.
*/
- dlane_bps_max = dlane_bps + DIV_ROUND_DOWN_ULL(dlane_bps, 500);
- fbdiv_max = DIV_ROUND_DOWN_ULL(dlane_bps_max * 2 *
cfg->pll_opdiv * cfg->pll_ipdiv,
pll_ref_hz);
- fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv *
cfg->pll_ipdiv,
pll_ref_hz);
- /*
* Iterate over all acceptable fbdiv and try to find an adjusted DSI
* htotal length providing an exact match.
*
* Note that we could do something even trickier by relying on the fact
* that a new line is not necessarily aligned on a lane boundary, so,
* by making adj_dsi_htotal non aligned on a dsi_lanes we can improve a
* bit the precision. With this, the step would be
*
* pll_ref_hz / (2 * opdiv * ipdiv * nlanes)
*
* instead of
*
* pll_ref_hz / (2 * opdiv * ipdiv)
*
* The drawback of this approach is that we would need to make sure the
* number or lines is a multiple of the realignment periodicity which is
* a function of the number of lanes and the original misalignment. For
* example, for NLANES = 4 and HTOTAL % NLANES = 3, it takes 4 lines
* to realign on a lane:
* LINE 0: expected number of bytes, starts emitting first byte of
* LINE 1 on LANE 3
* LINE 1: expected number of bytes, starts emitting first 2 bytes of
* LINE 2 on LANES 2 and 3
* LINE 2: expected number of bytes, starts emitting first 3 bytes of
* of LINE 3 on LANES 1, 2 and 3
* LINE 3: one byte less, now things are realigned on LANE 0 for LINE 4
*
* I figured this extra complexity was not worth the benefit, but if
* someone really has unfixable mismatch, that would be something to
* investigate.
*/
- for (; fbdiv <= fbdiv_max; fbdiv++) {
u32 rem;
adj_dsi_htotal = (u64)fbdiv * pll_ref_hz * dsi_nlanes *
dpi_htotal;
/*
* Do the division in 2 steps to avoid an overflow on the
* divider.
*/
rem = do_div(adj_dsi_htotal, dpi_hz);
if (rem)
continue;
rem = do_div(adj_dsi_htotal,
cfg->pll_opdiv * cfg->pll_ipdiv * 2 * 8);
if (rem)
continue;
cfg->pll_fbdiv = fbdiv;
*dsi_hfp_ext = adj_dsi_htotal - dsi_htotal;
break;
- }
- /* No match, let's just reject the display mode. */
- if (!cfg->pll_fbdiv)
return -EINVAL;
- dlane_bps = DIV_ROUND_DOWN_ULL((u64)dpi_hz * adj_dsi_htotal * 8,
dsi_nlanes * dpi_htotal);
- cfg->lane_bps = dlane_bps;
- return 0;
-}
-static int cdns_dphy_setup_psm(struct cdns_dphy *dphy) -{
- unsigned long psm_clk_hz = clk_get_rate(dphy->psm_clk);
- unsigned long psm_div;
- if (!psm_clk_hz || psm_clk_hz > 100000000)
return -EINVAL;
- psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000);
- if (dphy->ops->set_psm_div)
dphy->ops->set_psm_div(dphy, psm_div);
- return 0;
-}
-static void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy,
enum cdns_dphy_clk_lane_cfg cfg)
-{
- if (dphy->ops->set_clk_lane_cfg)
dphy->ops->set_clk_lane_cfg(dphy, cfg);
-}
-static void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy,
const struct cdns_dphy_cfg *cfg)
-{
- if (dphy->ops->set_pll_cfg)
dphy->ops->set_pll_cfg(dphy, cfg);
-}
-static unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy) -{
- return dphy->ops->get_wakeup_time_ns(dphy);
-}
static unsigned int dpi_to_dsi_timing(unsigned int dpi_timing, unsigned int dpi_bpp, unsigned int dsi_pkt_overhead) @@ -780,17 +545,20 @@ static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi, return 0; }
-static int cdns_dphy_validate(struct cdns_dsi *dsi, +static int cdns_dsi_adjust_phy_config(struct cdns_dsi *dsi, struct cdns_dsi_cfg *dsi_cfg,
struct cdns_dphy_cfg *dphy_cfg,
struct phy_configure_opts_mipi_dphy *phy_cfg, const struct drm_display_mode *mode, bool mode_valid_check)
{ struct cdns_dsi_output *output = &dsi->output;
- unsigned long long dlane_bps;
- unsigned long adj_dsi_htotal; unsigned long dsi_htotal;
- unsigned int dsi_hfp_ext = 0;
- int ret;
unsigned long dpi_htotal;
unsigned long dpi_hz;
unsigned int dsi_hfp_ext;
unsigned int lanes = output->dev->lanes;
dsi_htotal = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD; if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
@@ -799,25 +567,27 @@ static int cdns_dphy_validate(struct cdns_dsi *dsi, dsi_htotal += dsi_cfg->hact; dsi_htotal += dsi_cfg->hfp + DSI_HFP_FRAME_OVERHEAD;
- if (mode_valid_check)
ret = cdns_dsi_get_dphy_pll_cfg(dsi->dphy, dphy_cfg,
mode->htotal,
mode->clock * 1000,
mipi_dsi_pixel_format_to_bpp(output->dev->format),
dsi_htotal,
output->dev->lanes,
&dsi_hfp_ext);
- else
ret = cdns_dsi_get_dphy_pll_cfg(dsi->dphy, dphy_cfg,
mode->crtc_htotal,
mipi_dsi_pixel_format_to_bpp(output->dev->format),
mode->crtc_clock * 1000,
dsi_htotal,
output->dev->lanes,
&dsi_hfp_ext);
- if (ret)
return ret;
/*
* Make sure DSI htotal is aligned on a lane boundary when calculating
* the expected data rate. This is done by extending HFP in case of
* misalignment.
*/
adj_dsi_htotal = dsi_htotal;
if (dsi_htotal % lanes)
adj_dsi_htotal += lanes - (dsi_htotal % lanes);
dpi_hz = (mode_valid_check ? mode->clock : mode->crtc_clock) * 1000;
dlane_bps = (unsigned long long)dpi_hz * adj_dsi_htotal;
/* data rate in bytes/sec is not an integer, refuse the mode. */
dpi_htotal = mode_valid_check ? mode->htotal : mode->crtc_htotal;
if (do_div(dlane_bps, lanes * dpi_htotal))
return -EINVAL;
/* data rate was in bytes/sec, convert to bits/sec. */
phy_cfg->hs_clk_rate = dlane_bps * 8;
dsi_hfp_ext = adj_dsi_htotal - dsi_htotal; dsi_cfg->hfp += dsi_hfp_ext; dsi_cfg->htotal = dsi_htotal + dsi_hfp_ext;
@@ -827,10 +597,10 @@ static int cdns_dphy_validate(struct cdns_dsi *dsi, static int cdns_dsi_check_conf(struct cdns_dsi *dsi, const struct drm_display_mode *mode, struct cdns_dsi_cfg *dsi_cfg,
struct cdns_dphy_cfg *dphy_cfg, bool mode_valid_check)
{ struct cdns_dsi_output *output = &dsi->output;
- struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy; unsigned long dsi_hss_hsa_hse_hbp; unsigned int nlanes = output->dev->lanes; int ret;
@@ -839,7 +609,15 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi, if (ret) return ret;
- ret = cdns_dphy_validate(dsi, dsi_cfg, dphy_cfg, mode, mode_valid_check);
- phy_mipi_dphy_get_default_config(mode->crtc_clock * 1000,
mipi_dsi_pixel_format_to_bpp(output->dev->format),
nlanes, phy_cfg);
- ret = cdns_dsi_adjust_phy_config(dsi, dsi_cfg, phy_cfg, mode, mode_valid_check);
- if (ret)
return ret;
- ret = phy_validate(dsi->dphy, PHY_MODE_MIPI_DPHY, 0, &output->phy_opts); if (ret) return ret;
@@ -852,7 +630,7 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi, * is empty before we start a receiving a new line on the DPI * interface. */
- if ((u64)dphy_cfg->lane_bps * mode_to_dpi_hfp(mode) * nlanes <
- if ((u64)phy_cfg->hs_clk_rate * mode_to_dpi_hfp(mode) * nlanes < (u64)dsi_hss_hsa_hse_hbp * (mode_valid_check ? mode->clock : mode->crtc_clock) * 1000) return -EINVAL;
@@ -882,7 +660,6 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge, struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge); struct cdns_dsi *dsi = input_to_dsi(input); struct cdns_dsi_output *output = &dsi->output;
- struct cdns_dphy_cfg dphy_cfg; struct cdns_dsi_cfg dsi_cfg; int bpp, ret;
@@ -902,7 +679,7 @@ cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge, if ((mode->hdisplay * bpp) % 32) return MODE_H_ILLEGAL;
- ret = cdns_dsi_check_conf(dsi, mode, &dsi_cfg, &dphy_cfg, true);
- ret = cdns_dsi_check_conf(dsi, mode, &dsi_cfg, true); if (ret) return MODE_BAD;
@@ -925,9 +702,9 @@ static void cdns_dsi_bridge_disable(struct drm_bridge *bridge) pm_runtime_put(dsi->base.dev); }
-static void cdns_dsi_hs_init(struct cdns_dsi *dsi,
const struct cdns_dphy_cfg *dphy_cfg)
+static void cdns_dsi_hs_init(struct cdns_dsi *dsi) {
struct cdns_dsi_output *output = &dsi->output; u32 status;
/*
@@ -938,30 +715,10 @@ static void cdns_dsi_hs_init(struct cdns_dsi *dsi, DPHY_CMN_PDN | DPHY_PLL_PDN, dsi->regs + MCTL_DPHY_CFG0);
- /*
* Configure the internal PSM clk divider so that the DPHY has a
* 1MHz clk (or something close).
*/
- WARN_ON_ONCE(cdns_dphy_setup_psm(dsi->dphy));
- /*
* Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes
* and 8 data lanes, each clk lane can be attache different set of
* data lanes. The 2 groups are named 'left' and 'right', so here we
* just say that we want the 'left' clk lane to drive the 'left' data
* lanes.
*/
- cdns_dphy_set_clk_lane_cfg(dsi->dphy, DPHY_CLK_CFG_LEFT_DRIVES_LEFT);
- /*
* Configure the DPHY PLL that will be used to generate the TX byte
* clk.
*/
- cdns_dphy_set_pll_cfg(dsi->dphy, dphy_cfg);
- /* Start TX state machine. */
- writel(DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN,
dsi->dphy->regs + DPHY_CMN_SSM);
phy_init(dsi->dphy);
phy_set_mode(dsi->dphy, PHY_MODE_MIPI_DPHY);
phy_configure(dsi->dphy, &output->phy_opts);
phy_power_on(dsi->dphy);
/* Activate the PLL and wait until it's locked. */ writel(PLL_LOCKED, dsi->regs + MCTL_MAIN_STS_CLR);
@@ -971,7 +728,7 @@ static void cdns_dsi_hs_init(struct cdns_dsi *dsi, status & PLL_LOCKED, 100, 100)); /* De-assert data and clock reset lines. */ writel(DPHY_CMN_PSO | DPHY_ALL_D_PDN | DPHY_C_PDN | DPHY_CMN_PDN |
DPHY_D_RSTB(dphy_cfg->nlanes) | DPHY_C_RSTB,
DPHY_D_RSTB(output->dev->lanes) | DPHY_C_RSTB, dsi->regs + MCTL_DPHY_CFG0);
}
@@ -1017,7 +774,7 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge) struct cdns_dsi *dsi = input_to_dsi(input); struct cdns_dsi_output *output = &dsi->output; struct drm_display_mode *mode;
- struct cdns_dphy_cfg dphy_cfg;
- struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy; unsigned long tx_byte_period; struct cdns_dsi_cfg dsi_cfg; u32 tmp, reg_wakeup, div;
@@ -1030,9 +787,9 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge) bpp = mipi_dsi_pixel_format_to_bpp(output->dev->format); nlanes = output->dev->lanes;
- WARN_ON_ONCE(cdns_dsi_check_conf(dsi, mode, &dsi_cfg, &dphy_cfg, false));
- WARN_ON_ONCE(cdns_dsi_check_conf(dsi, mode, &dsi_cfg, false));
- cdns_dsi_hs_init(dsi, &dphy_cfg);
cdns_dsi_hs_init(dsi); cdns_dsi_init_link(dsi);
writel(HBP_LEN(dsi_cfg.hbp) | HSA_LEN(dsi_cfg.hsa),
@@ -1068,9 +825,8 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge) tmp -= DIV_ROUND_UP(DSI_EOT_PKT_SIZE, nlanes);
tx_byte_period = DIV_ROUND_DOWN_ULL((u64)NSEC_PER_SEC * 8,
dphy_cfg.lane_bps);
- reg_wakeup = cdns_dphy_get_wakeup_time_ns(dsi->dphy) /
tx_byte_period;
phy_cfg->hs_clk_rate);
- reg_wakeup = (phy_cfg->hs_prepare + phy_cfg->hs_zero) / tx_byte_period; writel(REG_WAKEUP_TIME(reg_wakeup) | REG_LINE_DURATION(tmp), dsi->regs + VID_DPHY_TIME);
@@ -1384,8 +1140,6 @@ static int __maybe_unused cdns_dsi_resume(struct device *dev) reset_control_deassert(dsi->dsi_p_rst); clk_prepare_enable(dsi->dsi_p_clk); clk_prepare_enable(dsi->dsi_sys_clk);
clk_prepare_enable(dsi->dphy->psm_clk);
clk_prepare_enable(dsi->dphy->pll_ref_clk);
return 0;
} @@ -1394,8 +1148,6 @@ static int __maybe_unused cdns_dsi_suspend(struct device *dev) { struct cdns_dsi *dsi = dev_get_drvdata(dev);
- clk_disable_unprepare(dsi->dphy->pll_ref_clk);
- clk_disable_unprepare(dsi->dphy->psm_clk); clk_disable_unprepare(dsi->dsi_sys_clk); clk_disable_unprepare(dsi->dsi_p_clk); reset_control_assert(dsi->dsi_p_rst);
@@ -1406,121 +1158,6 @@ static int __maybe_unused cdns_dsi_suspend(struct device *dev) static UNIVERSAL_DEV_PM_OPS(cdns_dsi_pm_ops, cdns_dsi_suspend, cdns_dsi_resume, NULL);
-static unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy) -{
- /* Default wakeup time is 800 ns (in a simulated environment). */
- return 800;
-}
-static void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy,
const struct cdns_dphy_cfg *cfg)
-{
- u32 fbdiv_low, fbdiv_high;
- fbdiv_low = (cfg->pll_fbdiv / 4) - 2;
- fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2;
- writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG |
DPHY_CMN_IPDIV(cfg->pll_ipdiv) |
DPHY_CMN_OPDIV(cfg->pll_opdiv),
dphy->regs + DPHY_CMN_OPIPDIV);
- writel(DPHY_CMN_FBDIV_FROM_REG |
DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high),
dphy->regs + DPHY_CMN_FBDIV);
- writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) |
DPHY_CMN_PWM_DIV(0x8),
dphy->regs + DPHY_CMN_PWM);
-}
-static void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div) -{
- writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div),
dphy->regs + DPHY_PSM_CFG);
-}
-/*
- This is the reference implementation of DPHY hooks. Specific integration of
- this IP may have to re-implement some of them depending on how they decided
- to wire things in the SoC.
- */
-static const struct cdns_dphy_ops ref_dphy_ops = {
- .get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns,
- .set_pll_cfg = cdns_dphy_ref_set_pll_cfg,
- .set_psm_div = cdns_dphy_ref_set_psm_div,
-};
-static const struct of_device_id cdns_dphy_of_match[] = {
- { .compatible = "cdns,dphy", .data = &ref_dphy_ops },
- { /* sentinel */ },
-};
-static struct cdns_dphy *cdns_dphy_probe(struct platform_device *pdev) -{
- const struct of_device_id *match;
- struct cdns_dphy *dphy;
- struct of_phandle_args args;
- struct resource res;
- int ret;
- ret = of_parse_phandle_with_args(pdev->dev.of_node, "phys",
"#phy-cells", 0, &args);
- if (ret)
return ERR_PTR(-ENOENT);
- match = of_match_node(cdns_dphy_of_match, args.np);
- if (!match || !match->data)
return ERR_PTR(-EINVAL);
- dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
- if (!dphy)
return ERR_PTR(-ENOMEM);
- dphy->ops = match->data;
- ret = of_address_to_resource(args.np, 0, &res);
- if (ret)
return ERR_PTR(ret);
- dphy->regs = devm_ioremap_resource(&pdev->dev, &res);
- if (IS_ERR(dphy->regs))
return ERR_CAST(dphy->regs);
- dphy->psm_clk = of_clk_get_by_name(args.np, "psm");
- if (IS_ERR(dphy->psm_clk))
return ERR_CAST(dphy->psm_clk);
- dphy->pll_ref_clk = of_clk_get_by_name(args.np, "pll_ref");
- if (IS_ERR(dphy->pll_ref_clk)) {
ret = PTR_ERR(dphy->pll_ref_clk);
goto err_put_psm_clk;
- }
- if (dphy->ops->probe) {
ret = dphy->ops->probe(dphy);
if (ret)
goto err_put_pll_ref_clk;
- }
- return dphy;
-err_put_pll_ref_clk:
- clk_put(dphy->pll_ref_clk);
-err_put_psm_clk:
- clk_put(dphy->psm_clk);
- return ERR_PTR(ret);
-}
-static void cdns_dphy_remove(struct cdns_dphy *dphy) -{
- if (dphy->ops->remove)
dphy->ops->remove(dphy);
- clk_put(dphy->pll_ref_clk);
- clk_put(dphy->psm_clk);
-}
static int cdns_dsi_drm_probe(struct platform_device *pdev) { struct cdns_dsi *dsi; @@ -1559,13 +1196,13 @@ static int cdns_dsi_drm_probe(struct platform_device *pdev) if (irq < 0) return irq;
- dsi->dphy = cdns_dphy_probe(pdev);
dsi->dphy = devm_phy_get(&pdev->dev, "dphy"); if (IS_ERR(dsi->dphy)) return PTR_ERR(dsi->dphy);
ret = clk_prepare_enable(dsi->dsi_p_clk); if (ret)
goto err_remove_dphy;
return ret;
val = readl(dsi->regs + ID_REG); if (REV_VENDOR_ID(val) != 0xcad) {
@@ -1623,9 +1260,6 @@ static int cdns_dsi_drm_probe(struct platform_device *pdev) err_disable_pclk: clk_disable_unprepare(dsi->dsi_p_clk);
-err_remove_dphy:
- cdns_dphy_remove(dsi->dphy);
- return ret;
}
@@ -1635,7 +1269,6 @@ static int cdns_dsi_drm_remove(struct platform_device *pdev)
mipi_dsi_host_unregister(&dsi->base); pm_runtime_disable(&pdev->dev);
cdns_dphy_remove(dsi->dphy);
return 0;
} diff --git a/drivers/phy/cadence/cdns-dphy.c b/drivers/phy/cadence/cdns-dphy.c index 1d0abba03f37..cde12b3aa4d4 100644 --- a/drivers/phy/cadence/cdns-dphy.c +++ b/drivers/phy/cadence/cdns-dphy.c @@ -227,7 +227,7 @@ static int cdns_dphy_config_from_opts(struct phy *phy, if (ret) return ret;
- opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) * 1000;
opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) / 1000;
return 0;
}
git-series 0.9.1 _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
On Wed, Jan 09, 2019 at 10:33:17AM +0100, Maxime Ripard wrote:
Hi,
Here is a set of patches to allow the phy framework consumers to test and apply runtime configurations.
This is needed to support more phy classes that require tuning based on parameters depending on the current use case of the device, in addition to the power state management already provided by the current functions.
A first test bed for that API are the MIPI D-PHY devices. There's a number of solutions that have been used so far to support these phy, most of the time being an ad-hoc driver in the consumer.
That approach has a big shortcoming though, which is that this is quite difficult to deal with consumers integrated with multiple variants of phy, of multiple consumers integrated with the same phy.
The latter case can be found in the Cadence DSI bridge, and the CSI transceiver and receivers. All of them are integrated with the same phy, or can be integrated with different phy, depending on the implementation.
I've looked at all the MIPI DSI drivers I could find, and gathered all the parameters I could find. The interface should be complete, and most of the drivers can be converted in the future. The current set converts two of them: the above mentionned Cadence DSI driver so that the v4l2 drivers can use them, and the Allwinner MIPI-DSI driver.
Let me know what you think, Maxime
Changes from v3
- Rebased on 5.0-rc1
- Added the fixes suggested by Sakari
Thanks!
For patches 1--3:
Acked-by: Sakari Ailus sakari.ailus@linux.intel.com
dri-devel@lists.freedesktop.org