summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/stm
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/stm')
-rw-r--r--drivers/gpu/drm/stm/Kconfig15
-rw-r--r--drivers/gpu/drm/stm/Makefile2
-rw-r--r--drivers/gpu/drm/stm/drv.c44
-rw-r--r--drivers/gpu/drm/stm/dw_mipi_dsi-stm.c289
-rw-r--r--drivers/gpu/drm/stm/ltdc.c314
-rw-r--r--drivers/gpu/drm/stm/ltdc.h6
-rw-r--r--drivers/gpu/drm/stm/lvds.c1222
7 files changed, 1682 insertions, 210 deletions
diff --git a/drivers/gpu/drm/stm/Kconfig b/drivers/gpu/drm/stm/Kconfig
index ded72f879482..635be0ac00af 100644
--- a/drivers/gpu/drm/stm/Kconfig
+++ b/drivers/gpu/drm/stm/Kconfig
@@ -1,7 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_STM
tristate "DRM Support for STMicroelectronics SoC Series"
- depends on DRM && (ARCH_STM32 || ARCH_MULTIPLATFORM)
+ depends on DRM && (ARCH_STM32 || COMPILE_TEST)
+ depends on COMMON_CLK
+ select DRM_CLIENT_SELECTION
select DRM_KMS_HELPER
select DRM_GEM_DMA_HELPER
select DRM_PANEL_BRIDGE
@@ -20,3 +22,14 @@ config DRM_STM_DSI
select DRM_DW_MIPI_DSI
help
Choose this option for MIPI DSI support on STMicroelectronics SoC.
+
+config DRM_STM_LVDS
+ tristate "STMicroelectronics LVDS Display Interface Transmitter DRM driver"
+ depends on DRM_STM
+ help
+ Enable support for LVDS encoders on STMicroelectronics SoC.
+ The STM LVDS is a bridge which serialize pixel stream onto
+ a LVDS protocol.
+
+ To compile this driver as a module, choose M here: the module will be
+ called lvds.
diff --git a/drivers/gpu/drm/stm/Makefile b/drivers/gpu/drm/stm/Makefile
index 4df5caf01f35..ad740d6175a6 100644
--- a/drivers/gpu/drm/stm/Makefile
+++ b/drivers/gpu/drm/stm/Makefile
@@ -5,4 +5,6 @@ stm-drm-y := \
obj-$(CONFIG_DRM_STM_DSI) += dw_mipi_dsi-stm.o
+obj-$(CONFIG_DRM_STM_LVDS) += lvds.o
+
obj-$(CONFIG_DRM_STM) += stm-drm.o
diff --git a/drivers/gpu/drm/stm/drv.c b/drivers/gpu/drm/stm/drv.c
index 50410bd99dfe..56d53ac3082d 100644
--- a/drivers/gpu/drm/stm/drv.c
+++ b/drivers/gpu/drm/stm/drv.c
@@ -8,22 +8,27 @@
* Mickael Reulier <mickael.reulier@st.com>
*/
+#include <linux/aperture.h>
#include <linux/component.h>
#include <linux/dma-mapping.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
-#include <linux/of_platform.h>
+#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
-#include <drm/drm_aperture.h>
+#include <drm/clients/drm_client_setup.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
-#include <drm/drm_fbdev_generic.h>
+#include <drm/drm_fbdev_dma.h>
+#include <drm/drm_fourcc.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_module.h>
+#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
+#include <drm/drm_managed.h>
#include "ltdc.h"
@@ -58,12 +63,12 @@ static const struct drm_driver drv_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
.name = "stm",
.desc = "STMicroelectronics SoC DRM",
- .date = "20170330",
.major = 1,
.minor = 0,
.patchlevel = 0,
.fops = &drv_driver_fops,
DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(stm_gem_dma_dumb_create),
+ DRM_FBDEV_DMA_DRIVER_OPS,
};
static int drv_load(struct drm_device *ddev)
@@ -74,7 +79,7 @@ static int drv_load(struct drm_device *ddev)
DRM_DEBUG("%s\n", __func__);
- ldev = devm_kzalloc(ddev->dev, sizeof(*ldev), GFP_KERNEL);
+ ldev = drmm_kzalloc(ddev, sizeof(*ldev), GFP_KERNEL);
if (!ldev)
return -ENOMEM;
@@ -113,6 +118,7 @@ static void drv_unload(struct drm_device *ddev)
DRM_DEBUG("%s\n", __func__);
drm_kms_helper_poll_fini(ddev);
+ drm_atomic_helper_shutdown(ddev);
ltdc_unload(ddev);
}
@@ -185,7 +191,7 @@ static int stm_drm_platform_probe(struct platform_device *pdev)
DRM_DEBUG("%s\n", __func__);
- ret = drm_aperture_remove_framebuffers(false, &drv_driver);
+ ret = aperture_remove_all_conflicting_devices(drv_driver.name);
if (ret)
return ret;
@@ -201,19 +207,21 @@ static int stm_drm_platform_probe(struct platform_device *pdev)
ret = drm_dev_register(ddev, 0);
if (ret)
- goto err_put;
+ goto err_unload;
- drm_fbdev_generic_setup(ddev, 16);
+ drm_client_setup_with_fourcc(ddev, DRM_FORMAT_RGB565);
return 0;
+err_unload:
+ drv_unload(ddev);
err_put:
drm_dev_put(ddev);
return ret;
}
-static int stm_drm_platform_remove(struct platform_device *pdev)
+static void stm_drm_platform_remove(struct platform_device *pdev)
{
struct drm_device *ddev = platform_get_drvdata(pdev);
@@ -222,12 +230,25 @@ static int stm_drm_platform_remove(struct platform_device *pdev)
drm_dev_unregister(ddev);
drv_unload(ddev);
drm_dev_put(ddev);
+}
- return 0;
+static void stm_drm_platform_shutdown(struct platform_device *pdev)
+{
+ drm_atomic_helper_shutdown(platform_get_drvdata(pdev));
}
+static struct ltdc_plat_data stm_drm_plat_data = {
+ .pad_max_freq_hz = 90000000,
+};
+
+static struct ltdc_plat_data stm_drm_plat_data_mp25 = {
+ .pad_max_freq_hz = 150000000,
+};
+
static const struct of_device_id drv_dt_ids[] = {
- { .compatible = "st,stm32-ltdc"},
+ { .compatible = "st,stm32-ltdc", .data = &stm_drm_plat_data, },
+ { .compatible = "st,stm32mp251-ltdc", .data = &stm_drm_plat_data_mp25, },
+ { .compatible = "st,stm32mp255-ltdc", .data = &stm_drm_plat_data_mp25, },
{ /* end node */ },
};
MODULE_DEVICE_TABLE(of, drv_dt_ids);
@@ -235,6 +256,7 @@ MODULE_DEVICE_TABLE(of, drv_dt_ids);
static struct platform_driver stm_drm_platform_driver = {
.probe = stm_drm_platform_probe,
.remove = stm_drm_platform_remove,
+ .shutdown = stm_drm_platform_shutdown,
.driver = {
.name = "stm32-display",
.of_match_table = drv_dt_ids,
diff --git a/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c b/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c
index 89897d5f5c72..58eae6804cc8 100644
--- a/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c
+++ b/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c
@@ -7,10 +7,13 @@
*/
#include <linux/clk.h>
+#include <linux/clk-provider.h>
#include <linux/iopoll.h>
+#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <video/mipi_display.h>
@@ -76,8 +79,12 @@ enum dsi_color {
struct dw_mipi_dsi_stm {
void __iomem *base;
+ struct device *dev;
struct clk *pllref_clk;
+ struct clk *pclk;
+ struct clk_hw txbyte_clk;
struct dw_mipi_dsi *dsi;
+ struct dw_mipi_dsi_plat_data pdata;
u32 hw_version;
int lane_min_kbps;
int lane_max_kbps;
@@ -194,29 +201,200 @@ static int dsi_pll_get_params(struct dw_mipi_dsi_stm *dsi,
return 0;
}
-static int dw_mipi_dsi_phy_init(void *priv_data)
+#define clk_to_dw_mipi_dsi_stm(clk) \
+ container_of(clk, struct dw_mipi_dsi_stm, txbyte_clk)
+
+static void dw_mipi_dsi_clk_disable(struct clk_hw *clk)
{
- struct dw_mipi_dsi_stm *dsi = priv_data;
+ struct dw_mipi_dsi_stm *dsi = clk_to_dw_mipi_dsi_stm(clk);
+
+ DRM_DEBUG_DRIVER("\n");
+
+ /* Disable the DSI PLL */
+ dsi_clear(dsi, DSI_WRPCR, WRPCR_PLLEN);
+
+ /* Disable the regulator */
+ dsi_clear(dsi, DSI_WRPCR, WRPCR_REGEN | WRPCR_BGREN);
+}
+
+static int dw_mipi_dsi_clk_enable(struct clk_hw *clk)
+{
+ struct dw_mipi_dsi_stm *dsi = clk_to_dw_mipi_dsi_stm(clk);
u32 val;
int ret;
+ DRM_DEBUG_DRIVER("\n");
+
/* Enable the regulator */
dsi_set(dsi, DSI_WRPCR, WRPCR_REGEN | WRPCR_BGREN);
- ret = readl_poll_timeout(dsi->base + DSI_WISR, val, val & WISR_RRS,
- SLEEP_US, TIMEOUT_US);
+ ret = readl_poll_timeout_atomic(dsi->base + DSI_WISR, val, val & WISR_RRS,
+ SLEEP_US, TIMEOUT_US);
if (ret)
DRM_DEBUG_DRIVER("!TIMEOUT! waiting REGU, let's continue\n");
/* Enable the DSI PLL & wait for its lock */
dsi_set(dsi, DSI_WRPCR, WRPCR_PLLEN);
- ret = readl_poll_timeout(dsi->base + DSI_WISR, val, val & WISR_PLLLS,
- SLEEP_US, TIMEOUT_US);
+ ret = readl_poll_timeout_atomic(dsi->base + DSI_WISR, val, val & WISR_PLLLS,
+ SLEEP_US, TIMEOUT_US);
if (ret)
DRM_DEBUG_DRIVER("!TIMEOUT! waiting PLL, let's continue\n");
return 0;
}
+static int dw_mipi_dsi_clk_is_enabled(struct clk_hw *hw)
+{
+ struct dw_mipi_dsi_stm *dsi = clk_to_dw_mipi_dsi_stm(hw);
+
+ return dsi_read(dsi, DSI_WRPCR) & WRPCR_PLLEN;
+}
+
+static unsigned long dw_mipi_dsi_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct dw_mipi_dsi_stm *dsi = clk_to_dw_mipi_dsi_stm(hw);
+ unsigned int idf, ndiv, odf, pll_in_khz, pll_out_khz;
+ u32 val;
+
+ DRM_DEBUG_DRIVER("\n");
+
+ pll_in_khz = (unsigned int)(parent_rate / 1000);
+
+ val = dsi_read(dsi, DSI_WRPCR);
+
+ idf = (val & WRPCR_IDF) >> 11;
+ if (!idf)
+ idf = 1;
+ ndiv = (val & WRPCR_NDIV) >> 2;
+ odf = int_pow(2, (val & WRPCR_ODF) >> 16);
+
+ /* Get the adjusted pll out value */
+ pll_out_khz = dsi_pll_get_clkout_khz(pll_in_khz, idf, ndiv, odf);
+
+ return (unsigned long)pll_out_khz * 1000;
+}
+
+static int dw_mipi_dsi_clk_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct dw_mipi_dsi_stm *dsi = clk_to_dw_mipi_dsi_stm(hw);
+ unsigned int idf, ndiv, odf, pll_in_khz, pll_out_khz;
+ int ret;
+
+ DRM_DEBUG_DRIVER("\n");
+
+ pll_in_khz = (unsigned int)(req->best_parent_rate / 1000);
+
+ /* Compute best pll parameters */
+ idf = 0;
+ ndiv = 0;
+ odf = 0;
+
+ ret = dsi_pll_get_params(dsi, pll_in_khz, req->rate / 1000,
+ &idf, &ndiv, &odf);
+ if (ret)
+ DRM_WARN("Warning dsi_pll_get_params(): bad params\n");
+
+ /* Get the adjusted pll out value */
+ pll_out_khz = dsi_pll_get_clkout_khz(pll_in_khz, idf, ndiv, odf);
+
+ req->rate = pll_out_khz * 1000;
+
+ return 0;
+}
+
+static int dw_mipi_dsi_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct dw_mipi_dsi_stm *dsi = clk_to_dw_mipi_dsi_stm(hw);
+ unsigned int idf, ndiv, odf, pll_in_khz, pll_out_khz;
+ int ret;
+ u32 val;
+
+ DRM_DEBUG_DRIVER("\n");
+
+ pll_in_khz = (unsigned int)(parent_rate / 1000);
+
+ /* Compute best pll parameters */
+ idf = 0;
+ ndiv = 0;
+ odf = 0;
+
+ ret = dsi_pll_get_params(dsi, pll_in_khz, rate / 1000, &idf, &ndiv, &odf);
+ if (ret)
+ DRM_WARN("Warning dsi_pll_get_params(): bad params\n");
+
+ /* Get the adjusted pll out value */
+ pll_out_khz = dsi_pll_get_clkout_khz(pll_in_khz, idf, ndiv, odf);
+
+ /* Set the PLL division factors */
+ dsi_update_bits(dsi, DSI_WRPCR, WRPCR_NDIV | WRPCR_IDF | WRPCR_ODF,
+ (ndiv << 2) | (idf << 11) | ((ffs(odf) - 1) << 16));
+
+ /* Compute uix4 & set the bit period in high-speed mode */
+ val = 4000000 / pll_out_khz;
+ dsi_update_bits(dsi, DSI_WPCR0, WPCR0_UIX4, val);
+
+ return 0;
+}
+
+static void dw_mipi_dsi_clk_unregister(void *data)
+{
+ struct dw_mipi_dsi_stm *dsi = data;
+
+ DRM_DEBUG_DRIVER("\n");
+
+ of_clk_del_provider(dsi->dev->of_node);
+ clk_hw_unregister(&dsi->txbyte_clk);
+}
+
+static const struct clk_ops dw_mipi_dsi_stm_clk_ops = {
+ .enable = dw_mipi_dsi_clk_enable,
+ .disable = dw_mipi_dsi_clk_disable,
+ .is_enabled = dw_mipi_dsi_clk_is_enabled,
+ .recalc_rate = dw_mipi_dsi_clk_recalc_rate,
+ .determine_rate = dw_mipi_dsi_clk_determine_rate,
+ .set_rate = dw_mipi_dsi_clk_set_rate,
+};
+
+static struct clk_init_data cdata_init = {
+ .name = "ck_dsi_phy",
+ .ops = &dw_mipi_dsi_stm_clk_ops,
+ .parent_names = (const char * []) {"ck_hse"},
+ .num_parents = 1,
+};
+
+static int dw_mipi_dsi_clk_register(struct dw_mipi_dsi_stm *dsi,
+ struct device *dev)
+{
+ struct device_node *node = dev->of_node;
+ int ret;
+
+ DRM_DEBUG_DRIVER("Registering clk\n");
+
+ dsi->txbyte_clk.init = &cdata_init;
+
+ ret = clk_hw_register(dev, &dsi->txbyte_clk);
+ if (ret)
+ return ret;
+
+ ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get,
+ &dsi->txbyte_clk);
+ if (ret)
+ clk_hw_unregister(&dsi->txbyte_clk);
+
+ return ret;
+}
+
+static int dw_mipi_dsi_phy_init(void *priv_data)
+{
+ struct dw_mipi_dsi_stm *dsi = priv_data;
+ int ret;
+
+ ret = clk_prepare_enable(dsi->txbyte_clk.clk);
+ return ret;
+}
+
static void dw_mipi_dsi_phy_power_on(void *priv_data)
{
struct dw_mipi_dsi_stm *dsi = priv_data;
@@ -233,6 +411,8 @@ static void dw_mipi_dsi_phy_power_off(void *priv_data)
DRM_DEBUG_DRIVER("\n");
+ clk_disable_unprepare(dsi->txbyte_clk.clk);
+
/* Disable the DSI wrapper */
dsi_clear(dsi, DSI_WCR, WCR_DSIEN);
}
@@ -243,9 +423,8 @@ dw_mipi_dsi_get_lane_mbps(void *priv_data, const struct drm_display_mode *mode,
unsigned int *lane_mbps)
{
struct dw_mipi_dsi_stm *dsi = priv_data;
- unsigned int idf, ndiv, odf, pll_in_khz, pll_out_khz;
+ unsigned int pll_in_khz, pll_out_khz;
int ret, bpp;
- u32 val;
pll_in_khz = (unsigned int)(clk_get_rate(dsi->pllref_clk) / 1000);
@@ -266,25 +445,10 @@ dw_mipi_dsi_get_lane_mbps(void *priv_data, const struct drm_display_mode *mode,
DRM_WARN("Warning min phy mbps is used\n");
}
- /* Compute best pll parameters */
- idf = 0;
- ndiv = 0;
- odf = 0;
- ret = dsi_pll_get_params(dsi, pll_in_khz, pll_out_khz,
- &idf, &ndiv, &odf);
+ ret = clk_set_rate((dsi->txbyte_clk.clk), pll_out_khz * 1000);
if (ret)
- DRM_WARN("Warning dsi_pll_get_params(): bad params\n");
-
- /* Get the adjusted pll out value */
- pll_out_khz = dsi_pll_get_clkout_khz(pll_in_khz, idf, ndiv, odf);
-
- /* Set the PLL division factors */
- dsi_update_bits(dsi, DSI_WRPCR, WRPCR_NDIV | WRPCR_IDF | WRPCR_ODF,
- (ndiv << 2) | (idf << 11) | ((ffs(odf) - 1) << 16));
-
- /* Compute uix4 & set the bit period in high-speed mode */
- val = 4000000 / pll_out_khz;
- dsi_update_bits(dsi, DSI_WPCR0, WPCR0_UIX4, val);
+ DRM_DEBUG_DRIVER("ERROR Could not set rate of %d to %s clk->name",
+ pll_out_khz, clk_hw_get_name(&dsi->txbyte_clk));
/* Select video mode by resetting DSIM bit */
dsi_clear(dsi, DSI_WCFGR, WCFGR_DSIM);
@@ -443,16 +607,14 @@ static int dw_mipi_dsi_stm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct dw_mipi_dsi_stm *dsi;
- struct clk *pclk;
- struct resource *res;
+ const struct dw_mipi_dsi_plat_data *pdata = of_device_get_match_data(dev);
int ret;
dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
if (!dsi)
return -ENOMEM;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- dsi->base = devm_ioremap_resource(dev, res);
+ dsi->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(dsi->base)) {
ret = PTR_ERR(dsi->base);
DRM_ERROR("Unable to get dsi registers %d\n", ret);
@@ -485,21 +647,21 @@ static int dw_mipi_dsi_stm_probe(struct platform_device *pdev)
goto err_clk_get;
}
- pclk = devm_clk_get(dev, "pclk");
- if (IS_ERR(pclk)) {
- ret = PTR_ERR(pclk);
+ dsi->pclk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(dsi->pclk)) {
+ ret = PTR_ERR(dsi->pclk);
DRM_ERROR("Unable to get peripheral clock: %d\n", ret);
goto err_dsi_probe;
}
- ret = clk_prepare_enable(pclk);
+ ret = clk_prepare_enable(dsi->pclk);
if (ret) {
DRM_ERROR("%s: Failed to enable peripheral clk\n", __func__);
goto err_dsi_probe;
}
dsi->hw_version = dsi_read(dsi, DSI_VERSION) & VERSION;
- clk_disable_unprepare(pclk);
+ clk_disable_unprepare(dsi->pclk);
if (dsi->hw_version != HWVER_130 && dsi->hw_version != HWVER_131) {
ret = -ENODEV;
@@ -515,18 +677,41 @@ static int dw_mipi_dsi_stm_probe(struct platform_device *pdev)
dsi->lane_max_kbps *= 2;
}
- dw_mipi_dsi_stm_plat_data.base = dsi->base;
- dw_mipi_dsi_stm_plat_data.priv_data = dsi;
+ dsi->pdata = *pdata;
+ dsi->pdata.base = dsi->base;
+ dsi->pdata.priv_data = dsi;
+
+ dsi->pdata.max_data_lanes = 2;
+ dsi->pdata.phy_ops = &dw_mipi_dsi_stm_phy_ops;
platform_set_drvdata(pdev, dsi);
- dsi->dsi = dw_mipi_dsi_probe(pdev, &dw_mipi_dsi_stm_plat_data);
+ dsi->dsi = dw_mipi_dsi_probe(pdev, &dsi->pdata);
if (IS_ERR(dsi->dsi)) {
ret = PTR_ERR(dsi->dsi);
dev_err_probe(dev, ret, "Failed to initialize mipi dsi host\n");
goto err_dsi_probe;
}
+ /*
+ * We need to wait for the generic bridge to probe before enabling and
+ * register the internal pixel clock.
+ */
+ ret = clk_prepare_enable(dsi->pclk);
+ if (ret) {
+ DRM_ERROR("%s: Failed to enable peripheral clk\n", __func__);
+ goto err_dsi_probe;
+ }
+
+ ret = dw_mipi_dsi_clk_register(dsi, dev);
+ if (ret) {
+ DRM_ERROR("Failed to register DSI pixel clock: %d\n", ret);
+ clk_disable_unprepare(dsi->pclk);
+ goto err_dsi_probe;
+ }
+
+ clk_disable_unprepare(dsi->pclk);
+
return 0;
err_dsi_probe:
@@ -537,32 +722,32 @@ err_clk_get:
return ret;
}
-static int dw_mipi_dsi_stm_remove(struct platform_device *pdev)
+static void dw_mipi_dsi_stm_remove(struct platform_device *pdev)
{
struct dw_mipi_dsi_stm *dsi = platform_get_drvdata(pdev);
dw_mipi_dsi_remove(dsi->dsi);
clk_disable_unprepare(dsi->pllref_clk);
+ dw_mipi_dsi_clk_unregister(dsi);
regulator_disable(dsi->vdd_supply);
-
- return 0;
}
-static int __maybe_unused dw_mipi_dsi_stm_suspend(struct device *dev)
+static int dw_mipi_dsi_stm_suspend(struct device *dev)
{
- struct dw_mipi_dsi_stm *dsi = dw_mipi_dsi_stm_plat_data.priv_data;
+ struct dw_mipi_dsi_stm *dsi = dev_get_drvdata(dev);
DRM_DEBUG_DRIVER("\n");
clk_disable_unprepare(dsi->pllref_clk);
+ clk_disable_unprepare(dsi->pclk);
regulator_disable(dsi->vdd_supply);
return 0;
}
-static int __maybe_unused dw_mipi_dsi_stm_resume(struct device *dev)
+static int dw_mipi_dsi_stm_resume(struct device *dev)
{
- struct dw_mipi_dsi_stm *dsi = dw_mipi_dsi_stm_plat_data.priv_data;
+ struct dw_mipi_dsi_stm *dsi = dev_get_drvdata(dev);
int ret;
DRM_DEBUG_DRIVER("\n");
@@ -573,8 +758,16 @@ static int __maybe_unused dw_mipi_dsi_stm_resume(struct device *dev)
return ret;
}
+ ret = clk_prepare_enable(dsi->pclk);
+ if (ret) {
+ regulator_disable(dsi->vdd_supply);
+ DRM_ERROR("Failed to enable pclk: %d\n", ret);
+ return ret;
+ }
+
ret = clk_prepare_enable(dsi->pllref_clk);
if (ret) {
+ clk_disable_unprepare(dsi->pclk);
regulator_disable(dsi->vdd_supply);
DRM_ERROR("Failed to enable pllref_clk: %d\n", ret);
return ret;
@@ -584,8 +777,10 @@ static int __maybe_unused dw_mipi_dsi_stm_resume(struct device *dev)
}
static const struct dev_pm_ops dw_mipi_dsi_stm_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(dw_mipi_dsi_stm_suspend,
- dw_mipi_dsi_stm_resume)
+ SYSTEM_SLEEP_PM_OPS(dw_mipi_dsi_stm_suspend,
+ dw_mipi_dsi_stm_resume)
+ RUNTIME_PM_OPS(dw_mipi_dsi_stm_suspend,
+ dw_mipi_dsi_stm_resume, NULL)
};
static struct platform_driver dw_mipi_dsi_stm_driver = {
diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c
index 03c6becda795..f7e847cfa38f 100644
--- a/drivers/gpu/drm/stm/ltdc.c
+++ b/drivers/gpu/drm/stm/ltdc.c
@@ -14,7 +14,7 @@
#include <linux/interrupt.h>
#include <linux/media-bus-format.h>
#include <linux/module.h>
-#include <linux/of_address.h>
+#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
@@ -34,9 +34,11 @@
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_of.h>
+#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <drm/drm_vblank.h>
+#include <drm/drm_managed.h>
#include <video/videomode.h>
@@ -51,6 +53,7 @@
#define HWVER_10300 0x010300
#define HWVER_20101 0x020101
#define HWVER_40100 0x040100
+#define HWVER_40101 0x040101
/*
* The address of some registers depends on the HW version: such registers have
@@ -170,6 +173,7 @@
#define IER_RRIE BIT(3) /* Register Reload Interrupt Enable */
#define IER_FUEIE BIT(6) /* Fifo Underrun Error Interrupt Enable */
#define IER_CRCIE BIT(7) /* CRC Error Interrupt Enable */
+#define IER_MASK (IER_LIE | IER_FUWIE | IER_TERRIE | IER_RRIE | IER_FUEIE | IER_CRCIE)
#define CPSR_CYPOS GENMASK(15, 0) /* Current Y position */
@@ -188,6 +192,7 @@
#define LXCR_COLKEN BIT(1) /* Color Keying Enable */
#define LXCR_CLUTEN BIT(4) /* Color Look-Up Table ENable */
#define LXCR_HMEN BIT(8) /* Horizontal Mirroring ENable */
+#define LXCR_MASK (LXCR_LEN | LXCR_COLKEN | LXCR_CLUTEN | LXCR_HMEN)
#define LXWHPCR_WHSTPOS GENMASK(11, 0) /* Window Horizontal StarT POSition */
#define LXWHPCR_WHSPPOS GENMASK(27, 16) /* Window Horizontal StoP POSition */
@@ -492,11 +497,6 @@ static inline struct ltdc_device *plane_to_ltdc(struct drm_plane *plane)
return (struct ltdc_device *)plane->dev->dev_private;
}
-static inline struct ltdc_device *encoder_to_ltdc(struct drm_encoder *enc)
-{
- return (struct ltdc_device *)enc->dev->dev_private;
-}
-
static inline enum ltdc_pix_fmt to_ltdc_pixelformat(u32 drm_fmt)
{
enum ltdc_pix_fmt pf;
@@ -644,7 +644,7 @@ static inline void ltdc_set_ycbcr_config(struct drm_plane *plane, u32 drm_pix_fm
break;
default:
/* RGB or not a YCbCr supported format */
- DRM_ERROR("Unsupported pixel format: %u\n", drm_pix_fmt);
+ drm_err(plane->dev, "Unsupported pixel format: %u\n", drm_pix_fmt);
return;
}
@@ -667,18 +667,19 @@ static inline void ltdc_set_ycbcr_coeffs(struct drm_plane *plane)
u32 lofs = plane->index * LAY_OFS;
if (enc != DRM_COLOR_YCBCR_BT601 && enc != DRM_COLOR_YCBCR_BT709) {
- DRM_ERROR("color encoding %d not supported, use bt601 by default\n", enc);
+ drm_err(plane->dev, "color encoding %d not supported, use bt601 by default\n", enc);
/* set by default color encoding to DRM_COLOR_YCBCR_BT601 */
enc = DRM_COLOR_YCBCR_BT601;
}
if (ran != DRM_COLOR_YCBCR_LIMITED_RANGE && ran != DRM_COLOR_YCBCR_FULL_RANGE) {
- DRM_ERROR("color range %d not supported, use limited range by default\n", ran);
+ drm_err(plane->dev,
+ "color range %d not supported, use limited range by default\n", ran);
/* set by default color range to DRM_COLOR_YCBCR_LIMITED_RANGE */
ran = DRM_COLOR_YCBCR_LIMITED_RANGE;
}
- DRM_DEBUG_DRIVER("Color encoding=%d, range=%d\n", enc, ran);
+ drm_err(plane->dev, "Color encoding=%d, range=%d\n", enc, ran);
regmap_write(ldev->regmap, LTDC_L1CYR0R + lofs,
ltdc_ycbcr2rgb_coeffs[enc][ran][0]);
regmap_write(ldev->regmap, LTDC_L1CYR1R + lofs,
@@ -777,7 +778,7 @@ static void ltdc_crtc_atomic_enable(struct drm_crtc *crtc,
struct ltdc_device *ldev = crtc_to_ltdc(crtc);
struct drm_device *ddev = crtc->dev;
- DRM_DEBUG_DRIVER("\n");
+ drm_dbg_driver(crtc->dev, "\n");
pm_runtime_get_sync(ddev->dev);
@@ -785,7 +786,7 @@ static void ltdc_crtc_atomic_enable(struct drm_crtc *crtc,
regmap_write(ldev->regmap, LTDC_BCCR, BCCR_BCBLACK);
/* Enable IRQ */
- regmap_set_bits(ldev->regmap, LTDC_IER, IER_FUWIE | IER_FUEIE | IER_RRIE | IER_TERRIE);
+ regmap_set_bits(ldev->regmap, LTDC_IER, IER_FUWIE | IER_FUEIE | IER_TERRIE);
/* Commit shadow registers = update planes at next vblank */
if (!ldev->caps.plane_reg_shadow)
@@ -801,17 +802,16 @@ static void ltdc_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_device *ddev = crtc->dev;
int layer_index = 0;
- DRM_DEBUG_DRIVER("\n");
+ drm_dbg_driver(crtc->dev, "\n");
drm_crtc_vblank_off(crtc);
/* Disable all layers */
for (layer_index = 0; layer_index < ldev->caps.nb_layers; layer_index++)
- regmap_write_bits(ldev->regmap, LTDC_L1CR + layer_index * LAY_OFS,
- LXCR_CLUTEN | LXCR_LEN, 0);
+ regmap_write_bits(ldev->regmap, LTDC_L1CR + layer_index * LAY_OFS, LXCR_MASK, 0);
- /* disable IRQ */
- regmap_clear_bits(ldev->regmap, LTDC_IER, IER_FUWIE | IER_FUEIE | IER_RRIE | IER_TERRIE);
+ /* Disable IRQ */
+ regmap_clear_bits(ldev->regmap, LTDC_IER, IER_FUWIE | IER_FUEIE | IER_TERRIE);
/* immediately commit disable of layers before switching off LTDC */
if (!ldev->caps.plane_reg_shadow)
@@ -839,9 +839,15 @@ ltdc_crtc_mode_valid(struct drm_crtc *crtc,
int target_max = target + CLK_TOLERANCE_HZ;
int result;
+ if (ldev->lvds_clk) {
+ result = clk_round_rate(ldev->lvds_clk, target);
+ drm_dbg_driver(crtc->dev, "lvds pixclk rate target %d, available %d\n",
+ target, result);
+ }
+
result = clk_round_rate(ldev->pixel_clk, target);
- DRM_DEBUG_DRIVER("clk rate target %d, available %d\n", target, result);
+ drm_dbg_driver(crtc->dev, "clk rate target %d, available %d\n", target, result);
/* Filter modes according to the max frequency supported by the pads */
if (result > ldev->caps.pad_max_freq_hz)
@@ -876,14 +882,14 @@ static bool ltdc_crtc_mode_fixup(struct drm_crtc *crtc,
int rate = mode->clock * 1000;
if (clk_set_rate(ldev->pixel_clk, rate) < 0) {
- DRM_ERROR("Cannot set rate (%dHz) for pixel clk\n", rate);
+ drm_err(crtc->dev, "Cannot set rate (%dHz) for pixel clk\n", rate);
return false;
}
adjusted_mode->clock = clk_get_rate(ldev->pixel_clk) / 1000;
- DRM_DEBUG_DRIVER("requested clock %dkHz, adjusted clock %dkHz\n",
- mode->clock, adjusted_mode->clock);
+ drm_dbg_driver(crtc->dev, "requested clock %dkHz, adjusted clock %dkHz\n",
+ mode->clock, adjusted_mode->clock);
return true;
}
@@ -938,20 +944,20 @@ static void ltdc_crtc_mode_set_nofb(struct drm_crtc *crtc)
if (!pm_runtime_active(ddev->dev)) {
ret = pm_runtime_get_sync(ddev->dev);
if (ret) {
- DRM_ERROR("Failed to set mode, cannot get sync\n");
+ drm_err(crtc->dev, "Failed to set mode, cannot get sync\n");
return;
}
}
- DRM_DEBUG_DRIVER("CRTC:%d mode:%s\n", crtc->base.id, mode->name);
- DRM_DEBUG_DRIVER("Video mode: %dx%d", mode->hdisplay, mode->vdisplay);
- DRM_DEBUG_DRIVER(" hfp %d hbp %d hsl %d vfp %d vbp %d vsl %d\n",
- mode->hsync_start - mode->hdisplay,
- mode->htotal - mode->hsync_end,
- mode->hsync_end - mode->hsync_start,
- mode->vsync_start - mode->vdisplay,
- mode->vtotal - mode->vsync_end,
- mode->vsync_end - mode->vsync_start);
+ drm_dbg_driver(crtc->dev, "CRTC:%d mode:%s\n", crtc->base.id, mode->name);
+ drm_dbg_driver(crtc->dev, "Video mode: %dx%d", mode->hdisplay, mode->vdisplay);
+ drm_dbg_driver(crtc->dev, " hfp %d hbp %d hsl %d vfp %d vbp %d vsl %d\n",
+ mode->hsync_start - mode->hdisplay,
+ mode->htotal - mode->hsync_end,
+ mode->hsync_end - mode->hsync_start,
+ mode->vsync_start - mode->vdisplay,
+ mode->vtotal - mode->vsync_end,
+ mode->vsync_end - mode->vsync_start);
/* Convert video timings to ltdc timings */
hsync = mode->hsync_end - mode->hsync_start - 1;
@@ -1037,7 +1043,7 @@ static void ltdc_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_device *ddev = crtc->dev;
struct drm_pending_vblank_event *event = crtc->state->event;
- DRM_DEBUG_ATOMIC("\n");
+ drm_dbg_atomic(crtc->dev, "\n");
ltdc_crtc_update_clut(crtc);
@@ -1125,7 +1131,7 @@ static int ltdc_crtc_enable_vblank(struct drm_crtc *crtc)
struct ltdc_device *ldev = crtc_to_ltdc(crtc);
struct drm_crtc_state *state = crtc->state;
- DRM_DEBUG_DRIVER("\n");
+ drm_dbg_driver(crtc->dev, "\n");
if (state->enable)
regmap_set_bits(ldev->regmap, LTDC_IER, IER_LIE);
@@ -1139,20 +1145,22 @@ static void ltdc_crtc_disable_vblank(struct drm_crtc *crtc)
{
struct ltdc_device *ldev = crtc_to_ltdc(crtc);
- DRM_DEBUG_DRIVER("\n");
+ drm_dbg_driver(crtc->dev, "\n");
regmap_clear_bits(ldev->regmap, LTDC_IER, IER_LIE);
}
static int ltdc_crtc_set_crc_source(struct drm_crtc *crtc, const char *source)
{
- struct ltdc_device *ldev = crtc_to_ltdc(crtc);
+ struct ltdc_device *ldev;
int ret;
- DRM_DEBUG_DRIVER("\n");
-
if (!crtc)
return -ENODEV;
+ drm_dbg_driver(crtc->dev, "\n");
+
+ ldev = crtc_to_ltdc(crtc);
+
if (source && strcmp(source, "auto") == 0) {
ldev->crc_active = true;
ret = regmap_set_bits(ldev->regmap, LTDC_GCR, GCR_CRCEN);
@@ -1170,14 +1178,14 @@ static int ltdc_crtc_set_crc_source(struct drm_crtc *crtc, const char *source)
static int ltdc_crtc_verify_crc_source(struct drm_crtc *crtc,
const char *source, size_t *values_cnt)
{
- DRM_DEBUG_DRIVER("\n");
-
if (!crtc)
return -ENODEV;
+ drm_dbg_driver(crtc->dev, "\n");
+
if (source && strcmp(source, "auto") != 0) {
- DRM_DEBUG_DRIVER("Unknown CRC source %s for %s\n",
- source, crtc->name);
+ drm_dbg_driver(crtc->dev, "Unknown CRC source %s for %s\n",
+ source, crtc->name);
return -EINVAL;
}
@@ -1198,7 +1206,6 @@ static void ltdc_crtc_atomic_print_state(struct drm_printer *p,
}
static const struct drm_crtc_funcs ltdc_crtc_funcs = {
- .destroy = drm_crtc_cleanup,
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.reset = drm_atomic_helper_crtc_reset,
@@ -1211,7 +1218,6 @@ static const struct drm_crtc_funcs ltdc_crtc_funcs = {
};
static const struct drm_crtc_funcs ltdc_crtc_with_crc_support_funcs = {
- .destroy = drm_crtc_cleanup,
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.reset = drm_atomic_helper_crtc_reset,
@@ -1237,7 +1243,7 @@ static int ltdc_plane_atomic_check(struct drm_plane *plane,
struct drm_framebuffer *fb = new_plane_state->fb;
u32 src_w, src_h;
- DRM_DEBUG_DRIVER("\n");
+ drm_dbg_driver(plane->dev, "\n");
if (!fb)
return 0;
@@ -1248,7 +1254,7 @@ static int ltdc_plane_atomic_check(struct drm_plane *plane,
/* Reject scaling */
if (src_w != new_plane_state->crtc_w || src_h != new_plane_state->crtc_h) {
- DRM_DEBUG_DRIVER("Scaling is not supported");
+ drm_dbg_driver(plane->dev, "Scaling is not supported");
return -EINVAL;
}
@@ -1274,7 +1280,7 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane,
enum ltdc_pix_fmt pf;
if (!newstate->crtc || !fb) {
- DRM_DEBUG_DRIVER("fb or crtc NULL");
+ drm_dbg_driver(plane->dev, "fb or crtc NULL");
return;
}
@@ -1284,11 +1290,11 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane,
src_w = newstate->src_w >> 16;
src_h = newstate->src_h >> 16;
- DRM_DEBUG_DRIVER("plane:%d fb:%d (%dx%d)@(%d,%d) -> (%dx%d)@(%d,%d)\n",
- plane->base.id, fb->base.id,
- src_w, src_h, src_x, src_y,
- newstate->crtc_w, newstate->crtc_h,
- newstate->crtc_x, newstate->crtc_y);
+ drm_dbg_driver(plane->dev, "plane:%d fb:%d (%dx%d)@(%d,%d) -> (%dx%d)@(%d,%d)\n",
+ plane->base.id, fb->base.id,
+ src_w, src_h, src_x, src_y,
+ newstate->crtc_w, newstate->crtc_h,
+ newstate->crtc_x, newstate->crtc_y);
regmap_read(ldev->regmap, LTDC_BPCR, &bpcr);
@@ -1316,8 +1322,8 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane,
val = ltdc_set_flexible_pixel_format(plane, pf);
if (val == NB_PF) {
- DRM_ERROR("Pixel format %.4s not supported\n",
- (char *)&fb->format->format);
+ drm_err(fb->dev, "Pixel format %.4s not supported\n",
+ (char *)&fb->format->format);
val = 0; /* set by default ARGB 32 bits */
}
regmap_write_bits(ldev->regmap, LTDC_L1PFCR + lofs, LXPFCR_PF, val);
@@ -1354,7 +1360,7 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane,
if (newstate->rotation & DRM_MODE_REFLECT_Y)
paddr += (fb->pitches[0] * (y1 - y0));
- DRM_DEBUG_DRIVER("fb: phys 0x%08x", paddr);
+ drm_dbg_driver(fb->dev, "fb: phys 0x%08x", paddr);
regmap_write(ldev->regmap, LTDC_L1CFBAR + lofs, paddr);
/* Configures the color frame buffer pitch in bytes & line length */
@@ -1473,7 +1479,7 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane,
if (newstate->rotation & DRM_MODE_REFLECT_X)
val |= LXCR_HMEN;
- regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN | LXCR_CLUTEN | LXCR_HMEN, val);
+ regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_MASK, val);
/* Commit shadow registers = update plane at next vblank */
if (ldev->caps.plane_reg_shadow)
@@ -1511,15 +1517,18 @@ static void ltdc_plane_atomic_disable(struct drm_plane *plane,
u32 lofs = plane->index * LAY_OFS;
/* Disable layer */
- regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN | LXCR_CLUTEN | LXCR_HMEN, 0);
+ regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_MASK, 0);
+
+ /* Reset the layer transparency to hide any related background color */
+ regmap_write_bits(ldev->regmap, LTDC_L1CACR + lofs, LXCACR_CONSTA, 0x00);
/* Commit shadow registers = update plane at next vblank */
if (ldev->caps.plane_reg_shadow)
regmap_write_bits(ldev->regmap, LTDC_L1RCR + lofs,
LXRCR_IMR | LXRCR_VBR | LXRCR_GRMSK, LXRCR_VBR);
- DRM_DEBUG_DRIVER("CRTC:%d plane:%d\n",
- oldstate->crtc->base.id, plane->base.id);
+ drm_dbg_driver(plane->dev, "CRTC:%d plane:%d\n",
+ oldstate->crtc->base.id, plane->base.id);
}
static void ltdc_plane_atomic_print_state(struct drm_printer *p,
@@ -1544,7 +1553,6 @@ static void ltdc_plane_atomic_print_state(struct drm_printer *p,
static const struct drm_plane_funcs ltdc_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
- .destroy = drm_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
@@ -1571,7 +1579,6 @@ static struct drm_plane *ltdc_plane_create(struct drm_device *ddev,
const u64 *modifiers = ltdc_format_modifiers;
u32 lofs = index * LAY_OFS;
u32 val;
- int ret;
/* Allocate the biggest size according to supported color formats */
formats = devm_kzalloc(dev, (ldev->caps.pix_fmt_nb +
@@ -1579,6 +1586,8 @@ static struct drm_plane *ltdc_plane_create(struct drm_device *ddev,
ARRAY_SIZE(ltdc_drm_fmt_ycbcr_sp) +
ARRAY_SIZE(ltdc_drm_fmt_ycbcr_fp)) *
sizeof(*formats), GFP_KERNEL);
+ if (!formats)
+ return NULL;
for (i = 0; i < ldev->caps.pix_fmt_nb; i++) {
drm_fmt = ldev->caps.pix_fmt_drm[i];
@@ -1612,14 +1621,10 @@ static struct drm_plane *ltdc_plane_create(struct drm_device *ddev,
}
}
- plane = devm_kzalloc(dev, sizeof(*plane), GFP_KERNEL);
- if (!plane)
- return NULL;
-
- ret = drm_universal_plane_init(ddev, plane, possible_crtcs,
- &ltdc_plane_funcs, formats, nb_fmt,
- modifiers, type, NULL);
- if (ret < 0)
+ plane = drmm_universal_plane_alloc(ddev, struct drm_plane, dev,
+ possible_crtcs, &ltdc_plane_funcs, formats,
+ nb_fmt, modifiers, type, NULL);
+ if (IS_ERR(plane))
return NULL;
if (ldev->caps.ycbcr_input) {
@@ -1637,20 +1642,11 @@ static struct drm_plane *ltdc_plane_create(struct drm_device *ddev,
drm_plane_create_alpha_property(plane);
- DRM_DEBUG_DRIVER("plane:%d created\n", plane->base.id);
+ drm_dbg_driver(plane->dev, "plane:%d created\n", plane->base.id);
return plane;
}
-static void ltdc_plane_destroy_all(struct drm_device *ddev)
-{
- struct drm_plane *plane, *plane_temp;
-
- list_for_each_entry_safe(plane, plane_temp,
- &ddev->mode_config.plane_list, head)
- drm_plane_cleanup(plane);
-}
-
static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc)
{
struct ltdc_device *ldev = ddev->dev_private;
@@ -1661,7 +1657,7 @@ static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc)
primary = ltdc_plane_create(ddev, DRM_PLANE_TYPE_PRIMARY, 0);
if (!primary) {
- DRM_ERROR("Can not create primary plane\n");
+ drm_err(ddev, "Can not create primary plane\n");
return -EINVAL;
}
@@ -1676,14 +1672,14 @@ static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc)
/* Init CRTC according to its hardware features */
if (ldev->caps.crc)
- ret = drm_crtc_init_with_planes(ddev, crtc, primary, NULL,
- &ltdc_crtc_with_crc_support_funcs, NULL);
+ ret = drmm_crtc_init_with_planes(ddev, crtc, primary, NULL,
+ &ltdc_crtc_with_crc_support_funcs, NULL);
else
- ret = drm_crtc_init_with_planes(ddev, crtc, primary, NULL,
- &ltdc_crtc_funcs, NULL);
+ ret = drmm_crtc_init_with_planes(ddev, crtc, primary, NULL,
+ &ltdc_crtc_funcs, NULL);
if (ret) {
- DRM_ERROR("Can not initialize CRTC\n");
- goto cleanup;
+ drm_err(ddev, "Can not initialize CRTC\n");
+ return ret;
}
drm_crtc_helper_add(crtc, &ltdc_crtc_helper_funcs);
@@ -1691,15 +1687,14 @@ static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc)
drm_mode_crtc_set_gamma_size(crtc, CLUT_SIZE);
drm_crtc_enable_color_mgmt(crtc, 0, false, CLUT_SIZE);
- DRM_DEBUG_DRIVER("CRTC:%d created\n", crtc->base.id);
+ drm_dbg_driver(ddev, "CRTC:%d created\n", crtc->base.id);
/* Add planes. Note : the first layer is used by primary plane */
for (i = 1; i < ldev->caps.nb_layers; i++) {
overlay = ltdc_plane_create(ddev, DRM_PLANE_TYPE_OVERLAY, i);
if (!overlay) {
- ret = -ENOMEM;
- DRM_ERROR("Can not create overlay plane %d\n", i);
- goto cleanup;
+ drm_err(ddev, "Can not create overlay plane %d\n", i);
+ return -ENOMEM;
}
if (ldev->caps.dynamic_zorder)
drm_plane_create_zpos_property(overlay, i, 0, ldev->caps.nb_layers - 1);
@@ -1712,10 +1707,6 @@ static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc)
}
return 0;
-
-cleanup:
- ltdc_plane_destroy_all(ddev);
- return ret;
}
static void ltdc_encoder_disable(struct drm_encoder *encoder)
@@ -1723,7 +1714,7 @@ static void ltdc_encoder_disable(struct drm_encoder *encoder)
struct drm_device *ddev = encoder->dev;
struct ltdc_device *ldev = ddev->dev_private;
- DRM_DEBUG_DRIVER("\n");
+ drm_dbg_driver(encoder->dev, "\n");
/* Disable LTDC */
regmap_clear_bits(ldev->regmap, LTDC_GCR, GCR_LTDCEN);
@@ -1737,7 +1728,7 @@ static void ltdc_encoder_enable(struct drm_encoder *encoder)
struct drm_device *ddev = encoder->dev;
struct ltdc_device *ldev = ddev->dev_private;
- DRM_DEBUG_DRIVER("\n");
+ drm_dbg_driver(encoder->dev, "\n");
/* set fifo underrun threshold register */
if (ldev->caps.fifo_threshold)
@@ -1753,7 +1744,7 @@ static void ltdc_encoder_mode_set(struct drm_encoder *encoder,
{
struct drm_device *ddev = encoder->dev;
- DRM_DEBUG_DRIVER("\n");
+ drm_dbg_driver(encoder->dev, "\n");
/*
* Set to default state the pinctrl only with DPI type.
@@ -1775,25 +1766,21 @@ static int ltdc_encoder_init(struct drm_device *ddev, struct drm_bridge *bridge)
struct drm_encoder *encoder;
int ret;
- encoder = devm_kzalloc(ddev->dev, sizeof(*encoder), GFP_KERNEL);
- if (!encoder)
- return -ENOMEM;
+ encoder = drmm_simple_encoder_alloc(ddev, struct drm_encoder, dev,
+ DRM_MODE_ENCODER_DPI);
+ if (IS_ERR(encoder))
+ return PTR_ERR(encoder);
encoder->possible_crtcs = CRTC_MASK;
encoder->possible_clones = 0; /* No cloning support */
- drm_simple_encoder_init(ddev, encoder, DRM_MODE_ENCODER_DPI);
-
drm_encoder_helper_add(encoder, &ltdc_encoder_helper_funcs);
ret = drm_bridge_attach(encoder, bridge, NULL, 0);
- if (ret) {
- if (ret != -EPROBE_DEFER)
- drm_encoder_cleanup(encoder);
+ if (ret)
return ret;
- }
- DRM_DEBUG_DRIVER("Bridge encoder:%d created\n", encoder->base.id);
+ drm_dbg_driver(encoder->dev, "Bridge encoder:%d created\n", encoder->base.id);
return 0;
}
@@ -1802,6 +1789,7 @@ static int ltdc_get_caps(struct drm_device *ddev)
{
struct ltdc_device *ldev = ddev->dev_private;
u32 bus_width_log2, lcr, gc2r;
+ const struct ltdc_plat_data *pdata = of_device_get_match_data(ddev->dev);
/*
* at least 1 layer must be managed & the number of layers
@@ -1817,6 +1805,8 @@ static int ltdc_get_caps(struct drm_device *ddev)
ldev->caps.bus_width = 8 << bus_width_log2;
regmap_read(ldev->regmap, LTDC_IDR, &ldev->caps.hw_version);
+ ldev->caps.pad_max_freq_hz = pdata->pad_max_freq_hz;
+
switch (ldev->caps.hw_version) {
case HWVER_10200:
case HWVER_10300:
@@ -1834,7 +1824,6 @@ static int ltdc_get_caps(struct drm_device *ddev)
* does not work on 2nd layer.
*/
ldev->caps.non_alpha_only_l1 = true;
- ldev->caps.pad_max_freq_hz = 90000000;
if (ldev->caps.hw_version == HWVER_10200)
ldev->caps.pad_max_freq_hz = 65000000;
ldev->caps.nb_irq = 2;
@@ -1865,6 +1854,7 @@ static int ltdc_get_caps(struct drm_device *ddev)
ldev->caps.fifo_threshold = false;
break;
case HWVER_40100:
+ case HWVER_40101:
ldev->caps.layer_ofs = LAY_OFS_1;
ldev->caps.layer_regs = ltdc_layer_regs_a2;
ldev->caps.pix_fmt_hw = ltdc_pix_fmt_a2;
@@ -1872,7 +1862,6 @@ static int ltdc_get_caps(struct drm_device *ddev)
ldev->caps.pix_fmt_nb = ARRAY_SIZE(ltdc_drm_fmt_a2);
ldev->caps.pix_fmt_flex = true;
ldev->caps.non_alpha_only_l1 = false;
- ldev->caps.pad_max_freq_hz = 90000000;
ldev->caps.nb_irq = 2;
ldev->caps.ycbcr_input = true;
ldev->caps.ycbcr_output = true;
@@ -1893,8 +1882,12 @@ void ltdc_suspend(struct drm_device *ddev)
{
struct ltdc_device *ldev = ddev->dev_private;
- DRM_DEBUG_DRIVER("\n");
+ drm_dbg_driver(ddev, "\n");
clk_disable_unprepare(ldev->pixel_clk);
+ if (ldev->bus_clk)
+ clk_disable_unprepare(ldev->bus_clk);
+ if (ldev->lvds_clk)
+ clk_disable_unprepare(ldev->lvds_clk);
}
int ltdc_resume(struct drm_device *ddev)
@@ -1902,15 +1895,29 @@ int ltdc_resume(struct drm_device *ddev)
struct ltdc_device *ldev = ddev->dev_private;
int ret;
- DRM_DEBUG_DRIVER("\n");
+ drm_dbg_driver(ddev, "\n");
ret = clk_prepare_enable(ldev->pixel_clk);
if (ret) {
- DRM_ERROR("failed to enable pixel clock (%d)\n", ret);
+ drm_err(ddev, "failed to enable pixel clock (%d)\n", ret);
return ret;
}
- return 0;
+ if (ldev->bus_clk) {
+ ret = clk_prepare_enable(ldev->bus_clk);
+ if (ret) {
+ drm_err(ddev, "failed to enable bus clock (%d)\n", ret);
+ return ret;
+ }
+ }
+
+ if (ldev->lvds_clk) {
+ ret = clk_prepare_enable(ldev->lvds_clk);
+ if (ret)
+ drm_err(ddev, "failed to prepare lvds clock\n");
+ }
+
+ return ret;
}
int ltdc_load(struct drm_device *ddev)
@@ -1923,11 +1930,10 @@ int ltdc_load(struct drm_device *ddev)
struct drm_panel *panel;
struct drm_crtc *crtc;
struct reset_control *rstc;
- struct resource *res;
int irq, i, nb_endpoints;
int ret = -ENODEV;
- DRM_DEBUG_DRIVER("\n");
+ drm_dbg_driver(ddev, "\n");
/* Get number of endpoints */
nb_endpoints = of_graph_get_endpoint_count(np);
@@ -1937,15 +1943,29 @@ int ltdc_load(struct drm_device *ddev)
ldev->pixel_clk = devm_clk_get(dev, "lcd");
if (IS_ERR(ldev->pixel_clk)) {
if (PTR_ERR(ldev->pixel_clk) != -EPROBE_DEFER)
- DRM_ERROR("Unable to get lcd clock\n");
+ drm_err(ddev, "Unable to get lcd clock\n");
return PTR_ERR(ldev->pixel_clk);
}
if (clk_prepare_enable(ldev->pixel_clk)) {
- DRM_ERROR("Unable to prepare pixel clock\n");
+ drm_err(ddev, "Unable to prepare pixel clock\n");
return -ENODEV;
}
+ if (of_device_is_compatible(np, "st,stm32mp251-ltdc") ||
+ of_device_is_compatible(np, "st,stm32mp255-ltdc")) {
+ ldev->bus_clk = devm_clk_get(dev, "bus");
+ if (IS_ERR(ldev->bus_clk))
+ return dev_err_probe(dev, PTR_ERR(ldev->bus_clk),
+ "Unable to get bus clock\n");
+
+ ret = clk_prepare_enable(ldev->bus_clk);
+ if (ret) {
+ drm_err(ddev, "Unable to prepare bus clock\n");
+ return ret;
+ }
+ }
+
/* Get endpoints if any */
for (i = 0; i < nb_endpoints; i++) {
ret = drm_of_find_panel_or_bridge(np, 0, i, &panel, &bridge);
@@ -1961,10 +1981,9 @@ int ltdc_load(struct drm_device *ddev)
goto err;
if (panel) {
- bridge = drm_panel_bridge_add_typed(panel,
- DRM_MODE_CONNECTOR_DPI);
+ bridge = drmm_panel_bridge_add(ddev, panel);
if (IS_ERR(bridge)) {
- DRM_ERROR("panel-bridge endpoint %d\n", i);
+ drm_err(ddev, "panel-bridge endpoint %d\n", i);
ret = PTR_ERR(bridge);
goto err;
}
@@ -1974,12 +1993,16 @@ int ltdc_load(struct drm_device *ddev)
ret = ltdc_encoder_init(ddev, bridge);
if (ret) {
if (ret != -EPROBE_DEFER)
- DRM_ERROR("init encoder endpoint %d\n", i);
+ drm_err(ddev, "init encoder endpoint %d\n", i);
goto err;
}
}
}
+ ldev->lvds_clk = devm_clk_get(dev, "lvds");
+ if (IS_ERR(ldev->lvds_clk))
+ ldev->lvds_clk = NULL;
+
rstc = devm_reset_control_get_exclusive(dev, NULL);
mutex_init(&ldev->err_lock);
@@ -1990,37 +2013,31 @@ int ltdc_load(struct drm_device *ddev)
reset_control_deassert(rstc);
}
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- ldev->regs = devm_ioremap_resource(dev, res);
+ ldev->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ldev->regs)) {
- DRM_ERROR("Unable to get ltdc registers\n");
+ drm_err(ddev, "Unable to get ltdc registers\n");
ret = PTR_ERR(ldev->regs);
goto err;
}
ldev->regmap = devm_regmap_init_mmio(&pdev->dev, ldev->regs, &stm32_ltdc_regmap_cfg);
if (IS_ERR(ldev->regmap)) {
- DRM_ERROR("Unable to regmap ltdc registers\n");
+ drm_err(ddev, "Unable to regmap ltdc registers\n");
ret = PTR_ERR(ldev->regmap);
goto err;
}
ret = ltdc_get_caps(ddev);
if (ret) {
- DRM_ERROR("hardware identifier (0x%08x) not supported!\n",
- ldev->caps.hw_version);
+ drm_err(ddev, "hardware identifier (0x%08x) not supported!\n",
+ ldev->caps.hw_version);
goto err;
}
- /* Disable interrupts */
- if (ldev->caps.fifo_threshold)
- regmap_clear_bits(ldev->regmap, LTDC_IER, IER_LIE | IER_RRIE | IER_FUWIE |
- IER_TERRIE);
- else
- regmap_clear_bits(ldev->regmap, LTDC_IER, IER_LIE | IER_RRIE | IER_FUWIE |
- IER_TERRIE | IER_FUEIE);
+ /* Disable all interrupts */
+ regmap_clear_bits(ldev->regmap, LTDC_IER, IER_MASK);
- DRM_DEBUG_DRIVER("ltdc hw version 0x%08x\n", ldev->caps.hw_version);
+ drm_dbg_driver(ddev, "ltdc hw version 0x%08x\n", ldev->caps.hw_version);
/* initialize default value for fifo underrun threshold & clear interrupt error counters */
ldev->transfer_err = 0;
@@ -2039,57 +2056,52 @@ int ltdc_load(struct drm_device *ddev)
ltdc_irq_thread, IRQF_ONESHOT,
dev_name(dev), ddev);
if (ret) {
- DRM_ERROR("Failed to register LTDC interrupt\n");
+ drm_err(ddev, "Failed to register LTDC interrupt\n");
goto err;
}
}
- crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL);
+ crtc = drmm_kzalloc(ddev, sizeof(*crtc), GFP_KERNEL);
if (!crtc) {
- DRM_ERROR("Failed to allocate crtc\n");
+ drm_err(ddev, "Failed to allocate crtc\n");
ret = -ENOMEM;
goto err;
}
ret = ltdc_crtc_init(ddev, crtc);
if (ret) {
- DRM_ERROR("Failed to init crtc\n");
+ drm_err(ddev, "Failed to init crtc\n");
goto err;
}
ret = drm_vblank_init(ddev, NB_CRTC);
if (ret) {
- DRM_ERROR("Failed calling drm_vblank_init()\n");
+ drm_err(ddev, "Failed calling drm_vblank_init()\n");
goto err;
}
clk_disable_unprepare(ldev->pixel_clk);
+ if (ldev->bus_clk)
+ clk_disable_unprepare(ldev->bus_clk);
+
pinctrl_pm_select_sleep_state(ddev->dev);
pm_runtime_enable(ddev->dev);
return 0;
err:
- for (i = 0; i < nb_endpoints; i++)
- drm_of_panel_bridge_remove(ddev->dev->of_node, 0, i);
-
clk_disable_unprepare(ldev->pixel_clk);
+ if (ldev->bus_clk)
+ clk_disable_unprepare(ldev->bus_clk);
+
return ret;
}
void ltdc_unload(struct drm_device *ddev)
{
- struct device *dev = ddev->dev;
- int nb_endpoints, i;
-
- DRM_DEBUG_DRIVER("\n");
-
- nb_endpoints = of_graph_get_endpoint_count(dev->of_node);
-
- for (i = 0; i < nb_endpoints; i++)
- drm_of_panel_bridge_remove(ddev->dev->of_node, 0, i);
+ drm_dbg_driver(ddev, "\n");
pm_runtime_disable(ddev->dev);
}
diff --git a/drivers/gpu/drm/stm/ltdc.h b/drivers/gpu/drm/stm/ltdc.h
index 9d488043ffdb..17b51a7ce28e 100644
--- a/drivers/gpu/drm/stm/ltdc.h
+++ b/drivers/gpu/drm/stm/ltdc.h
@@ -40,10 +40,16 @@ struct fps_info {
ktime_t last_timestamp;
};
+struct ltdc_plat_data {
+ int pad_max_freq_hz; /* max frequency supported by pad */
+};
+
struct ltdc_device {
void __iomem *regs;
struct regmap *regmap;
struct clk *pixel_clk; /* lcd pixel clock */
+ struct clk *lvds_clk; /* lvds pixel clock */
+ struct clk *bus_clk; /* bus clock */
struct mutex err_lock; /* protecting error_status */
struct ltdc_caps caps;
u32 irq_status;
diff --git a/drivers/gpu/drm/stm/lvds.c b/drivers/gpu/drm/stm/lvds.c
new file mode 100644
index 000000000000..fe38c0984b2b
--- /dev/null
+++ b/drivers/gpu/drm/stm/lvds.c
@@ -0,0 +1,1222 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023, STMicroelectronics - All Rights Reserved
+ * Author(s): Raphaël GALLAIS-POU <raphael.gallais-pou@foss.st.com> for STMicroelectronics.
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_device.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+/* LVDS Host registers */
+#define LVDS_CR 0x0000 /* configuration register */
+#define LVDS_DMLCR0 0x0004 /* data mapping lsb configuration register 0 */
+#define LVDS_DMMCR0 0x0008 /* data mapping msb configuration register 0 */
+#define LVDS_DMLCR1 0x000C /* data mapping lsb configuration register 1 */
+#define LVDS_DMMCR1 0x0010 /* data mapping msb configuration register 1 */
+#define LVDS_DMLCR2 0x0014 /* data mapping lsb configuration register 2 */
+#define LVDS_DMMCR2 0x0018 /* data mapping msb configuration register 2 */
+#define LVDS_DMLCR3 0x001C /* data mapping lsb configuration register 3 */
+#define LVDS_DMMCR3 0x0020 /* data mapping msb configuration register 3 */
+#define LVDS_DMLCR4 0x0024 /* data mapping lsb configuration register 4 */
+#define LVDS_DMMCR4 0x0028 /* data mapping msb configuration register 4 */
+#define LVDS_CDL1CR 0x002C /* channel distrib link 1 configuration register */
+#define LVDS_CDL2CR 0x0030 /* channel distrib link 2 configuration register */
+
+#define CDL1CR_DEFAULT 0x04321 /* Default value for CDL1CR */
+#define CDL2CR_DEFAULT 0x59876 /* Default value for CDL2CR */
+
+#define LVDS_DMLCR(bit) (LVDS_DMLCR0 + 0x8 * (bit))
+#define LVDS_DMMCR(bit) (LVDS_DMMCR0 + 0x8 * (bit))
+
+/* LVDS Wrapper registers */
+#define LVDS_WCLKCR 0x11B0 /* Wrapper clock control register */
+
+#define LVDS_HWCFGR 0x1FF0 /* HW configuration register */
+#define LVDS_VERR 0x1FF4 /* Version register */
+#define LVDS_IPIDR 0x1FF8 /* Identification register */
+#define LVDS_SIDR 0x1FFC /* Size Identification register */
+
+/* Bitfield description */
+#define CR_LVDSEN BIT(0) /* LVDS PHY Enable */
+#define CR_HSPOL BIT(1) /* Horizontal Synchronization Polarity */
+#define CR_VSPOL BIT(2) /* Vertical Synchronization Polarity */
+#define CR_DEPOL BIT(3) /* Data Enable Polarity */
+#define CR_CI BIT(4) /* Control Internal (software controlled bit) */
+#define CR_LKMOD BIT(5) /* Link Mode, for both Links */
+#define CR_LKPHA BIT(6) /* Link Phase, for both Links */
+#define CR_LK1POL GENMASK(20, 16) /* Link-1 output Polarity */
+#define CR_LK2POL GENMASK(25, 21) /* Link-2 output Polarity */
+
+#define DMMCR_MAP0 GENMASK(4, 0) /* Mapping for bit 0 of datalane x */
+#define DMMCR_MAP1 GENMASK(9, 5) /* Mapping for bit 1 of datalane x */
+#define DMMCR_MAP2 GENMASK(14, 10) /* Mapping for bit 2 of datalane x */
+#define DMMCR_MAP3 GENMASK(19, 15) /* Mapping for bit 3 of datalane x */
+#define DMLCR_MAP4 GENMASK(4, 0) /* Mapping for bit 4 of datalane x */
+#define DMLCR_MAP5 GENMASK(9, 5) /* Mapping for bit 5 of datalane x */
+#define DMLCR_MAP6 GENMASK(14, 10) /* Mapping for bit 6 of datalane x */
+
+#define CDLCR_DISTR0 GENMASK(3, 0) /* Channel distribution for lane 0 */
+#define CDLCR_DISTR1 GENMASK(7, 4) /* Channel distribution for lane 1 */
+#define CDLCR_DISTR2 GENMASK(11, 8) /* Channel distribution for lane 2 */
+#define CDLCR_DISTR3 GENMASK(15, 12) /* Channel distribution for lane 3 */
+#define CDLCR_DISTR4 GENMASK(19, 16) /* Channel distribution for lane 4 */
+
+#define PHY_GCR_BIT_CLK_OUT BIT(0) /* BIT clock enable */
+#define PHY_GCR_LS_CLK_OUT BIT(4) /* LS clock enable */
+#define PHY_GCR_DP_CLK_OUT BIT(8) /* DP clock enable */
+#define PHY_GCR_RSTZ BIT(24) /* LVDS PHY digital reset */
+#define PHY_GCR_DIV_RSTN BIT(25) /* Output divider reset */
+#define PHY_SCR_TX_EN BIT(16) /* Transmission mode enable */
+/* Current mode driver enable */
+#define PHY_CMCR_CM_EN_DL (BIT(28) | BIT(20) | BIT(12) | BIT(4))
+#define PHY_CMCR_CM_EN_DL4 BIT(4)
+/* Bias enable */
+#define PHY_BCR1_EN_BIAS_DL (BIT(16) | BIT(12) | BIT(8) | BIT(4) | BIT(0))
+#define PHY_BCR2_BIAS_EN BIT(28)
+/* Voltage mode driver enable */
+#define PHY_BCR3_VM_EN_DL (BIT(16) | BIT(12) | BIT(8) | BIT(4) | BIT(0))
+#define PHY_DCR_POWER_OK BIT(12)
+#define PHY_CFGCR_EN_DIG_DL GENMASK(4, 0) /* LVDS PHY digital lane enable */
+#define PHY_PLLCR1_PLL_EN BIT(0) /* LVDS PHY PLL enable */
+#define PHY_PLLCR1_EN_SD BIT(1) /* LVDS PHY PLL sigma-delta signal enable */
+#define PHY_PLLCR1_EN_TWG BIT(2) /* LVDS PHY PLL triangular wave generator enable */
+#define PHY_PLLCR1_DIV_EN BIT(8) /* LVDS PHY PLL dividers enable */
+#define PHY_PLLCR2_NDIV GENMASK(25, 16) /* NDIV mask value */
+#define PHY_PLLCR2_BDIV GENMASK(9, 0) /* BDIV mask value */
+#define PHY_PLLSR_PLL_LOCK BIT(0) /* LVDS PHY PLL lock status */
+#define PHY_PLLSDCR1_MDIV GENMASK(9, 0) /* MDIV mask value */
+#define PHY_PLLTESTCR_TDIV GENMASK(25, 16) /* TDIV mask value */
+#define PHY_PLLTESTCR_CLK_EN BIT(0) /* Test clock enable */
+#define PHY_PLLTESTCR_EN BIT(8) /* Test divider output enable */
+
+#define WCLKCR_SECND_CLKPIX_SEL BIT(0) /* Pixel clock selection */
+#define WCLKCR_SRCSEL BIT(8) /* Source selection for the pixel clock */
+
+/* Sleep & timeout for pll lock/unlock */
+#define SLEEP_US 1000
+#define TIMEOUT_US 200000
+
+/*
+ * The link phase defines whether an ODD pixel is carried over together with
+ * the next EVEN pixel or together with the previous EVEN pixel.
+ *
+ * LVDS_DUAL_LINK_EVEN_ODD_PIXELS (LKPHA = 0)
+ *
+ * ,--------. ,--------. ,--------. ,--------. ,---------.
+ * | ODD LK \/ PIXEL 3 \/ PIXEL 1 \/ PIXEL' 1 \/ PIXEL' 3 |
+ * | EVEN LK /\ PIXEL 2 /\ PIXEL' 0 /\ PIXEL' 2 /\ PIXEL' 4 |
+ * `--------' `--------' `--------' `--------' `---------'
+ *
+ * LVDS_DUAL_LINK_ODD_EVEN_PIXELS (LKPHA = 1)
+ *
+ * ,--------. ,--------. ,--------. ,--------. ,---------.
+ * | ODD LK \/ PIXEL 3 \/ PIXEL 1 \/ PIXEL' 1 \/ PIXEL' 3 |
+ * | EVEN LK /\ PIXEL 4 /\ PIXEL 2 /\ PIXEL' 0 /\ PIXEL' 2 |
+ * `--------' `--------' `--------' `--------' `---------'
+ *
+ */
+enum lvds_link_type {
+ LVDS_SINGLE_LINK_PRIMARY = 0,
+ LVDS_SINGLE_LINK_SECONDARY,
+ LVDS_DUAL_LINK_EVEN_ODD_PIXELS,
+ LVDS_DUAL_LINK_ODD_EVEN_PIXELS,
+};
+
+enum lvds_pixel {
+ PIX_R_0 = 0,
+ PIX_R_1,
+ PIX_R_2,
+ PIX_R_3,
+ PIX_R_4,
+ PIX_R_5,
+ PIX_R_6,
+ PIX_R_7,
+ PIX_G_0,
+ PIX_G_1,
+ PIX_G_2,
+ PIX_G_3,
+ PIX_G_4,
+ PIX_G_5,
+ PIX_G_6,
+ PIX_G_7,
+ PIX_B_0,
+ PIX_B_1,
+ PIX_B_2,
+ PIX_B_3,
+ PIX_B_4,
+ PIX_B_5,
+ PIX_B_6,
+ PIX_B_7,
+ PIX_H_S,
+ PIX_V_S,
+ PIX_D_E,
+ PIX_C_E,
+ PIX_C_I,
+ PIX_TOG,
+ PIX_ONE,
+ PIX_ZER,
+};
+
+struct phy_reg_offsets {
+ u32 GCR; /* Global Control Register */
+ u32 CMCR1; /* Current Mode Control Register 1 */
+ u32 CMCR2; /* Current Mode Control Register 2 */
+ u32 SCR; /* Serial Control Register */
+ u32 BCR1; /* Bias Control Register 1 */
+ u32 BCR2; /* Bias Control Register 2 */
+ u32 BCR3; /* Bias Control Register 3 */
+ u32 MPLCR; /* Monitor PLL Lock Control Register */
+ u32 DCR; /* Debug Control Register */
+ u32 SSR1; /* Spare Status Register 1 */
+ u32 CFGCR; /* Configuration Control Register */
+ u32 PLLCR1; /* PLL_MODE 1 Control Register */
+ u32 PLLCR2; /* PLL_MODE 2 Control Register */
+ u32 PLLSR; /* PLL Status Register */
+ u32 PLLSDCR1; /* PLL_SD_1 Control Register */
+ u32 PLLSDCR2; /* PLL_SD_2 Control Register */
+ u32 PLLTWGCR1;/* PLL_TWG_1 Control Register */
+ u32 PLLTWGCR2;/* PLL_TWG_2 Control Register */
+ u32 PLLCPCR; /* PLL_CP Control Register */
+ u32 PLLTESTCR;/* PLL_TEST Control Register */
+};
+
+struct lvds_phy_info {
+ u32 base;
+ struct phy_reg_offsets ofs;
+};
+
+static struct lvds_phy_info lvds_phy_16ff_primary = {
+ .base = 0x1000,
+ .ofs = {
+ .GCR = 0x0,
+ .CMCR1 = 0xC,
+ .CMCR2 = 0x10,
+ .SCR = 0x20,
+ .BCR1 = 0x2C,
+ .BCR2 = 0x30,
+ .BCR3 = 0x34,
+ .MPLCR = 0x64,
+ .DCR = 0x84,
+ .SSR1 = 0x88,
+ .CFGCR = 0xA0,
+ .PLLCR1 = 0xC0,
+ .PLLCR2 = 0xC4,
+ .PLLSR = 0xC8,
+ .PLLSDCR1 = 0xCC,
+ .PLLSDCR2 = 0xD0,
+ .PLLTWGCR1 = 0xD4,
+ .PLLTWGCR2 = 0xD8,
+ .PLLCPCR = 0xE0,
+ .PLLTESTCR = 0xE8,
+ }
+};
+
+static struct lvds_phy_info lvds_phy_16ff_secondary = {
+ .base = 0x1100,
+ .ofs = {
+ .GCR = 0x0,
+ .CMCR1 = 0xC,
+ .CMCR2 = 0x10,
+ .SCR = 0x20,
+ .BCR1 = 0x2C,
+ .BCR2 = 0x30,
+ .BCR3 = 0x34,
+ .MPLCR = 0x64,
+ .DCR = 0x84,
+ .SSR1 = 0x88,
+ .CFGCR = 0xA0,
+ .PLLCR1 = 0xC0,
+ .PLLCR2 = 0xC4,
+ .PLLSR = 0xC8,
+ .PLLSDCR1 = 0xCC,
+ .PLLSDCR2 = 0xD0,
+ .PLLTWGCR1 = 0xD4,
+ .PLLTWGCR2 = 0xD8,
+ .PLLCPCR = 0xE0,
+ .PLLTESTCR = 0xE8,
+ }
+};
+
+struct stm_lvds {
+ void __iomem *base;
+ struct device *dev;
+ struct clk *pclk; /* APB peripheral clock */
+ struct clk *pllref_clk; /* Reference clock for the internal PLL */
+ struct clk_hw lvds_ck_px; /* Pixel clock */
+ u32 pixel_clock_rate; /* Pixel clock rate */
+
+ struct lvds_phy_info *primary;
+ struct lvds_phy_info *secondary;
+
+ struct drm_bridge lvds_bridge;
+ struct drm_bridge *next_bridge;
+ struct drm_connector connector;
+ struct drm_encoder *encoder;
+ struct drm_panel *panel;
+
+ u32 hw_version;
+ u32 link_type;
+};
+
+#define bridge_to_stm_lvds(b) \
+ container_of(b, struct stm_lvds, lvds_bridge)
+
+#define connector_to_stm_lvds(c) \
+ container_of(c, struct stm_lvds, connector)
+
+#define lvds_is_dual_link(lvds) \
+ ({ \
+ typeof(lvds) __lvds = (lvds); \
+ __lvds == LVDS_DUAL_LINK_EVEN_ODD_PIXELS || \
+ __lvds == LVDS_DUAL_LINK_ODD_EVEN_PIXELS; \
+ })
+
+static inline void lvds_write(struct stm_lvds *lvds, u32 reg, u32 val)
+{
+ writel(val, lvds->base + reg);
+}
+
+static inline u32 lvds_read(struct stm_lvds *lvds, u32 reg)
+{
+ return readl(lvds->base + reg);
+}
+
+static inline void lvds_set(struct stm_lvds *lvds, u32 reg, u32 mask)
+{
+ lvds_write(lvds, reg, lvds_read(lvds, reg) | mask);
+}
+
+static inline void lvds_clear(struct stm_lvds *lvds, u32 reg, u32 mask)
+{
+ lvds_write(lvds, reg, lvds_read(lvds, reg) & ~mask);
+}
+
+/*
+ * Expected JEIDA-RGB888 data to be sent in LSB format
+ * bit6 ............................bit0
+ * CHAN0 {ONE, ONE, ZERO, ZERO, ZERO, ONE, ONE}
+ * CHAN1 {G2, R7, R6, R5, R4, R3, R2}
+ * CHAN2 {B3, B2, G7, G6, G5, G4, G3}
+ * CHAN3 {DE, VS, HS, B7, B6, B5, B4}
+ * CHAN4 {CE, B1, B0, G1, G0, R1, R0}
+ */
+static enum lvds_pixel lvds_bitmap_jeida_rgb888[5][7] = {
+ { PIX_ONE, PIX_ONE, PIX_ZER, PIX_ZER, PIX_ZER, PIX_ONE, PIX_ONE },
+ { PIX_G_2, PIX_R_7, PIX_R_6, PIX_R_5, PIX_R_4, PIX_R_3, PIX_R_2 },
+ { PIX_B_3, PIX_B_2, PIX_G_7, PIX_G_6, PIX_G_5, PIX_G_4, PIX_G_3 },
+ { PIX_D_E, PIX_V_S, PIX_H_S, PIX_B_7, PIX_B_6, PIX_B_5, PIX_B_4 },
+ { PIX_C_E, PIX_B_1, PIX_B_0, PIX_G_1, PIX_G_0, PIX_R_1, PIX_R_0 }
+};
+
+/*
+ * Expected VESA-RGB888 data to be sent in LSB format
+ * bit6 ............................bit0
+ * CHAN0 {ONE, ONE, ZERO, ZERO, ZERO, ONE, ONE}
+ * CHAN1 {G0, R5, R4, R3, R2, R1, R0}
+ * CHAN2 {B1, B0, G5, G4, G3, G2, G1}
+ * CHAN3 {DE, VS, HS, B5, B4, B3, B2}
+ * CHAN4 {CE, B7, B6, G7, G6, R7, R6}
+ */
+static enum lvds_pixel lvds_bitmap_vesa_rgb888[5][7] = {
+ { PIX_ONE, PIX_ONE, PIX_ZER, PIX_ZER, PIX_ZER, PIX_ONE, PIX_ONE },
+ { PIX_G_0, PIX_R_5, PIX_R_4, PIX_R_3, PIX_R_2, PIX_R_1, PIX_R_0 },
+ { PIX_B_1, PIX_B_0, PIX_G_5, PIX_G_4, PIX_G_3, PIX_G_2, PIX_G_1 },
+ { PIX_D_E, PIX_V_S, PIX_H_S, PIX_B_5, PIX_B_4, PIX_B_3, PIX_B_2 },
+ { PIX_C_E, PIX_B_7, PIX_B_6, PIX_G_7, PIX_G_6, PIX_R_7, PIX_R_6 }
+};
+
+/*
+ * Clocks and PHY related functions
+ */
+static int lvds_pll_enable(struct stm_lvds *lvds, struct lvds_phy_info *phy)
+{
+ struct drm_device *drm = lvds->lvds_bridge.dev;
+ u32 lvds_gcr;
+ int val, ret;
+
+ /*
+ * PLL lock timing control for the monitor unmask after startup (pll_en)
+ * Adjusted value so that the masking window is opened at start-up
+ */
+ lvds_write(lvds, phy->base + phy->ofs.MPLCR, (0x200 - 0x160) << 16);
+
+ /* Enable bias */
+ lvds_write(lvds, phy->base + phy->ofs.BCR2, PHY_BCR2_BIAS_EN);
+
+ /* Enable DP, LS, BIT clock output */
+ lvds_gcr = PHY_GCR_DP_CLK_OUT | PHY_GCR_LS_CLK_OUT | PHY_GCR_BIT_CLK_OUT;
+ lvds_set(lvds, phy->base + phy->ofs.GCR, lvds_gcr);
+
+ /* Power up all output dividers */
+ lvds_set(lvds, phy->base + phy->ofs.PLLTESTCR, PHY_PLLTESTCR_EN);
+ lvds_set(lvds, phy->base + phy->ofs.PLLCR1, PHY_PLLCR1_DIV_EN);
+
+ /* Set PHY in serial transmission mode */
+ lvds_set(lvds, phy->base + phy->ofs.SCR, PHY_SCR_TX_EN);
+
+ /* Enable the LVDS PLL & wait for its lock */
+ lvds_set(lvds, phy->base + phy->ofs.PLLCR1, PHY_PLLCR1_PLL_EN);
+ ret = readl_poll_timeout_atomic(lvds->base + phy->base + phy->ofs.PLLSR,
+ val, val & PHY_PLLSR_PLL_LOCK,
+ SLEEP_US, TIMEOUT_US);
+ if (ret)
+ drm_err(drm, "!TIMEOUT! waiting PLL, let's continue\n");
+
+ /* WCLKCR_SECND_CLKPIX_SEL is for dual link */
+ lvds_write(lvds, LVDS_WCLKCR, WCLKCR_SECND_CLKPIX_SEL);
+
+ lvds_set(lvds, phy->ofs.PLLTESTCR, PHY_PLLTESTCR_CLK_EN);
+
+ return ret;
+}
+
+static int pll_get_clkout_khz(int clkin_khz, int bdiv, int mdiv, int ndiv)
+{
+ int divisor = ndiv * bdiv;
+
+ /* Prevents from division by 0 */
+ if (!divisor)
+ return 0;
+
+ return clkin_khz * mdiv / divisor;
+}
+
+#define TDIV 70
+#define NDIV_MIN 2
+#define NDIV_MAX 6
+#define BDIV_MIN 2
+#define BDIV_MAX 6
+#define MDIV_MIN 1
+#define MDIV_MAX 1023
+
+static int lvds_pll_get_params(struct stm_lvds *lvds,
+ unsigned int clkin_khz, unsigned int clkout_khz,
+ unsigned int *bdiv, unsigned int *mdiv, unsigned int *ndiv)
+{
+ int delta, best_delta; /* all in khz */
+ int i, o, n;
+
+ /* Early checks preventing division by 0 & odd results */
+ if (clkin_khz <= 0 || clkout_khz <= 0)
+ return -EINVAL;
+
+ best_delta = 1000000; /* big started value (1000000khz) */
+
+ for (i = NDIV_MIN; i <= NDIV_MAX; i++) {
+ for (o = BDIV_MIN; o <= BDIV_MAX; o++) {
+ n = DIV_ROUND_CLOSEST(i * o * clkout_khz, clkin_khz);
+ /* Check ndiv according to vco range */
+ if (n < MDIV_MIN || n > MDIV_MAX)
+ continue;
+ /* Check if new delta is better & saves parameters */
+ delta = pll_get_clkout_khz(clkin_khz, i, n, o) - clkout_khz;
+ if (delta < 0)
+ delta = -delta;
+ if (delta < best_delta) {
+ *ndiv = i;
+ *mdiv = n;
+ *bdiv = o;
+ best_delta = delta;
+ }
+ /* fast return in case of "perfect result" */
+ if (!delta)
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static void lvds_pll_config(struct stm_lvds *lvds, struct lvds_phy_info *phy)
+{
+ unsigned int pll_in_khz, bdiv = 0, mdiv = 0, ndiv = 0;
+ struct clk_hw *hwclk;
+ int multiplier;
+
+ /*
+ * The LVDS PHY includes a low power low jitter high performance and
+ * highly configuration Phase Locked Loop supporting integer and
+ * fractional multiplication ratios and Spread Spectrum Clocking. In
+ * integer mode, the only software supported feature for now, the PLL is
+ * made of a pre-divider NDIV, a feedback multiplier MDIV, followed by
+ * several post-dividers, each one with a specific application.
+ *
+ * ,------. ,-----. ,-----.
+ * Fref --> | NDIV | -Fpdf-> | PFD | --> | VCO | --------> Fvco
+ * `------' ,-> | | `-----' |
+ * | `-----' |
+ * | ,------. |
+ * `-------- | MDIV | <-----'
+ * `------'
+ *
+ * From the output of the VCO, the clock can be optionally extracted on
+ * the RCC clock observer, with a divider TDIV, for testing purpose, or
+ * is passed through a programmable post-divider BDIV. Finally, the
+ * frequency can be divided further with two fixed dividers.
+ *
+ * ,--------.
+ * ,-----> | DP div | ----------------> Fdp
+ * ,------. | `--------'
+ * Fvco --> | BDIV | ------------------------------------> Fbit
+ * | `------' ,------. |
+ * `-------------> | TDIV | --.---------------------> ClkObs
+ * '------' | ,--------.
+ * `--> | LS div | ------> Fls
+ * '--------'
+ *
+ * The LS and DP clock dividers operate at a fixed ratio of 7 and 3.5
+ * respectively with regards to fbit. LS divider converts the bit clock
+ * to a pixel clock per lane per clock sample (Fls). This is useful
+ * when used to generate a dot clock for the display unit RGB output,
+ * and DP divider is.
+ */
+
+ hwclk = __clk_get_hw(lvds->pllref_clk);
+ if (!hwclk)
+ return;
+
+ pll_in_khz = clk_hw_get_rate(hwclk) / 1000;
+
+ if (lvds_is_dual_link(lvds->link_type))
+ multiplier = 2;
+ else
+ multiplier = 1;
+
+ lvds_pll_get_params(lvds, pll_in_khz,
+ lvds->pixel_clock_rate * 7 / 1000 / multiplier,
+ &bdiv, &mdiv, &ndiv);
+
+ /* Set BDIV, MDIV and NDIV */
+ lvds_write(lvds, phy->base + phy->ofs.PLLCR2, ndiv << 16);
+ lvds_set(lvds, phy->base + phy->ofs.PLLCR2, bdiv);
+ lvds_write(lvds, phy->base + phy->ofs.PLLSDCR1, mdiv);
+
+ /* Hardcode TDIV as dynamic values are not yet implemented */
+ lvds_write(lvds, phy->base + phy->ofs.PLLTESTCR, TDIV << 16);
+
+ /*
+ * For now, PLL just needs to be in integer mode
+ * Fractional and spread spectrum clocking are not yet implemented
+ *
+ * PLL integer mode:
+ * - PMRY_PLL_TWG_STEP = PMRY_PLL_SD_INT_RATIO
+ * - EN_TWG = 0
+ * - EN_SD = 0
+ * - DOWN_SPREAD = 0
+ *
+ * PLL fractional mode:
+ * - EN_TWG = 0
+ * - EN_SD = 1
+ * - DOWN_SPREAD = 0
+ *
+ * Spread Spectrum Clocking
+ * - EN_TWG = 1
+ * - EN_SD = 1
+ */
+
+ /* Disable TWG and SD */
+ lvds_clear(lvds, phy->base + phy->ofs.PLLCR1, PHY_PLLCR1_EN_TWG | PHY_PLLCR1_EN_SD);
+
+ /* Power up bias and PLL dividers */
+ lvds_set(lvds, phy->base + phy->ofs.DCR, PHY_DCR_POWER_OK);
+ lvds_set(lvds, phy->base + phy->ofs.CMCR1, PHY_CMCR_CM_EN_DL);
+ lvds_set(lvds, phy->base + phy->ofs.CMCR2, PHY_CMCR_CM_EN_DL4);
+
+ /* Set up voltage mode */
+ lvds_set(lvds, phy->base + phy->ofs.PLLCPCR, 0x1);
+ lvds_set(lvds, phy->base + phy->ofs.BCR3, PHY_BCR3_VM_EN_DL);
+ lvds_set(lvds, phy->base + phy->ofs.BCR1, PHY_BCR1_EN_BIAS_DL);
+ /* Enable digital datalanes */
+ lvds_set(lvds, phy->base + phy->ofs.CFGCR, PHY_CFGCR_EN_DIG_DL);
+}
+
+static int lvds_pixel_clk_enable(struct clk_hw *hw)
+{
+ struct stm_lvds *lvds = container_of(hw, struct stm_lvds, lvds_ck_px);
+ struct drm_device *drm = lvds->lvds_bridge.dev;
+ struct lvds_phy_info *phy;
+ int ret;
+
+ ret = clk_prepare_enable(lvds->pclk);
+ if (ret) {
+ drm_err(drm, "Failed to enable lvds peripheral clk\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(lvds->pllref_clk);
+ if (ret) {
+ drm_err(drm, "Failed to enable lvds reference clk\n");
+ clk_disable_unprepare(lvds->pclk);
+ return ret;
+ }
+
+ /* In case we are operating in dual link the second PHY is set before the primary PHY. */
+ if (lvds->secondary) {
+ phy = lvds->secondary;
+
+ /* Release LVDS PHY from reset mode */
+ lvds_set(lvds, phy->base + phy->ofs.GCR, PHY_GCR_DIV_RSTN | PHY_GCR_RSTZ);
+ lvds_pll_config(lvds, phy);
+
+ ret = lvds_pll_enable(lvds, phy);
+ if (ret) {
+ drm_err(drm, "Failed to enable secondary PHY PLL: %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (lvds->primary) {
+ phy = lvds->primary;
+
+ /* Release LVDS PHY from reset mode */
+ lvds_set(lvds, phy->base + phy->ofs.GCR, PHY_GCR_DIV_RSTN | PHY_GCR_RSTZ);
+ lvds_pll_config(lvds, phy);
+
+ ret = lvds_pll_enable(lvds, phy);
+ if (ret) {
+ drm_err(drm, "Failed to enable primary PHY PLL: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void lvds_pixel_clk_disable(struct clk_hw *hw)
+{
+ struct stm_lvds *lvds = container_of(hw, struct stm_lvds, lvds_ck_px);
+
+ /*
+ * For each PHY:
+ * Disable DP, LS, BIT clock outputs
+ * Shutdown the PLL
+ * Assert LVDS PHY in reset mode
+ */
+
+ if (lvds->primary) {
+ lvds_clear(lvds, lvds->primary->base + lvds->primary->ofs.GCR,
+ (PHY_GCR_DP_CLK_OUT | PHY_GCR_LS_CLK_OUT | PHY_GCR_BIT_CLK_OUT));
+ lvds_clear(lvds, lvds->primary->base + lvds->primary->ofs.PLLCR1,
+ PHY_PLLCR1_PLL_EN);
+ lvds_clear(lvds, lvds->primary->base + lvds->primary->ofs.GCR,
+ PHY_GCR_DIV_RSTN | PHY_GCR_RSTZ);
+ }
+
+ if (lvds->secondary) {
+ lvds_clear(lvds, lvds->secondary->base + lvds->secondary->ofs.GCR,
+ (PHY_GCR_DP_CLK_OUT | PHY_GCR_LS_CLK_OUT | PHY_GCR_BIT_CLK_OUT));
+ lvds_clear(lvds, lvds->secondary->base + lvds->secondary->ofs.PLLCR1,
+ PHY_PLLCR1_PLL_EN);
+ lvds_clear(lvds, lvds->secondary->base + lvds->secondary->ofs.GCR,
+ PHY_GCR_DIV_RSTN | PHY_GCR_RSTZ);
+ }
+
+ clk_disable_unprepare(lvds->pllref_clk);
+ clk_disable_unprepare(lvds->pclk);
+}
+
+static unsigned long lvds_pixel_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct stm_lvds *lvds = container_of(hw, struct stm_lvds, lvds_ck_px);
+ struct drm_device *drm = lvds->lvds_bridge.dev;
+ unsigned int pll_in_khz, bdiv, mdiv, ndiv;
+ int ret, multiplier, pll_out_khz;
+ u32 val;
+
+ ret = clk_prepare_enable(lvds->pclk);
+ if (ret) {
+ drm_err(drm, "Failed to enable lvds peripheral clk\n");
+ return 0;
+ }
+
+ if (lvds_is_dual_link(lvds->link_type))
+ multiplier = 2;
+ else
+ multiplier = 1;
+
+ val = lvds_read(lvds, lvds->primary->base + lvds->primary->ofs.PLLCR2);
+
+ ndiv = (val & PHY_PLLCR2_NDIV) >> 16;
+ bdiv = (val & PHY_PLLCR2_BDIV) >> 0;
+
+ mdiv = (unsigned int)lvds_read(lvds,
+ lvds->primary->base + lvds->primary->ofs.PLLSDCR1);
+
+ pll_in_khz = (unsigned int)(parent_rate / 1000);
+
+ /* Compute values if not yet accessible */
+ if (val == 0 || mdiv == 0) {
+ lvds_pll_get_params(lvds, pll_in_khz,
+ lvds->pixel_clock_rate * 7 / 1000 / multiplier,
+ &bdiv, &mdiv, &ndiv);
+ }
+
+ pll_out_khz = pll_get_clkout_khz(pll_in_khz, bdiv, mdiv, ndiv);
+ drm_dbg(drm, "ndiv %d , bdiv %d, mdiv %d, pll_out_khz %d\n",
+ ndiv, bdiv, mdiv, pll_out_khz);
+
+ /*
+ * 1/7 because for each pixel in 1 lane there is 7 bits
+ * We want pixclk, not bitclk
+ */
+ lvds->pixel_clock_rate = pll_out_khz * 1000 * multiplier / 7;
+
+ clk_disable_unprepare(lvds->pclk);
+
+ return (unsigned long)lvds->pixel_clock_rate;
+}
+
+static int lvds_pixel_clk_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct stm_lvds *lvds = container_of(hw, struct stm_lvds, lvds_ck_px);
+ unsigned int pll_in_khz, bdiv = 0, mdiv = 0, ndiv = 0;
+ const struct drm_connector *connector;
+ const struct drm_display_mode *mode;
+ int multiplier;
+
+ connector = &lvds->connector;
+ if (!connector)
+ return -EINVAL;
+
+ if (list_empty(&connector->modes)) {
+ drm_dbg(connector->dev, "connector: empty modes list\n");
+ return -EINVAL;
+ }
+
+ mode = list_first_entry(&connector->modes,
+ struct drm_display_mode, head);
+
+ pll_in_khz = (unsigned int)(req->best_parent_rate / 1000);
+
+ if (lvds_is_dual_link(lvds->link_type))
+ multiplier = 2;
+ else
+ multiplier = 1;
+
+ lvds_pll_get_params(lvds, pll_in_khz, mode->clock * 7 / multiplier, &bdiv, &mdiv, &ndiv);
+
+ /*
+ * 1/7 because for each pixel in 1 lane there is 7 bits
+ * We want pixclk, not bitclk
+ */
+ lvds->pixel_clock_rate = (unsigned long)pll_get_clkout_khz(pll_in_khz, bdiv, mdiv, ndiv)
+ * 1000 * multiplier / 7;
+
+ req->rate = lvds->pixel_clock_rate;
+
+ return 0;
+}
+
+static const struct clk_ops lvds_pixel_clk_ops = {
+ .enable = lvds_pixel_clk_enable,
+ .disable = lvds_pixel_clk_disable,
+ .recalc_rate = lvds_pixel_clk_recalc_rate,
+ .determine_rate = lvds_pixel_clk_determine_rate,
+};
+
+static const struct clk_init_data clk_data = {
+ .name = "clk_pix_lvds",
+ .ops = &lvds_pixel_clk_ops,
+ .parent_names = (const char * []) {"ck_ker_lvdsphy"},
+ .num_parents = 1,
+ .flags = CLK_IGNORE_UNUSED,
+};
+
+static void lvds_pixel_clk_unregister(void *data)
+{
+ struct stm_lvds *lvds = data;
+
+ of_clk_del_provider(lvds->dev->of_node);
+ clk_hw_unregister(&lvds->lvds_ck_px);
+}
+
+static int lvds_pixel_clk_register(struct stm_lvds *lvds)
+{
+ struct device_node *node = lvds->dev->of_node;
+ int ret;
+
+ lvds->lvds_ck_px.init = &clk_data;
+
+ /* set the rate by default at 148500000 */
+ lvds->pixel_clock_rate = 148500000;
+
+ ret = clk_hw_register(lvds->dev, &lvds->lvds_ck_px);
+ if (ret)
+ return ret;
+
+ ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get,
+ &lvds->lvds_ck_px);
+ if (ret)
+ clk_hw_unregister(&lvds->lvds_ck_px);
+
+ return ret;
+}
+
+/*
+ * Host configuration related
+ */
+static void lvds_config_data_mapping(struct stm_lvds *lvds)
+{
+ struct drm_device *drm = lvds->lvds_bridge.dev;
+ const struct drm_display_info *info;
+ enum lvds_pixel (*bitmap)[7];
+ u32 lvds_dmlcr, lvds_dmmcr;
+ int i;
+
+ info = &(&lvds->connector)->display_info;
+ if (!info->num_bus_formats || !info->bus_formats) {
+ drm_warn(drm, "No LVDS bus format reported\n");
+ return;
+ }
+
+ switch (info->bus_formats[0]) {
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: /* VESA-RGB666 */
+ drm_warn(drm, "Pixel format with data mapping not yet supported.\n");
+ return;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: /* VESA-RGB888 */
+ bitmap = lvds_bitmap_vesa_rgb888;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: /* JEIDA-RGB888 */
+ bitmap = lvds_bitmap_jeida_rgb888;
+ break;
+ default:
+ drm_warn(drm, "Unsupported LVDS bus format 0x%04x\n", info->bus_formats[0]);
+ return;
+ }
+
+ /* Set bitmap for each lane */
+ for (i = 0; i < 5; i++) {
+ lvds_dmlcr = ((bitmap[i][0])
+ + (bitmap[i][1] << 5)
+ + (bitmap[i][2] << 10)
+ + (bitmap[i][3] << 15));
+ lvds_dmmcr = ((bitmap[i][4])
+ + (bitmap[i][5] << 5)
+ + (bitmap[i][6] << 10));
+
+ lvds_write(lvds, LVDS_DMLCR(i), lvds_dmlcr);
+ lvds_write(lvds, LVDS_DMMCR(i), lvds_dmmcr);
+ }
+}
+
+static void lvds_config_mode(struct stm_lvds *lvds)
+{
+ u32 bus_flags, lvds_cr = 0, lvds_cdl1cr = 0, lvds_cdl2cr = 0;
+ const struct drm_display_mode *mode;
+ const struct drm_connector *connector;
+
+ connector = &lvds->connector;
+ if (!connector)
+ return;
+
+ if (list_empty(&connector->modes)) {
+ drm_dbg(connector->dev, "connector: empty modes list\n");
+ return;
+ }
+
+ bus_flags = connector->display_info.bus_flags;
+ mode = list_first_entry(&connector->modes,
+ struct drm_display_mode, head);
+
+ lvds_clear(lvds, LVDS_CR, CR_LKMOD);
+ lvds_clear(lvds, LVDS_CDL1CR, CDLCR_DISTR0 | CDLCR_DISTR1 | CDLCR_DISTR2 |
+ CDLCR_DISTR3 | CDLCR_DISTR4);
+ lvds_clear(lvds, LVDS_CDL2CR, CDLCR_DISTR0 | CDLCR_DISTR1 | CDLCR_DISTR2 |
+ CDLCR_DISTR3 | CDLCR_DISTR4);
+
+ /* Set channel distribution */
+ if (lvds->primary)
+ lvds_cdl1cr = CDL1CR_DEFAULT;
+
+ if (lvds->secondary) {
+ lvds_cr |= CR_LKMOD;
+ lvds_cdl2cr = CDL2CR_DEFAULT;
+ }
+
+ /* Set signal polarity */
+ if (bus_flags & DRM_BUS_FLAG_DE_LOW)
+ lvds_cr |= CR_DEPOL;
+
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ lvds_cr |= CR_HSPOL;
+
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ lvds_cr |= CR_VSPOL;
+
+ switch (lvds->link_type) {
+ case LVDS_DUAL_LINK_EVEN_ODD_PIXELS: /* LKPHA = 0 */
+ lvds_cr &= ~CR_LKPHA;
+ break;
+ case LVDS_DUAL_LINK_ODD_EVEN_PIXELS: /* LKPHA = 1 */
+ lvds_cr |= CR_LKPHA;
+ break;
+ default:
+ drm_notice(lvds->lvds_bridge.dev, "No phase precised, setting default\n");
+ lvds_cr &= ~CR_LKPHA;
+ break;
+ }
+
+ /* Write config to registers */
+ lvds_set(lvds, LVDS_CR, lvds_cr);
+ lvds_write(lvds, LVDS_CDL1CR, lvds_cdl1cr);
+ lvds_write(lvds, LVDS_CDL2CR, lvds_cdl2cr);
+}
+
+static int lvds_connector_get_modes(struct drm_connector *connector)
+{
+ struct stm_lvds *lvds = connector_to_stm_lvds(connector);
+
+ return drm_panel_get_modes(lvds->panel, connector);
+}
+
+static int lvds_connector_atomic_check(struct drm_connector *connector,
+ struct drm_atomic_state *state)
+{
+ const struct drm_display_mode *panel_mode;
+ struct drm_connector_state *conn_state;
+ struct drm_crtc_state *crtc_state;
+
+ conn_state = drm_atomic_get_new_connector_state(state, connector);
+ if (!conn_state)
+ return -EINVAL;
+
+ if (list_empty(&connector->modes)) {
+ drm_dbg(connector->dev, "connector: empty modes list\n");
+ return -EINVAL;
+ }
+
+ if (!conn_state->crtc)
+ return -EINVAL;
+
+ panel_mode = list_first_entry(&connector->modes,
+ struct drm_display_mode, head);
+
+ /* We're not allowed to modify the resolution. */
+ crtc_state = drm_atomic_get_crtc_state(state, conn_state->crtc);
+ if (IS_ERR(crtc_state))
+ return PTR_ERR(crtc_state);
+
+ if (crtc_state->mode.hdisplay != panel_mode->hdisplay ||
+ crtc_state->mode.vdisplay != panel_mode->vdisplay)
+ return -EINVAL;
+
+ /* The flat panel mode is fixed, just copy it to the adjusted mode. */
+ drm_mode_copy(&crtc_state->adjusted_mode, panel_mode);
+
+ return 0;
+}
+
+static const struct drm_connector_helper_funcs lvds_conn_helper_funcs = {
+ .get_modes = lvds_connector_get_modes,
+ .atomic_check = lvds_connector_atomic_check,
+};
+
+static const struct drm_connector_funcs lvds_conn_funcs = {
+ .reset = drm_atomic_helper_connector_reset,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int lvds_attach(struct drm_bridge *bridge, struct drm_encoder *encoder,
+ enum drm_bridge_attach_flags flags)
+{
+ struct stm_lvds *lvds = bridge_to_stm_lvds(bridge);
+ struct drm_connector *connector = &lvds->connector;
+ int ret;
+
+ if (!encoder) {
+ drm_err(bridge->dev, "Parent encoder object not found\n");
+ return -ENODEV;
+ }
+
+ /* Set the encoder type as caller does not know it */
+ encoder->encoder_type = DRM_MODE_ENCODER_LVDS;
+
+ /* No cloning support */
+ encoder->possible_clones = 0;
+
+ /* If we have a next bridge just attach it. */
+ if (lvds->next_bridge)
+ return drm_bridge_attach(encoder, lvds->next_bridge,
+ bridge, flags);
+
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ drm_err(bridge->dev, "Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
+ /* Otherwise if we have a panel, create a connector. */
+ if (!lvds->panel)
+ return 0;
+
+ ret = drm_connector_init(bridge->dev, connector,
+ &lvds_conn_funcs, DRM_MODE_CONNECTOR_LVDS);
+ if (ret < 0)
+ return ret;
+
+ drm_connector_helper_add(connector, &lvds_conn_helper_funcs);
+
+ ret = drm_connector_attach_encoder(connector, encoder);
+
+ return ret;
+}
+
+static void lvds_atomic_enable(struct drm_bridge *bridge,
+ struct drm_atomic_state *state)
+{
+ struct stm_lvds *lvds = bridge_to_stm_lvds(bridge);
+ struct drm_connector_state *conn_state;
+ struct drm_connector *connector;
+ int ret;
+
+ ret = clk_prepare_enable(lvds->pclk);
+ if (ret) {
+ drm_err(bridge->dev, "Failed to enable lvds peripheral clk\n");
+ return;
+ }
+
+ connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
+ if (!connector)
+ return;
+
+ conn_state = drm_atomic_get_new_connector_state(state, connector);
+ if (!conn_state)
+ return;
+
+ lvds_config_mode(lvds);
+
+ /* Set Data Mapping */
+ lvds_config_data_mapping(lvds);
+
+ /* Turn the output on. */
+ lvds_set(lvds, LVDS_CR, CR_LVDSEN);
+
+ if (lvds->panel) {
+ drm_panel_prepare(lvds->panel);
+ drm_panel_enable(lvds->panel);
+ }
+}
+
+static void lvds_atomic_disable(struct drm_bridge *bridge,
+ struct drm_atomic_state *state)
+{
+ struct stm_lvds *lvds = bridge_to_stm_lvds(bridge);
+
+ if (lvds->panel) {
+ drm_panel_disable(lvds->panel);
+ drm_panel_unprepare(lvds->panel);
+ }
+
+ /* Disable LVDS module */
+ lvds_clear(lvds, LVDS_CR, CR_LVDSEN);
+
+ clk_disable_unprepare(lvds->pclk);
+}
+
+static const struct drm_bridge_funcs lvds_bridge_funcs = {
+ .attach = lvds_attach,
+ .atomic_enable = lvds_atomic_enable,
+ .atomic_disable = lvds_atomic_disable,
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+ .atomic_reset = drm_atomic_helper_bridge_reset,
+};
+
+static int lvds_probe(struct platform_device *pdev)
+{
+ struct device_node *port1, *port2, *remote;
+ struct device *dev = &pdev->dev;
+ struct reset_control *rstc;
+ struct stm_lvds *lvds;
+ int ret, dual_link;
+
+ dev_dbg(dev, "Probing LVDS driver...\n");
+
+ lvds = devm_drm_bridge_alloc(dev, struct stm_lvds, lvds_bridge, &lvds_bridge_funcs);
+ if (IS_ERR(lvds))
+ return PTR_ERR(lvds);
+
+ lvds->dev = dev;
+
+ ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0,
+ &lvds->panel, &lvds->next_bridge);
+ if (ret) {
+ dev_err_probe(dev, ret, "Panel not found\n");
+ return ret;
+ }
+
+ lvds->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(lvds->base)) {
+ ret = PTR_ERR(lvds->base);
+ dev_err(dev, "Unable to get regs %d\n", ret);
+ return ret;
+ }
+
+ lvds->pclk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(lvds->pclk)) {
+ ret = PTR_ERR(lvds->pclk);
+ dev_err(dev, "Unable to get peripheral clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(lvds->pclk);
+ if (ret) {
+ dev_err(dev, "%s: Failed to enable peripheral clk\n", __func__);
+ return ret;
+ }
+
+ rstc = devm_reset_control_get_exclusive(dev, NULL);
+
+ if (IS_ERR(rstc)) {
+ ret = PTR_ERR(rstc);
+ goto err_lvds_probe;
+ }
+
+ reset_control_assert(rstc);
+ usleep_range(10, 20);
+ reset_control_deassert(rstc);
+
+ port1 = of_graph_get_port_by_id(dev->of_node, 1);
+ port2 = of_graph_get_port_by_id(dev->of_node, 2);
+ dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2);
+
+ switch (dual_link) {
+ case DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS:
+ lvds->link_type = LVDS_DUAL_LINK_ODD_EVEN_PIXELS;
+ lvds->primary = &lvds_phy_16ff_primary;
+ lvds->secondary = &lvds_phy_16ff_secondary;
+ break;
+ case DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS:
+ lvds->link_type = LVDS_DUAL_LINK_EVEN_ODD_PIXELS;
+ lvds->primary = &lvds_phy_16ff_primary;
+ lvds->secondary = &lvds_phy_16ff_secondary;
+ break;
+ case -EINVAL:
+ /*
+ * drm_of_lvds_get_dual_pixel_order returns 4 possible values.
+ * In the case where the returned value is an error, it can be
+ * either ENODEV or EINVAL. Seeing the structure of this
+ * function, EINVAL means that either port1 or port2 is not
+ * present in the device tree.
+ * In that case, the lvds panel can be a single link panel, or
+ * there is a semantical error in the device tree code.
+ */
+ remote = of_get_next_available_child(port1, NULL);
+ if (remote) {
+ if (of_graph_get_remote_endpoint(remote)) {
+ lvds->link_type = LVDS_SINGLE_LINK_PRIMARY;
+ lvds->primary = &lvds_phy_16ff_primary;
+ lvds->secondary = NULL;
+ } else {
+ ret = -EINVAL;
+ }
+
+ of_node_put(remote);
+ }
+
+ remote = of_get_next_available_child(port2, NULL);
+ if (remote) {
+ if (of_graph_get_remote_endpoint(remote)) {
+ lvds->link_type = LVDS_SINGLE_LINK_SECONDARY;
+ lvds->primary = NULL;
+ lvds->secondary = &lvds_phy_16ff_secondary;
+ } else {
+ ret = (ret == -EINVAL) ? -EINVAL : 0;
+ }
+
+ of_node_put(remote);
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ goto err_lvds_probe;
+ }
+ of_node_put(port1);
+ of_node_put(port2);
+
+ lvds->pllref_clk = devm_clk_get(dev, "ref");
+ if (IS_ERR(lvds->pllref_clk)) {
+ ret = PTR_ERR(lvds->pllref_clk);
+ dev_err(dev, "Unable to get reference clock: %d\n", ret);
+ goto err_lvds_probe;
+ }
+
+ ret = lvds_pixel_clk_register(lvds);
+ if (ret) {
+ dev_err(dev, "Failed to register LVDS pixel clock: %d\n", ret);
+ goto err_lvds_probe;
+ }
+
+ lvds->lvds_bridge.of_node = dev->of_node;
+ lvds->hw_version = lvds_read(lvds, LVDS_VERR);
+
+ dev_info(dev, "version 0x%02x initialized\n", lvds->hw_version);
+
+ drm_bridge_add(&lvds->lvds_bridge);
+
+ platform_set_drvdata(pdev, lvds);
+
+ clk_disable_unprepare(lvds->pclk);
+
+ return 0;
+
+err_lvds_probe:
+ clk_disable_unprepare(lvds->pclk);
+
+ return ret;
+}
+
+static void lvds_remove(struct platform_device *pdev)
+{
+ struct stm_lvds *lvds = platform_get_drvdata(pdev);
+
+ lvds_pixel_clk_unregister(lvds);
+
+ drm_bridge_remove(&lvds->lvds_bridge);
+}
+
+static const struct of_device_id lvds_dt_ids[] = {
+ {
+ .compatible = "st,stm32mp25-lvds",
+ .data = NULL
+ },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, lvds_dt_ids);
+
+static struct platform_driver lvds_platform_driver = {
+ .probe = lvds_probe,
+ .remove = lvds_remove,
+ .driver = {
+ .name = "stm32-display-lvds",
+ .of_match_table = lvds_dt_ids,
+ },
+};
+
+module_platform_driver(lvds_platform_driver);
+
+MODULE_AUTHOR("Raphaël Gallais-Pou <raphael.gallais-pou@foss.st.com>");
+MODULE_AUTHOR("Philippe Cornu <philippe.cornu@foss.st.com>");
+MODULE_AUTHOR("Yannick Fertre <yannick.fertre@foss.st.com>");
+MODULE_DESCRIPTION("STMicroelectronics LVDS Display Interface Transmitter DRM driver");
+MODULE_LICENSE("GPL");