#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>
#define LVDS_CR 0x0000
#define LVDS_DMLCR0 0x0004
#define LVDS_DMMCR0 0x0008
#define LVDS_DMLCR1 0x000C
#define LVDS_DMMCR1 0x0010
#define LVDS_DMLCR2 0x0014
#define LVDS_DMMCR2 0x0018
#define LVDS_DMLCR3 0x001C
#define LVDS_DMMCR3 0x0020
#define LVDS_DMLCR4 0x0024
#define LVDS_DMMCR4 0x0028
#define LVDS_CDL1CR 0x002C
#define LVDS_CDL2CR 0x0030
#define CDL1CR_DEFAULT 0x04321
#define CDL2CR_DEFAULT 0x59876
#define LVDS_DMLCR(bit) (LVDS_DMLCR0 + 0x8 * (bit))
#define LVDS_DMMCR(bit) (LVDS_DMMCR0 + 0x8 * (bit))
#define LVDS_WCLKCR 0x11B0
#define LVDS_HWCFGR 0x1FF0
#define LVDS_VERR 0x1FF4
#define LVDS_IPIDR 0x1FF8
#define LVDS_SIDR 0x1FFC
#define CR_LVDSEN BIT(0)
#define CR_HSPOL BIT(1)
#define CR_VSPOL BIT(2)
#define CR_DEPOL BIT(3)
#define CR_CI BIT(4)
#define CR_LKMOD BIT(5)
#define CR_LKPHA BIT(6)
#define CR_LK1POL GENMASK(20, 16)
#define CR_LK2POL GENMASK(25, 21)
#define DMMCR_MAP0 GENMASK(4, 0)
#define DMMCR_MAP1 GENMASK(9, 5)
#define DMMCR_MAP2 GENMASK(14, 10)
#define DMMCR_MAP3 GENMASK(19, 15)
#define DMLCR_MAP4 GENMASK(4, 0)
#define DMLCR_MAP5 GENMASK(9, 5)
#define DMLCR_MAP6 GENMASK(14, 10)
#define CDLCR_DISTR0 GENMASK(3, 0)
#define CDLCR_DISTR1 GENMASK(7, 4)
#define CDLCR_DISTR2 GENMASK(11, 8)
#define CDLCR_DISTR3 GENMASK(15, 12)
#define CDLCR_DISTR4 GENMASK(19, 16)
#define PHY_GCR_BIT_CLK_OUT BIT(0)
#define PHY_GCR_LS_CLK_OUT BIT(4)
#define PHY_GCR_DP_CLK_OUT BIT(8)
#define PHY_GCR_RSTZ BIT(24)
#define PHY_GCR_DIV_RSTN BIT(25)
#define PHY_SCR_TX_EN BIT(16)
#define PHY_CMCR_CM_EN_DL (BIT(28) | BIT(20) | BIT(12) | BIT(4))
#define PHY_CMCR_CM_EN_DL4 BIT(4)
#define PHY_BCR1_EN_BIAS_DL (BIT(16) | BIT(12) | BIT(8) | BIT(4) | BIT(0))
#define PHY_BCR2_BIAS_EN BIT(28)
#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)
#define PHY_PLLCR1_PLL_EN BIT(0)
#define PHY_PLLCR1_EN_SD BIT(1)
#define PHY_PLLCR1_EN_TWG BIT(2)
#define PHY_PLLCR1_DIV_EN BIT(8)
#define PHY_PLLCR2_NDIV GENMASK(25, 16)
#define PHY_PLLCR2_BDIV GENMASK(9, 0)
#define PHY_PLLSR_PLL_LOCK BIT(0)
#define PHY_PLLSDCR1_MDIV GENMASK(9, 0)
#define PHY_PLLTESTCR_TDIV GENMASK(25, 16)
#define PHY_PLLTESTCR_CLK_EN BIT(0)
#define PHY_PLLTESTCR_EN BIT(8)
#define WCLKCR_SECND_CLKPIX_SEL BIT(0)
#define WCLKCR_SRCSEL BIT(8)
#define SLEEP_US 1000
#define TIMEOUT_US 200000
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;
u32 CMCR1;
u32 CMCR2;
u32 SCR;
u32 BCR1;
u32 BCR2;
u32 BCR3;
u32 MPLCR;
u32 DCR;
u32 SSR1;
u32 CFGCR;
u32 PLLCR1;
u32 PLLCR2;
u32 PLLSR;
u32 PLLSDCR1;
u32 PLLSDCR2;
u32 PLLTWGCR1;
u32 PLLTWGCR2;
u32 PLLCPCR;
u32 PLLTESTCR;
};
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;
struct clk *pllref_clk;
struct clk_hw lvds_ck_px;
u32 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);
}
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 }
};
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 }
};
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;
lvds_write(lvds, phy->base + phy->ofs.MPLCR, (0x200 - 0x160) << 16);
lvds_write(lvds, phy->base + phy->ofs.BCR2, PHY_BCR2_BIAS_EN);
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);
lvds_set(lvds, phy->base + phy->ofs.PLLTESTCR, PHY_PLLTESTCR_EN);
lvds_set(lvds, phy->base + phy->ofs.PLLCR1, PHY_PLLCR1_DIV_EN);
lvds_set(lvds, phy->base + phy->ofs.SCR, PHY_SCR_TX_EN);
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");
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;
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;
int i, o, n;
if (clkin_khz <= 0 || clkout_khz <= 0)
return -EINVAL;
best_delta = 1000000;
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);
if (n < MDIV_MIN || n > MDIV_MAX)
continue;
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;
}
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;
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);
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);
lvds_write(lvds, phy->base + phy->ofs.PLLTESTCR, TDIV << 16);
lvds_clear(lvds, phy->base + phy->ofs.PLLCR1, PHY_PLLCR1_EN_TWG | PHY_PLLCR1_EN_SD);
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);
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);
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;
}
if (lvds->secondary) {
phy = lvds->secondary;
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;
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);
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);
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);
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);
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;
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;
}
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:
drm_warn(drm, "Pixel format with data mapping not yet supported.\n");
return;
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
bitmap = lvds_bitmap_vesa_rgb888;
break;
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
bitmap = lvds_bitmap_jeida_rgb888;
break;
default:
drm_warn(drm, "Unsupported LVDS bus format 0x%04x\n", info->bus_formats[0]);
return;
}
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);
if (lvds->primary)
lvds_cdl1cr = CDL1CR_DEFAULT;
if (lvds->secondary) {
lvds_cr |= CR_LKMOD;
lvds_cdl2cr = CDL2CR_DEFAULT;
}
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:
lvds_cr &= ~CR_LKPHA;
break;
case LVDS_DUAL_LINK_ODD_EVEN_PIXELS:
lvds_cr |= CR_LKPHA;
break;
default:
drm_notice(lvds->lvds_bridge.dev, "No phase precised, setting default\n");
lvds_cr &= ~CR_LKPHA;
break;
}
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);
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;
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;
}
encoder->encoder_type = DRM_MODE_ENCODER_LVDS;
encoder->possible_clones = 0;
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;
}
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);
lvds_config_data_mapping(lvds);
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);
}
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:
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
},
{ }
};
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");