On Mon, Apr 27, 2020 at 04:00:35PM +0200, Angelo Ribeiro wrote: > Add Synopsys DesignWare IPK specific extensions for Synopsys DesignWare > MIPI DSI Host driver. > > Cc: Maarten Lankhorst <maarten.lankhorst@xxxxxxxxxxxxxxx> > Cc: Maxime Ripard <mripard@xxxxxxxxxx> > Cc: David Airlie <airlied@xxxxxxxx> > Cc: Daniel Vetter <daniel@xxxxxxxx> > Cc: Sam Ravnborg <sam@xxxxxxxxxxxx> > Cc: Gustavo Pimentel <gustavo.pimentel@xxxxxxxxxxxx> > Cc: Joao Pinto <jpinto@xxxxxxxxxxxx> > Signed-off-by: Angelo Ribeiro <angelo.ribeiro@xxxxxxxxxxxx> I've dumped this on a pile of bridge drivers by now, but I don't think the dw-mipi-dsi organization makes much sense. I think what we'd need is: - drm_encoder is handled by the drm_device driver, not by dw-mipi-dsi drm_bridge driver - the glue code for the various soc specific implementations (like ipk here) should be put behind the drm_bridge abstraction. Otherwise I'm not really seeing why exactly dw-mipi-dsi is a bridge driver if it doesn't work like a bridge driver - Probably we should put all these files into drm/bridge/dw-mipi-dsi/ - drm_device drivers should get at their bridges with one of the standard of helpers we have in drm_bridge, not by directly calling into a bridge drivers. I know that dw-hdmi is using the exact same code pattern, but we got to stop this eventually or it becomes an unfixable mess. -Daniel > --- > Changes since v3: > - Rearranged headers. > --- > drivers/gpu/drm/ipk/Kconfig | 9 + > drivers/gpu/drm/ipk/Makefile | 2 + > drivers/gpu/drm/ipk/dw-mipi-dsi-ipk.c | 557 ++++++++++++++++++++++++++++++++++ > 3 files changed, 568 insertions(+) > create mode 100644 drivers/gpu/drm/ipk/dw-mipi-dsi-ipk.c > > diff --git a/drivers/gpu/drm/ipk/Kconfig b/drivers/gpu/drm/ipk/Kconfig > index 1f87444..49819e5 100644 > --- a/drivers/gpu/drm/ipk/Kconfig > +++ b/drivers/gpu/drm/ipk/Kconfig > @@ -11,3 +11,12 @@ config DRM_IPK > Enable support for the Synopsys DesignWare DRM DSI. > To compile this driver as a module, choose M here: the module > will be called ipk-drm. > + > +config DRM_IPK_DSI > + tristate "Synopsys DesignWare IPK specific extensions for MIPI DSI" > + depends on DRM_IPK > + select DRM_DW_MIPI_DSI > + help > + Choose this option for Synopsys DesignWare IPK MIPI DSI support. > + To compile this driver as a module, choose M here: the module > + will be called dw-mipi-dsi-ipk. > diff --git a/drivers/gpu/drm/ipk/Makefile b/drivers/gpu/drm/ipk/Makefile > index 6a1a911..f22d590 100644 > --- a/drivers/gpu/drm/ipk/Makefile > +++ b/drivers/gpu/drm/ipk/Makefile > @@ -2,3 +2,5 @@ > ipk-drm-y := dw-drv.o dw-vpg.o > > obj-$(CONFIG_DRM_IPK) += ipk-drm.o > + > +obj-$(CONFIG_DRM_IPK_DSI) += dw-mipi-dsi-ipk.o > diff --git a/drivers/gpu/drm/ipk/dw-mipi-dsi-ipk.c b/drivers/gpu/drm/ipk/dw-mipi-dsi-ipk.c > new file mode 100644 > index 0000000..f8ac4ca > --- /dev/null > +++ b/drivers/gpu/drm/ipk/dw-mipi-dsi-ipk.c > @@ -0,0 +1,557 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates. > + * Synopsys DesignWare MIPI DSI solution driver > + * > + * Author: Angelo Ribeiro <angelo.ribeiro@xxxxxxxxxxxx> > + * Author: Luis Oliveira <luis.oliveira@xxxxxxxxxxxx> > + */ > + > +#include <linux/clk.h> > +#include <linux/iopoll.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/platform_device.h> > + > +#include <video/mipi_display.h> > + > +#include <drm/bridge/dw_mipi_dsi.h> > +#include <drm/drm_crtc.h> > +#include <drm/drm_device.h> > +#include <drm/drm_mipi_dsi.h> > +#include <drm/drm_print.h> > + > +#define DW_DPHY_LPCLK_CTRL 0x94 > +#define DW_DPHY_RSTZ 0xA0 > +#define DW_DPHY_IF_CFG 0xA4 > +#define DW_DPHY_ULPS_CTRL 0xA8 > +#define DW_DPHY_TX_TRIGGERS 0xAC > +#define DW_DPHY_STATUS 0xB0 > +#define DW_DPHY_TST_CTRL0 0xB4 > +#define DW_DPHY_TST_CTRL1 0xB8 > +#define DW_GEN3_IF_TESTER 0x3c > +#define DW_GEN3_IF_SOC_PLL 0x48 > +#define DW_GEN3_IF_SOC_PLL_EN 0x4C > + > +#define DW_12BITS_DPHY_RDY_L0 0x507 > +#define DW_12BITS_DPHY_RDY_L1 0x707 > +#define DW_12BITS_DPHY_RDY_L2 0x907 > +#define DW_12BITS_DPHY_RDY_L3 0xB07 > + > +#define DW_LANE_MIN_KBPS 80000 > +#define DW_LANE_MAX_KBPS 2500000000 > +#define DW_DPHY_DIV_UPPER_LIMIT 8000 > +#define DW_DPHY_DIV_LOWER_LIMIT 2000 > +#define DW_MIN_OUTPUT_FREQ 80 > +#define DW_LPHS_TIM_TRANSIONS 0x40 > + > +enum dw_glueiftester { > + GLUE_LOGIC = 0x4, > + RX_PHY = 0x2, > + TX_PHY = 0x1, > + RESET = 0x0, > +}; > + > +struct dw_range_dphy { > + u32 freq; > + u8 hs_freq_range; > + u32 osc_freq_target; > +} dw_range_gen3[] = { > + { 80, 0x00, 0x3f }, { 90, 0x10, 0x3f }, { 100, 0x20, 0x3f }, > + { 110, 0x30, 0x39 }, { 120, 0x01, 0x39 }, { 130, 0x11, 0x39 }, > + { 140, 0x21, 0x39 }, { 150, 0x31, 0x39 }, { 160, 0x02, 0x39 }, > + { 170, 0x12, 0x2f }, { 180, 0x22, 0x2f }, { 190, 0x32, 0x2f }, > + { 205, 0x03, 0x2f }, { 220, 0x13, 0x29 }, { 235, 0x23, 0x29 }, > + { 250, 0x33, 0x29 }, { 275, 0x04, 0x29 }, { 300, 0x14, 0x29 }, > + { 325, 0x25, 0x29 }, { 350, 0x35, 0x1f }, { 400, 0x05, 0x1f }, > + { 450, 0x16, 0x19 }, { 500, 0x26, 0x19 }, { 550, 0x37, 0x19 }, > + { 600, 0x07, 0x19 }, { 650, 0x18, 0x19 }, { 700, 0x28, 0x0f }, > + { 750, 0x39, 0x0f }, { 800, 0x09, 0x0f }, { 850, 0x19, 0x0f }, > + { 900, 0x29, 0x09 }, { 950, 0x3a, 0x09 }, { 1000, 0x0a, 0x09 }, > + { 1050, 0x1a, 0x09 }, { 1100, 0x2a, 0x09 }, { 1150, 0x3b, 0x09 }, > + { 1200, 0x0b, 0x09 }, { 1250, 0x1b, 0x09 }, { 1300, 0x2b, 0x09 }, > + { 1350, 0x3c, 0x03 }, { 1400, 0x0c, 0x03 }, { 1450, 0x1c, 0x03 }, > + { 1500, 0x2c, 0x03 }, { 1550, 0x3d, 0x03 }, { 1600, 0x0d, 0x03 }, > + { 1650, 0x1d, 0x03 }, { 1700, 0x2e, 0x03 }, { 1750, 0x3e, 0x03 }, > + { 1800, 0x0e, 0x03 }, { 1850, 0x1e, 0x03 }, { 1900, 0x2f, 0x03 }, > + { 1950, 0x3f, 0x03 }, { 2000, 0x0f, 0x03 }, { 2050, 0x40, 0x03 }, > + { 2100, 0x41, 0x03 }, { 2150, 0x42, 0x03 }, { 2200, 0x43, 0x03 }, > + { 2250, 0x44, 0x03 }, { 2300, 0x45, 0x01 }, { 2350, 0x46, 0x01 }, > + { 2400, 0x47, 0x01 }, { 2450, 0x48, 0x01 }, { 2500, 0x49, 0x01 } > +}; > + > +struct dw_dsi_ipk { > + void __iomem *base; > + void __iomem *base_phy; > + struct clk *pllref_clk; > + struct dw_mipi_dsi *dsi; > + u32 lane_min_kbps; > + u32 lane_max_kbps; > + int range; > + int in_div; > + int loop_div; > +}; > + > +#define dw_mipi_dsi_to_dw_dsi_ipk(target) \ > + container_of(target, struct dw_dsi_ipk, dsi) > + > +static void dw_dsi_write(struct dw_dsi_ipk *dsi, u32 reg, u32 val) > +{ > + writel(val, dsi->base + reg); > +} > + > +static u32 dw_dsi_read(struct dw_dsi_ipk *dsi, u32 reg) > +{ > + return readl(dsi->base + reg); > +} > + > +static void dw_phy_write(struct dw_dsi_ipk *dsi, u32 reg, u32 val) > +{ > + writel(val, dsi->base_phy + reg); > +} > + > +static void dw_dsi_phy_write_part(struct dw_dsi_ipk *dsi, u32 reg_address, > + u32 data, u8 shift, u8 width) > +{ > + u32 temp = dw_dsi_read(dsi, reg_address); > + u32 mask = (1 << width) - 1; > + > + temp &= ~(mask << shift); > + temp |= (data & mask) << shift; > + dw_dsi_write(dsi, reg_address, temp); > +} > + > +static void dw_dsi_phy_test_data_in(struct dw_dsi_ipk *dsi, u8 test_data) > +{ > + dw_dsi_phy_write_part(dsi, DW_DPHY_TST_CTRL1, test_data, 0, 8); > +} > + > +static void dw_dsi_phy_test_clock(struct dw_dsi_ipk *dsi, int value) > +{ > + dw_dsi_phy_write_part(dsi, DW_DPHY_TST_CTRL0, value, 1, 1); > +} > + > +static void dw_dsi_phy_test_en(struct dw_dsi_ipk *dsi, u8 on_falling_edge) > +{ > + dw_dsi_phy_write_part(dsi, DW_DPHY_TST_CTRL1, on_falling_edge, 16, 1); > +} > + > +static void dw_dsi_phy_test_clear(struct dw_dsi_ipk *dsi, int value) > +{ > + dw_dsi_phy_write_part(dsi, DW_DPHY_TST_CTRL0, value, 0, 1); > +} > + > +static void dw_dsi_phy_write(struct dw_dsi_ipk *dsi, u16 address, > + u32 value, u8 data_length) > +{ > + u8 data[4]; > + int i; > + > + data[0] = value; > + > + dw_dsi_write(dsi, DW_DPHY_TST_CTRL0, 0); > + dw_dsi_write(dsi, DW_DPHY_TST_CTRL1, 0); > + > + dw_dsi_phy_test_en(dsi, 1); > + dw_dsi_phy_test_clock(dsi, 1); > + dw_dsi_phy_test_data_in(dsi, 0x00); > + dw_dsi_phy_test_clock(dsi, 0); > + dw_dsi_write(dsi, DW_DPHY_TST_CTRL1, 0); > + dw_dsi_write(dsi, DW_DPHY_TST_CTRL1, (u8)(address >> 8)); > + dw_dsi_phy_test_clock(dsi, 1); > + dw_dsi_phy_test_clock(dsi, 0); > + dw_dsi_phy_test_en(dsi, 1); > + dw_dsi_phy_test_clock(dsi, 1); > + dw_dsi_phy_test_data_in(dsi, ((u8)address)); > + dw_dsi_phy_test_clock(dsi, 0); > + dw_dsi_phy_test_en(dsi, 0); > + > + for (i = data_length; i > 0; i--) { > + dw_dsi_phy_test_data_in(dsi, ((u8)data[i - 1])); > + dw_dsi_phy_test_clock(dsi, 1); > + dw_dsi_phy_test_clock(dsi, 0); > + } > +} > + > +static void dw_dsi_phy_delay(struct dw_dsi_ipk *dsi, int value) > +{ > + u32 data = value << 2; > + > + dw_dsi_phy_write(dsi, DW_12BITS_DPHY_RDY_L0, data, 1); > + dw_dsi_phy_write(dsi, DW_12BITS_DPHY_RDY_L1, data, 1); > + dw_dsi_phy_write(dsi, DW_12BITS_DPHY_RDY_L2, data, 1); > + dw_dsi_phy_write(dsi, DW_12BITS_DPHY_RDY_L3, data, 1); > +} > + > +static int dsi_pll_get_clkout_khz(int clkin_khz, int idf, int ndiv, int odf) > +{ > + int divisor = idf * odf; > + > + /* prevent from division by 0 */ > + if (!divisor) > + return 0; > + > + return DIV_ROUND_CLOSEST(clkin_khz * ndiv, divisor); > +} > + > +static int dsi_pll_get_params(struct dw_dsi_ipk *dsi, int in_freq, > + int out_freq, int *idf, int *ndiv, int *odf) > +{ > + int range, tmp_loop_div, tmp_in_freq, delta, step = 0, flag = 0; > + int out_data_rate = out_freq * 2; > + int loop_div = 0; /* M */ > + int out_div; /* VCO */ > + int in_div; /* N */ > + > + /* Find ranges */ > + for (range = 0; ARRAY_SIZE(dw_range_gen3) && > + (out_data_rate / 1000) > dw_range_gen3[range].freq; range++) > + ; > + > + if (range >= ARRAY_SIZE(dw_range_gen3)) > + return -EINVAL; > + > + if ((dw_range_gen3[range].osc_freq_target >> 4) == 3) > + out_div = 8; > + else if ((dw_range_gen3[range].osc_freq_target >> 4) == 2) > + out_div = 4; > + else > + out_div = 2; > + > + if (dw_range_gen3[range].freq > 640) > + out_div = 1; > + > + out_freq = out_freq * out_div; > + > + loop_div = (out_freq * (in_freq / DW_DPHY_DIV_LOWER_LIMIT)) / in_freq; > + > + /* here delta will account for the rounding */ > + delta = (loop_div * in_freq) / (in_freq / DW_DPHY_DIV_LOWER_LIMIT) - > + out_freq; > + > + for (in_div = 1 + in_freq / DW_DPHY_DIV_UPPER_LIMIT; > + (in_freq / in_div >= DW_DPHY_DIV_LOWER_LIMIT) && !flag; in_div++) { > + tmp_loop_div = out_freq * in_div / in_freq; > + tmp_in_freq = in_freq / in_div; > + if (tmp_loop_div % 2) { > + tmp_loop_div += 1; > + if (out_freq == tmp_loop_div * tmp_in_freq) { > + /* Exact values found */ > + flag = 1; > + loop_div = tmp_loop_div; > + delta = tmp_loop_div * tmp_in_freq - out_freq; > + in_div--; > + } else if (tmp_loop_div * tmp_in_freq - out_freq < > + delta) { > + /* Values found with smaller delta */ > + loop_div = tmp_loop_div; > + delta = tmp_loop_div * tmp_in_freq - out_freq; > + step = 0; > + } > + } else if (out_freq == tmp_loop_div * tmp_in_freq) { > + /* Exact values found */ > + flag = 1; > + loop_div = tmp_loop_div; > + delta = out_freq - tmp_loop_div * tmp_in_freq; > + in_div--; > + } else if (out_freq - tmp_loop_div * tmp_in_freq < delta) { > + /* Values found with smaller delta */ > + loop_div = tmp_loop_div; > + delta = out_freq - tmp_loop_div * tmp_in_freq; > + step = 1; > + } > + } > + > + if (!flag) > + in_div = step + loop_div * in_freq / out_freq; > + > + *idf = in_div; > + *ndiv = loop_div; > + *odf = out_div; > + > + dsi->range = range; > + dsi->in_div = in_div; > + dsi->loop_div = loop_div; > + > + return 0; > +} > + > +/* DPHY GEN 3 12 bits */ > +static void dw_phy_init_gen3_128(void *priv_data) > +{ > + struct dw_dsi_ipk *dsi = priv_data; > + int loop_div = dsi->loop_div; > + int in_div = dsi->in_div; > + int range = dsi->range; > + u32 data; > + > + /* hs frequency range [6:0] */ > + data = dw_range_gen3[range].hs_freq_range; > + dw_dsi_phy_write(dsi, 0x02, data, 1); > + > + /* [7:6] reserved | [5] hsfreqrange_ovr_en_rw | > + * [4:1] target_state_rw | [0] force_state_rw > + */ > + dw_dsi_phy_write(dsi, 0x01, 0x20, 1); > + > + /* PLL Lock Configurations */ > + dw_dsi_phy_write(dsi, 0x173, 0x02, 1); > + dw_dsi_phy_write(dsi, 0x174, 0x00, 1); > + dw_dsi_phy_write(dsi, 0x175, 0x60, 1); > + dw_dsi_phy_write(dsi, 0x176, 0x03, 1); > + dw_dsi_phy_write(dsi, 0x166, 0x01, 1); > + > + /* Charge-pump Programmability */ > + /* [7] pll_vco_cntrl_ovr_en | > + * [6:1] pll_vco_cntrl_ovr | [0] pll_m_ovr_en > + */ > + if (dw_range_gen3[range].freq > 640) > + data = 1 | (dw_range_gen3[range].osc_freq_target << 1); > + else > + data = 1 | (1 << 7) | > + (dw_range_gen3[range].osc_freq_target << 1); > + > + dw_dsi_phy_write(dsi, 0x17b, data, 1); > + dw_dsi_phy_write(dsi, 0x15e, 0x10, 1); > + dw_dsi_phy_write(dsi, 0x162, 0x04, 1); > + dw_dsi_phy_write(dsi, 0x16e, 0x0c, 1); > + > + /* Slew-Rate */ > + dw_dsi_phy_write(dsi, 0x26b, 0x04, 1); > + > + /* pll_n_ovr_en_rw | PLL input divider ratio [6:3] | > + * pll_tstplldig_rw > + */ > + data = (1 << 7) | (in_div - 1) << 3; > + dw_dsi_phy_write(dsi, 0x178, data, 1); > + > + /* PLL loop divider ratio [7:0] */ > + data = loop_div - 2; > + dw_dsi_phy_write(dsi, 0x179, data, 1); > + > + /* PLL loop divider ratio [9:8] */ > + data = (loop_div - 2) >> 8; > + dw_dsi_phy_write(dsi, 0x17a, data, 1); > + > + if (dw_range_gen3[range].freq < 450) > + dw_dsi_phy_write(dsi, 0x1ac, 0x1b, 1); > + else > + dw_dsi_phy_write(dsi, 0x1ac, 0x0b, 1); > +} > + > +static int dw_mipi_dsi_phy_init(void *priv_data) > +{ > + struct dw_dsi_ipk *dsi = priv_data; > + int range = dsi->range; > + unsigned int in_freq; > + u32 data; > + > + in_freq = (unsigned int)(clk_get_rate(dsi->pllref_clk) / 1000); > + > + dw_phy_write(dsi, DW_GEN3_IF_TESTER, RESET); > + dw_phy_write(dsi, DW_GEN3_IF_TESTER, GLUE_LOGIC); > + dw_dsi_phy_test_clear(dsi, 1); > + dw_dsi_phy_test_clear(dsi, 0); > + > + dw_dsi_phy_write(dsi, 0x30, 0x0f, 1); > + > + data = ((in_freq / 1000) - 17) * 4; > + dw_dsi_phy_write(dsi, 0x02, data, 1); > + > + dw_dsi_phy_write(dsi, 0x20, 0x3f, 1); > + > + /* RESET RX */ > + dw_phy_write(dsi, DW_GEN3_IF_TESTER, RESET); > + dw_phy_write(dsi, DW_GEN3_IF_TESTER, RX_PHY); > + dw_dsi_phy_test_clear(dsi, 1); > + dw_dsi_phy_test_clear(dsi, 0); > + > + /* RESET TX */ > + dw_phy_write(dsi, DW_GEN3_IF_TESTER, RESET); > + dw_phy_write(dsi, DW_GEN3_IF_TESTER, TX_PHY); > + dw_dsi_phy_test_clear(dsi, 1); > + dw_dsi_phy_test_clear(dsi, 0); > + > + dw_phy_init_gen3_128(priv_data); > + > + if (dw_range_gen3[range].freq > 648) > + dw_dsi_phy_delay(dsi, 5); > + else > + dw_dsi_phy_delay(dsi, 4); > + > + DRM_DEBUG_DRIVER("Phy configured\n"); > + > + return 0; > +} > + > +static int > +dw_mipi_dsi_get_lane_mbps(void *priv_data, const struct drm_display_mode *mode, > + unsigned long mode_flags, u32 lanes, u32 format, > + unsigned int *lane_mbps) > +{ > + int idf = 0, ndiv = 0, odf = 0, pll_in_khz, pll_out_khz, ret, bpp; > + struct dw_dsi_ipk *dsi = priv_data; > + > + DRM_DEBUG_DRIVER("\n"); > + > + dsi->lane_min_kbps = (unsigned int)DW_LANE_MIN_KBPS; > + dsi->lane_max_kbps = (unsigned int)DW_LANE_MAX_KBPS; > + > + pll_in_khz = (unsigned int)(clk_get_rate(dsi->pllref_clk) / 1000); > + > + /* Compute requested pll out */ > + bpp = mipi_dsi_pixel_format_to_bpp((enum mipi_dsi_pixel_format)format); > + pll_out_khz = ((mode->clock * bpp) / lanes) / 2; > + > + if (pll_out_khz > dsi->lane_max_kbps) { > + pll_out_khz = dsi->lane_max_kbps; > + DRM_WARN("Warning max phy mbps is used\n"); > + } > + > + if (pll_out_khz < dsi->lane_min_kbps) { > + pll_out_khz = dsi->lane_min_kbps; > + DRM_WARN("Warning min phy mbps is used\n"); > + } > + > + ret = dsi_pll_get_params(dsi, pll_in_khz, pll_out_khz, > + &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); > + > + *lane_mbps = (pll_out_khz / 1000) * 2; > + > + DRM_DEBUG_DRIVER("pll_in %ukHz pll_out %ukHz lane_mbps %uMHz\n", > + pll_in_khz, pll_out_khz, *lane_mbps); > + > + return ret; > +} > + > +static int > +dw_mipi_dsi_phy_get_timing(void *priv_data, unsigned int lane_mbps, > + struct dw_mipi_dsi_dphy_timing *timing) > +{ > + timing->clk_hs2lp = DW_LPHS_TIM_TRANSIONS; > + timing->clk_lp2hs = DW_LPHS_TIM_TRANSIONS; > + timing->data_hs2lp = DW_LPHS_TIM_TRANSIONS; > + timing->data_lp2hs = DW_LPHS_TIM_TRANSIONS; > + > + return 0; > +} > + > +static const struct dw_mipi_dsi_phy_ops dw_dsi_ipk_phy_ops = { > + .init = dw_mipi_dsi_phy_init, > + .get_lane_mbps = dw_mipi_dsi_get_lane_mbps, > + .get_timing = dw_mipi_dsi_phy_get_timing, > +}; > + > +static struct dw_mipi_dsi_plat_data dw_dsi_ipk_plat_data = { > + .max_data_lanes = 4, > + .phy_ops = &dw_dsi_ipk_phy_ops, > +}; > + > +static const struct of_device_id dw_ipk_dt_ids[] = { > + {.compatible = "snps,dw-ipk-dsi", > + .data = &dw_dsi_ipk_plat_data,}, > + { }, > +}; > + > +MODULE_DEVICE_TABLE(of, dw_ipk_dt_ids); > + > +static int dw_dsi_ipk_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct dw_dsi_ipk *dsi; > + struct resource *res; > + struct clk *pclk; > + int ret; > + > + DRM_DEBUG_DRIVER("\n"); > + > + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); > + if (!dsi) > + return -ENOMEM; > + > + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dsi"); > + dsi->base = devm_ioremap_resource(dev, res); > + if (IS_ERR(dsi->base)) { > + ret = PTR_ERR(dsi->base); > + DRM_ERROR("Unable to get dsi registers %d\n", ret); > + return ret; > + } > + > + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); > + dsi->base_phy = devm_ioremap_resource(dev, res); > + if (IS_ERR(dsi->base_phy)) { > + ret = PTR_ERR(dsi->base_phy); > + DRM_ERROR("Unable to get PHY registers %d\n", ret); > + return ret; > + } > + > + pclk = devm_clk_get(dev, "pclk"); > + if (IS_ERR(pclk)) { > + ret = PTR_ERR(pclk); > + DRM_ERROR("Unable to get peripheral clock: %d\n", ret); > + goto err_dsi_probe; > + } > + > + ret = clk_prepare_enable(pclk); > + if (ret) > + goto err_dsi_probe; > + > + dsi->pllref_clk = devm_clk_get(dev, "ref"); > + if (IS_ERR(dsi->pllref_clk)) { > + ret = PTR_ERR(dsi->pllref_clk); > + DRM_ERROR("Unable to get pll reference clock: %d\n", ret); > + return ret; > + } > + > + ret = clk_prepare_enable(dsi->pllref_clk); > + if (ret) > + return ret; > + > + dw_dsi_ipk_plat_data.base = dsi->base; > + dw_dsi_ipk_plat_data.priv_data = dsi; > + > + platform_set_drvdata(pdev, dsi); > + > + dsi->dsi = dw_mipi_dsi_probe(pdev, &dw_dsi_ipk_plat_data); > + if (IS_ERR(dsi->dsi)) { > + ret = PTR_ERR(dsi->dsi); > + DRM_ERROR("Failed to initialize mipi dsi host: %d\n", ret); > + goto err_dsi_probe; > + } > + > + return ret; > + > +err_dsi_probe: > + clk_disable_unprepare(dsi->pllref_clk); > + return ret; > +} > + > +static int dw_dsi_ipk_remove(struct platform_device *pdev) > +{ > + struct dw_dsi_ipk *dsi = platform_get_drvdata(pdev); > + > + dw_mipi_dsi_remove(dsi->dsi); > + > + return 0; > +} > + > +struct platform_driver dw_mipi_dsi_ipk_driver = { > + .probe = dw_dsi_ipk_probe, > + .remove = dw_dsi_ipk_remove, > + .driver = { > + .name = "ipk-dw-mipi-dsi", > + .of_match_table = dw_ipk_dt_ids, > + }, > +}; > + > +module_platform_driver(dw_mipi_dsi_ipk_driver); > + > +MODULE_AUTHOR("Angelo Ribeiro <angelo.ribeiro@xxxxxxxxxxxx>"); > +MODULE_AUTHOR("Luis Oliveira <luis.oliveira@xxxxxxxxxxxx>"); > +MODULE_DESCRIPTION("Synopsys IPK DW MIPI DSI host controller driver"); > +MODULE_LICENSE("GPL v2"); > -- > 2.7.4 > -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch