diff options
author | Rob Clark <robdclark@gmail.com> | 2014-08-01 13:08:11 -0400 |
---|---|---|
committer | Rob Clark <robdclark@gmail.com> | 2014-09-10 11:19:07 -0400 |
commit | 3e87599b68e7929a84a32ab65ad17b79a3f271f6 (patch) | |
tree | 073b98e5d2e097be7016f467be3eaaf3c08f2a18 /drivers | |
parent | d65bd0e431156f156f43946b6efb524694afb685 (diff) | |
download | linux-stable-3e87599b68e7929a84a32ab65ad17b79a3f271f6.tar.gz linux-stable-3e87599b68e7929a84a32ab65ad17b79a3f271f6.tar.bz2 linux-stable-3e87599b68e7929a84a32ab65ad17b79a3f271f6.zip |
drm/msm/mdp4: add LVDS panel support
LVDS panel support uses the LCDC (parallel) encoder. Unlike with HDMI,
there is not a separate LVDS block, so no need to split things into a
bridge+connector. Nor is there is anything re-used with mdp5.
Note that there can be some regulators shared between HDMI and LVDS (in
particular, on apq8064, ext_3v3p), so we should not use the _exclusive()
variants of devm_regulator_get().
The drm_panel framework is used for panel-specific driver.
Signed-off-by: Rob Clark <robdclark@gmail.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/gpu/drm/msm/Kconfig | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/msm/Makefile | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/msm/hdmi/hdmi.c | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c | 88 | ||||
-rw-r--r-- | drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h | 18 | ||||
-rw-r--r-- | drivers/gpu/drm/msm/mdp/mdp4/mdp4_lcdc_encoder.c | 506 | ||||
-rw-r--r-- | drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_connector.c | 151 | ||||
-rw-r--r-- | drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_pll.c | 172 |
8 files changed, 933 insertions, 10 deletions
diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig index c99c50de3226..9d907c526c94 100644 --- a/drivers/gpu/drm/msm/Kconfig +++ b/drivers/gpu/drm/msm/Kconfig @@ -4,6 +4,7 @@ config DRM_MSM depends on DRM depends on ARCH_QCOM || (ARM && COMPILE_TEST) select DRM_KMS_HELPER + select DRM_PANEL select SHMEM select TMPFS default y diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index 93ca49c8df44..48f796056887 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -18,6 +18,8 @@ msm-y := \ mdp/mdp_kms.o \ mdp/mdp4/mdp4_crtc.o \ mdp/mdp4/mdp4_dtv_encoder.o \ + mdp/mdp4/mdp4_lcdc_encoder.o \ + mdp/mdp4/mdp4_lvds_connector.o \ mdp/mdp4/mdp4_irq.o \ mdp/mdp4/mdp4_kms.o \ mdp/mdp4/mdp4_plane.o \ @@ -39,5 +41,6 @@ msm-y := \ msm_ringbuffer.o msm-$(CONFIG_DRM_MSM_FBDEV) += msm_fbdev.o +msm-$(CONFIG_COMMON_CLK) += mdp/mdp4/mdp4_lvds_pll.o obj-$(CONFIG_DRM_MSM) += msm.o diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c b/drivers/gpu/drm/msm/hdmi/hdmi.c index a125a7e32742..2e0eac7cdf89 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi.c @@ -123,7 +123,7 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder) for (i = 0; i < config->hpd_reg_cnt; i++) { struct regulator *reg; - reg = devm_regulator_get_exclusive(&pdev->dev, + reg = devm_regulator_get(&pdev->dev, config->hpd_reg_names[i]); if (IS_ERR(reg)) { ret = PTR_ERR(reg); @@ -139,7 +139,7 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder) for (i = 0; i < config->pwr_reg_cnt; i++) { struct regulator *reg; - reg = devm_regulator_get_exclusive(&pdev->dev, + reg = devm_regulator_get(&pdev->dev, config->pwr_reg_names[i]); if (IS_ERR(reg)) { ret = PTR_ERR(reg); diff --git a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c index af69079082f7..79d804e61cc4 100644 --- a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c +++ b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c @@ -197,6 +197,28 @@ int mdp4_enable(struct mdp4_kms *mdp4_kms) return 0; } +#ifdef CONFIG_OF +static struct drm_panel *detect_panel(struct drm_device *dev, const char *name) +{ + struct device_node *n; + struct drm_panel *panel = NULL; + + n = of_parse_phandle(dev->dev->of_node, name, 0); + if (n) { + panel = of_drm_find_panel(n); + if (!panel) + panel = ERR_PTR(-EPROBE_DEFER); + } + + return panel; +} +#else +static struct drm_panel *detect_panel(struct drm_device *dev, const char *name) +{ + // ??? maybe use a module param to specify which panel is attached? +} +#endif + static int modeset_init(struct mdp4_kms *mdp4_kms) { struct drm_device *dev = mdp4_kms->dev; @@ -204,14 +226,11 @@ static int modeset_init(struct mdp4_kms *mdp4_kms) struct drm_plane *plane; struct drm_crtc *crtc; struct drm_encoder *encoder; + struct drm_connector *connector; + struct drm_panel *panel; struct hdmi *hdmi; int ret; - /* - * NOTE: this is a bit simplistic until we add support - * for more than just RGB1->DMA_E->DTV->HDMI - */ - /* construct non-private planes: */ plane = mdp4_plane_init(dev, VG1, false); if (IS_ERR(plane)) { @@ -229,7 +248,57 @@ static int modeset_init(struct mdp4_kms *mdp4_kms) } priv->planes[priv->num_planes++] = plane; - /* the CRTCs get constructed with a private plane: */ + /* + * Setup the LCDC/LVDS path: RGB2 -> DMA_P -> LCDC -> LVDS: + */ + + panel = detect_panel(dev, "qcom,lvds-panel"); + if (IS_ERR(panel)) { + ret = PTR_ERR(panel); + dev_err(dev->dev, "failed to detect LVDS panel: %d\n", ret); + goto fail; + } + + plane = mdp4_plane_init(dev, RGB2, true); + if (IS_ERR(plane)) { + dev_err(dev->dev, "failed to construct plane for RGB2\n"); + ret = PTR_ERR(plane); + goto fail; + } + + crtc = mdp4_crtc_init(dev, plane, priv->num_crtcs, 0, DMA_P); + if (IS_ERR(crtc)) { + dev_err(dev->dev, "failed to construct crtc for DMA_P\n"); + ret = PTR_ERR(crtc); + goto fail; + } + + encoder = mdp4_lcdc_encoder_init(dev, panel); + if (IS_ERR(encoder)) { + dev_err(dev->dev, "failed to construct LCDC encoder\n"); + ret = PTR_ERR(encoder); + goto fail; + } + + /* LCDC can be hooked to DMA_P: */ + encoder->possible_crtcs = 1 << priv->num_crtcs; + + priv->crtcs[priv->num_crtcs++] = crtc; + priv->encoders[priv->num_encoders++] = encoder; + + connector = mdp4_lvds_connector_init(dev, panel, encoder); + if (IS_ERR(connector)) { + ret = PTR_ERR(connector); + dev_err(dev->dev, "failed to initialize LVDS connector: %d\n", ret); + goto fail; + } + + priv->connectors[priv->num_connectors++] = connector; + + /* + * Setup DTV/HDMI path: RGB1 -> DMA_E -> DTV -> HDMI: + */ + plane = mdp4_plane_init(dev, RGB1, true); if (IS_ERR(plane)) { dev_err(dev->dev, "failed to construct plane for RGB1\n"); @@ -243,7 +312,6 @@ static int modeset_init(struct mdp4_kms *mdp4_kms) ret = PTR_ERR(crtc); goto fail; } - priv->crtcs[priv->num_crtcs++] = crtc; encoder = mdp4_dtv_encoder_init(dev); if (IS_ERR(encoder)) { @@ -251,7 +319,11 @@ static int modeset_init(struct mdp4_kms *mdp4_kms) ret = PTR_ERR(encoder); goto fail; } - encoder->possible_crtcs = 0x1; /* DTV can be hooked to DMA_E */ + + /* DTV can be hooked to DMA_E: */ + encoder->possible_crtcs = 1 << priv->num_crtcs; + + priv->crtcs[priv->num_crtcs++] = crtc; priv->encoders[priv->num_encoders++] = encoder; hdmi = hdmi_init(dev, encoder); diff --git a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h index e74146fe2ae6..9ff6e7ccfe90 100644 --- a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h +++ b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h @@ -23,6 +23,8 @@ #include "mdp/mdp_kms.h" #include "mdp4.xml.h" +#include "drm_panel.h" + struct mdp4_kms { struct mdp_kms base; @@ -217,6 +219,22 @@ struct drm_crtc *mdp4_crtc_init(struct drm_device *dev, long mdp4_dtv_round_pixclk(struct drm_encoder *encoder, unsigned long rate); struct drm_encoder *mdp4_dtv_encoder_init(struct drm_device *dev); +long mdp4_lcdc_round_pixclk(struct drm_encoder *encoder, unsigned long rate); +struct drm_encoder *mdp4_lcdc_encoder_init(struct drm_device *dev, + struct drm_panel *panel); + +struct drm_connector *mdp4_lvds_connector_init(struct drm_device *dev, + struct drm_panel *panel, struct drm_encoder *encoder); + +#ifdef CONFIG_COMMON_CLK +struct clk *mpd4_lvds_pll_init(struct drm_device *dev); +#else +static inline struct clk *mpd4_lvds_pll_init(struct drm_device *dev) +{ + return ERR_PTR(-ENODEV); +} +#endif + #ifdef CONFIG_MSM_BUS_SCALING static inline int match_dev_name(struct device *dev, void *data) { diff --git a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lcdc_encoder.c b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lcdc_encoder.c new file mode 100644 index 000000000000..41f6436754fc --- /dev/null +++ b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lcdc_encoder.c @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2014 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + * Author: Vinay Simha <vinaysimha@inforcecomputing.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "mdp4_kms.h" + +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +struct mdp4_lcdc_encoder { + struct drm_encoder base; + struct drm_panel *panel; + struct clk *lcdc_clk; + unsigned long int pixclock; + struct regulator *regs[3]; + bool enabled; + uint32_t bsc; +}; +#define to_mdp4_lcdc_encoder(x) container_of(x, struct mdp4_lcdc_encoder, base) + +static struct mdp4_kms *get_kms(struct drm_encoder *encoder) +{ + struct msm_drm_private *priv = encoder->dev->dev_private; + return to_mdp4_kms(to_mdp_kms(priv->kms)); +} + +#ifdef CONFIG_MSM_BUS_SCALING +#include <mach/board.h> +static void bs_init(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder) +{ + struct drm_device *dev = mdp4_lcdc_encoder->base.dev; + struct lcdc_platform_data *lcdc_pdata = mdp4_find_pdata("lvds.0"); + + if (!lcdc_pdata) { + dev_err(dev->dev, "could not find lvds pdata\n"); + return; + } + + if (lcdc_pdata->bus_scale_table) { + mdp4_lcdc_encoder->bsc = msm_bus_scale_register_client( + lcdc_pdata->bus_scale_table); + DBG("lvds : bus scale client: %08x", mdp4_lcdc_encoder->bsc); + } +} + +static void bs_fini(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder) +{ + if (mdp4_lcdc_encoder->bsc) { + msm_bus_scale_unregister_client(mdp4_lcdc_encoder->bsc); + mdp4_lcdc_encoder->bsc = 0; + } +} + +static void bs_set(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder, int idx) +{ + if (mdp4_lcdc_encoder->bsc) { + DBG("set bus scaling: %d", idx); + msm_bus_scale_client_update_request(mdp4_lcdc_encoder->bsc, idx); + } +} +#else +static void bs_init(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder) {} +static void bs_fini(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder) {} +static void bs_set(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder, int idx) {} +#endif + +static void mdp4_lcdc_encoder_destroy(struct drm_encoder *encoder) +{ + struct mdp4_lcdc_encoder *mdp4_lcdc_encoder = + to_mdp4_lcdc_encoder(encoder); + bs_fini(mdp4_lcdc_encoder); + drm_encoder_cleanup(encoder); + kfree(mdp4_lcdc_encoder); +} + +static const struct drm_encoder_funcs mdp4_lcdc_encoder_funcs = { + .destroy = mdp4_lcdc_encoder_destroy, +}; + +/* this should probably be a helper: */ +struct drm_connector *get_connector(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct drm_connector *connector; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) + if (connector->encoder == encoder) + return connector; + + return NULL; +} + +static void setup_phy(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct drm_connector *connector = get_connector(encoder); + struct mdp4_kms *mdp4_kms = get_kms(encoder); + uint32_t lvds_intf = 0, lvds_phy_cfg0 = 0; + int bpp, nchan, swap; + + if (!connector) + return; + + bpp = 3 * connector->display_info.bpc; + + if (!bpp) + bpp = 18; + + /* TODO, these should come from panel somehow: */ + nchan = 1; + swap = 0; + + switch (bpp) { + case 24: + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(0), + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x08) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x05) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x04) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x03)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(0), + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x02) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x01) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x00)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(1), + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x11) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x10) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x0d) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0c)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(1), + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0b) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x0a) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x09)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(2), + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1a) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x19) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x18) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x15)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(2), + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x14) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x13) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x12)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(3), + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1b) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x17) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x16) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0f)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(3), + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0e) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x07) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x06)); + if (nchan == 2) { + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE3_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE2_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE1_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE0_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE3_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN; + } else { + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE3_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN; + } + break; + + case 18: + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(0), + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x0a) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x07) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x06) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x05)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(0), + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x04) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x03) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x02)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(1), + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x13) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x12) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x0f) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0e)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(1), + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0d) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x0c) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x0b)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(2), + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1a) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x19) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x18) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x17)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(2), + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x16) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x15) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x14)); + if (nchan == 2) { + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE2_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE1_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE0_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN; + } else { + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN; + } + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_RGB_OUT; + break; + + default: + dev_err(dev->dev, "unknown bpp: %d\n", bpp); + return; + } + + switch (nchan) { + case 1: + lvds_phy_cfg0 = MDP4_LVDS_PHY_CFG0_CHANNEL0; + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_CLK_LANE_EN | + MDP4_LCDC_LVDS_INTF_CTL_MODE_SEL; + break; + case 2: + lvds_phy_cfg0 = MDP4_LVDS_PHY_CFG0_CHANNEL0 | + MDP4_LVDS_PHY_CFG0_CHANNEL1; + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_CLK_LANE_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_CLK_LANE_EN; + break; + default: + dev_err(dev->dev, "unknown # of channels: %d\n", nchan); + return; + } + + if (swap) + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH_SWAP; + + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_ENABLE; + + mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, lvds_phy_cfg0); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_INTF_CTL, lvds_intf); + mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG2, 0x30); + + mb(); + udelay(1); + lvds_phy_cfg0 |= MDP4_LVDS_PHY_CFG0_SERIALIZATION_ENBLE; + mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, lvds_phy_cfg0); +} + +static void mdp4_lcdc_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + struct drm_device *dev = encoder->dev; + struct mdp4_lcdc_encoder *mdp4_lcdc_encoder = + to_mdp4_lcdc_encoder(encoder); + struct mdp4_kms *mdp4_kms = get_kms(encoder); + struct drm_panel *panel = mdp4_lcdc_encoder->panel; + bool enabled = (mode == DRM_MODE_DPMS_ON); + int i, ret; + + DBG("mode=%d", mode); + + if (enabled == mdp4_lcdc_encoder->enabled) + return; + + if (enabled) { + unsigned long pc = mdp4_lcdc_encoder->pixclock; + int ret; + + bs_set(mdp4_lcdc_encoder, 1); + + for (i = 0; i < ARRAY_SIZE(mdp4_lcdc_encoder->regs); i++) { + ret = regulator_enable(mdp4_lcdc_encoder->regs[i]); + if (ret) + dev_err(dev->dev, "failed to enable regulator: %d\n", ret); + } + + DBG("setting lcdc_clk=%lu", pc); + ret = clk_set_rate(mdp4_lcdc_encoder->lcdc_clk, pc); + if (ret) + dev_err(dev->dev, "failed to configure lcdc_clk: %d\n", ret); + ret = clk_prepare_enable(mdp4_lcdc_encoder->lcdc_clk); + if (ret) + dev_err(dev->dev, "failed to enable lcdc_clk: %d\n", ret); + + if (panel) + drm_panel_enable(panel); + + setup_phy(encoder); + + mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 1); + } else { + mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 0); + + if (panel) + drm_panel_disable(panel); + + /* + * Wait for a vsync so we know the ENABLE=0 latched before + * the (connector) source of the vsync's gets disabled, + * otherwise we end up in a funny state if we re-enable + * before the disable latches, which results that some of + * the settings changes for the new modeset (like new + * scanout buffer) don't latch properly.. + */ + mdp_irq_wait(&mdp4_kms->base, MDP4_IRQ_PRIMARY_VSYNC); + + clk_disable_unprepare(mdp4_lcdc_encoder->lcdc_clk); + + for (i = 0; i < ARRAY_SIZE(mdp4_lcdc_encoder->regs); i++) { + ret = regulator_disable(mdp4_lcdc_encoder->regs[i]); + if (ret) + dev_err(dev->dev, "failed to disable regulator: %d\n", ret); + } + + bs_set(mdp4_lcdc_encoder, 0); + } + + mdp4_lcdc_encoder->enabled = enabled; +} + +static bool mdp4_lcdc_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void mdp4_lcdc_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct mdp4_lcdc_encoder *mdp4_lcdc_encoder = + to_mdp4_lcdc_encoder(encoder); + struct mdp4_kms *mdp4_kms = get_kms(encoder); + uint32_t lcdc_hsync_skew, vsync_period, vsync_len, ctrl_pol; + uint32_t display_v_start, display_v_end; + uint32_t hsync_start_x, hsync_end_x; + + mode = adjusted_mode; + + DBG("set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", + mode->base.id, mode->name, + mode->vrefresh, mode->clock, + mode->hdisplay, mode->hsync_start, + mode->hsync_end, mode->htotal, + mode->vdisplay, mode->vsync_start, + mode->vsync_end, mode->vtotal, + mode->type, mode->flags); + + mdp4_lcdc_encoder->pixclock = mode->clock * 1000; + + DBG("pixclock=%lu", mdp4_lcdc_encoder->pixclock); + + ctrl_pol = 0; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + ctrl_pol |= MDP4_LCDC_CTRL_POLARITY_HSYNC_LOW; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + ctrl_pol |= MDP4_LCDC_CTRL_POLARITY_VSYNC_LOW; + /* probably need to get DATA_EN polarity from panel.. */ + + lcdc_hsync_skew = 0; /* get this from panel? */ + + hsync_start_x = (mode->htotal - mode->hsync_start); + hsync_end_x = mode->htotal - (mode->hsync_start - mode->hdisplay) - 1; + + vsync_period = mode->vtotal * mode->htotal; + vsync_len = (mode->vsync_end - mode->vsync_start) * mode->htotal; + display_v_start = (mode->vtotal - mode->vsync_start) * mode->htotal + lcdc_hsync_skew; + display_v_end = vsync_period - ((mode->vsync_start - mode->vdisplay) * mode->htotal) + lcdc_hsync_skew - 1; + + mdp4_write(mdp4_kms, REG_MDP4_LCDC_HSYNC_CTRL, + MDP4_LCDC_HSYNC_CTRL_PULSEW(mode->hsync_end - mode->hsync_start) | + MDP4_LCDC_HSYNC_CTRL_PERIOD(mode->htotal)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_VSYNC_PERIOD, vsync_period); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_VSYNC_LEN, vsync_len); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_HCTRL, + MDP4_LCDC_DISPLAY_HCTRL_START(hsync_start_x) | + MDP4_LCDC_DISPLAY_HCTRL_END(hsync_end_x)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_VSTART, display_v_start); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_VEND, display_v_end); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_BORDER_CLR, 0); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_UNDERFLOW_CLR, + MDP4_LCDC_UNDERFLOW_CLR_ENABLE_RECOVERY | + MDP4_LCDC_UNDERFLOW_CLR_COLOR(0xff)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_HSYNC_SKEW, lcdc_hsync_skew); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_CTRL_POLARITY, ctrl_pol); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_HCTL, + MDP4_LCDC_ACTIVE_HCTL_START(0) | + MDP4_LCDC_ACTIVE_HCTL_END(0)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_VSTART, 0); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_VEND, 0); +} + +static void mdp4_lcdc_encoder_prepare(struct drm_encoder *encoder) +{ + mdp4_lcdc_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void mdp4_lcdc_encoder_commit(struct drm_encoder *encoder) +{ + /* TODO: hard-coded for 18bpp: */ + mdp4_crtc_set_config(encoder->crtc, + MDP4_DMA_CONFIG_R_BPC(BPC6) | + MDP4_DMA_CONFIG_G_BPC(BPC6) | + MDP4_DMA_CONFIG_B_BPC(BPC6) | + MDP4_DMA_CONFIG_PACK_ALIGN_MSB | + MDP4_DMA_CONFIG_PACK(0x21) | + MDP4_DMA_CONFIG_DEFLKR_EN | + MDP4_DMA_CONFIG_DITHER_EN); + mdp4_crtc_set_intf(encoder->crtc, INTF_LCDC_DTV, 0); + mdp4_lcdc_encoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static const struct drm_encoder_helper_funcs mdp4_lcdc_encoder_helper_funcs = { + .dpms = mdp4_lcdc_encoder_dpms, + .mode_fixup = mdp4_lcdc_encoder_mode_fixup, + .mode_set = mdp4_lcdc_encoder_mode_set, + .prepare = mdp4_lcdc_encoder_prepare, + .commit = mdp4_lcdc_encoder_commit, +}; + +long mdp4_lcdc_round_pixclk(struct drm_encoder *encoder, unsigned long rate) +{ + struct mdp4_lcdc_encoder *mdp4_lcdc_encoder = + to_mdp4_lcdc_encoder(encoder); + return clk_round_rate(mdp4_lcdc_encoder->lcdc_clk, rate); +} + +/* initialize encoder */ +struct drm_encoder *mdp4_lcdc_encoder_init(struct drm_device *dev, + struct drm_panel *panel) +{ + struct drm_encoder *encoder = NULL; + struct mdp4_lcdc_encoder *mdp4_lcdc_encoder; + struct regulator *reg; + int ret; + + mdp4_lcdc_encoder = kzalloc(sizeof(*mdp4_lcdc_encoder), GFP_KERNEL); + if (!mdp4_lcdc_encoder) { + ret = -ENOMEM; + goto fail; + } + + mdp4_lcdc_encoder->panel = panel; + + encoder = &mdp4_lcdc_encoder->base; + + drm_encoder_init(dev, encoder, &mdp4_lcdc_encoder_funcs, + DRM_MODE_ENCODER_LVDS); + drm_encoder_helper_add(encoder, &mdp4_lcdc_encoder_helper_funcs); + + /* TODO: do we need different pll in other cases? */ + mdp4_lcdc_encoder->lcdc_clk = mpd4_lvds_pll_init(dev); + if (IS_ERR(mdp4_lcdc_encoder->lcdc_clk)) { + dev_err(dev->dev, "failed to get lvds_clk\n"); + ret = PTR_ERR(mdp4_lcdc_encoder->lcdc_clk); + goto fail; + } + + /* TODO: different regulators in other cases? */ + reg = devm_regulator_get(dev->dev, "lvds-vccs-3p3v"); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + dev_err(dev->dev, "failed to get lvds-vccs-3p3v: %d\n", ret); + goto fail; + } + mdp4_lcdc_encoder->regs[0] = reg; + + reg = devm_regulator_get(dev->dev, "lvds-pll-vdda"); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + dev_err(dev->dev, "failed to get lvds-pll-vdda: %d\n", ret); + goto fail; + } + mdp4_lcdc_encoder->regs[1] = reg; + + reg = devm_regulator_get(dev->dev, "lvds-vdda"); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + dev_err(dev->dev, "failed to get lvds-vdda: %d\n", ret); + goto fail; + } + mdp4_lcdc_encoder->regs[2] = reg; + + bs_init(mdp4_lcdc_encoder); + + return encoder; + +fail: + if (encoder) + mdp4_lcdc_encoder_destroy(encoder); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_connector.c b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_connector.c new file mode 100644 index 000000000000..310034688c15 --- /dev/null +++ b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_connector.c @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2014 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + * Author: Vinay Simha <vinaysimha@inforcecomputing.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/gpio.h> + +#include "mdp4_kms.h" + +struct mdp4_lvds_connector { + struct drm_connector base; + struct drm_encoder *encoder; + struct drm_panel *panel; +}; +#define to_mdp4_lvds_connector(x) container_of(x, struct mdp4_lvds_connector, base) + +static enum drm_connector_status mdp4_lvds_connector_detect( + struct drm_connector *connector, bool force) +{ + struct mdp4_lvds_connector *mdp4_lvds_connector = + to_mdp4_lvds_connector(connector); + + return mdp4_lvds_connector->panel ? + connector_status_connected : + connector_status_disconnected; +} + +static void mdp4_lvds_connector_destroy(struct drm_connector *connector) +{ + struct mdp4_lvds_connector *mdp4_lvds_connector = + to_mdp4_lvds_connector(connector); + struct drm_panel *panel = mdp4_lvds_connector->panel; + + if (panel) + drm_panel_detach(panel); + + drm_connector_unregister(connector); + drm_connector_cleanup(connector); + + kfree(mdp4_lvds_connector); +} + +static int mdp4_lvds_connector_get_modes(struct drm_connector *connector) +{ + struct mdp4_lvds_connector *mdp4_lvds_connector = + to_mdp4_lvds_connector(connector); + struct drm_panel *panel = mdp4_lvds_connector->panel; + int ret = 0; + + if (panel) + ret = panel->funcs->get_modes(panel); + + return ret; +} + +static int mdp4_lvds_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct mdp4_lvds_connector *mdp4_lvds_connector = + to_mdp4_lvds_connector(connector); + struct drm_encoder *encoder = mdp4_lvds_connector->encoder; + long actual, requested; + + requested = 1000 * mode->clock; + actual = mdp4_lcdc_round_pixclk(encoder, requested); + + DBG("requested=%ld, actual=%ld", requested, actual); + + if (actual != requested) + return MODE_CLOCK_RANGE; + + return MODE_OK; +} + +static struct drm_encoder * +mdp4_lvds_connector_best_encoder(struct drm_connector *connector) +{ + struct mdp4_lvds_connector *mdp4_lvds_connector = + to_mdp4_lvds_connector(connector); + return mdp4_lvds_connector->encoder; +} + +static const struct drm_connector_funcs mdp4_lvds_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = mdp4_lvds_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = mdp4_lvds_connector_destroy, +}; + +static const struct drm_connector_helper_funcs mdp4_lvds_connector_helper_funcs = { + .get_modes = mdp4_lvds_connector_get_modes, + .mode_valid = mdp4_lvds_connector_mode_valid, + .best_encoder = mdp4_lvds_connector_best_encoder, +}; + +/* initialize connector */ +struct drm_connector *mdp4_lvds_connector_init(struct drm_device *dev, + struct drm_panel *panel, struct drm_encoder *encoder) +{ + struct drm_connector *connector = NULL; + struct mdp4_lvds_connector *mdp4_lvds_connector; + int ret; + + mdp4_lvds_connector = kzalloc(sizeof(*mdp4_lvds_connector), GFP_KERNEL); + if (!mdp4_lvds_connector) { + ret = -ENOMEM; + goto fail; + } + + mdp4_lvds_connector->encoder = encoder; + mdp4_lvds_connector->panel = panel; + + connector = &mdp4_lvds_connector->base; + + drm_connector_init(dev, connector, &mdp4_lvds_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + drm_connector_helper_add(connector, &mdp4_lvds_connector_helper_funcs); + + connector->polled = 0; + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + drm_connector_register(connector); + + drm_mode_connector_attach_encoder(connector, encoder); + + if (panel) + drm_panel_attach(panel, connector); + + return connector; + +fail: + if (connector) + mdp4_lvds_connector_destroy(connector); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_pll.c b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_pll.c new file mode 100644 index 000000000000..ce4245971673 --- /dev/null +++ b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_pll.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2014 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> + +#include "mdp4_kms.h" + +struct mdp4_lvds_pll { + struct clk_hw pll_hw; + struct drm_device *dev; + unsigned long pixclk; +}; +#define to_mdp4_lvds_pll(x) container_of(x, struct mdp4_lvds_pll, pll_hw) + +static struct mdp4_kms *get_kms(struct mdp4_lvds_pll *lvds_pll) +{ + struct msm_drm_private *priv = lvds_pll->dev->dev_private; + return to_mdp4_kms(to_mdp_kms(priv->kms)); +} + +struct pll_rate { + unsigned long rate; + struct { + uint32_t val; + uint32_t reg; + } conf[32]; +}; + +/* NOTE: keep sorted highest freq to lowest: */ +static const struct pll_rate freqtbl[] = { + { 72000000, { + { 0x8f, REG_MDP4_LVDS_PHY_PLL_CTRL_1 }, + { 0x30, REG_MDP4_LVDS_PHY_PLL_CTRL_2 }, + { 0xc6, REG_MDP4_LVDS_PHY_PLL_CTRL_3 }, + { 0x10, REG_MDP4_LVDS_PHY_PLL_CTRL_5 }, + { 0x07, REG_MDP4_LVDS_PHY_PLL_CTRL_6 }, + { 0x62, REG_MDP4_LVDS_PHY_PLL_CTRL_7 }, + { 0x41, REG_MDP4_LVDS_PHY_PLL_CTRL_8 }, + { 0x0d, REG_MDP4_LVDS_PHY_PLL_CTRL_9 }, + { 0, 0 } } + }, +}; + +static const struct pll_rate *find_rate(unsigned long rate) +{ + int i; + for (i = 1; i < ARRAY_SIZE(freqtbl); i++) + if (rate > freqtbl[i].rate) + return &freqtbl[i-1]; + return &freqtbl[i-1]; +} + +static int mpd4_lvds_pll_enable(struct clk_hw *hw) +{ + struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); + struct mdp4_kms *mdp4_kms = get_kms(lvds_pll); + const struct pll_rate *pll_rate = find_rate(lvds_pll->pixclk); + int i; + + DBG("pixclk=%lu (%lu)", lvds_pll->pixclk, pll_rate->rate); + + if (WARN_ON(!pll_rate)) + return -EINVAL; + + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_PHY_RESET, 0x33); + + for (i = 0; pll_rate->conf[i].reg; i++) + mdp4_write(mdp4_kms, pll_rate->conf[i].reg, pll_rate->conf[i].val); + + mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_CTRL_0, 0x01); + + /* Wait until LVDS PLL is locked and ready */ + while (!mdp4_read(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_LOCKED)) + cpu_relax(); + + return 0; +} + +static void mpd4_lvds_pll_disable(struct clk_hw *hw) +{ + struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); + struct mdp4_kms *mdp4_kms = get_kms(lvds_pll); + + DBG(""); + + mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, 0x0); + mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_CTRL_0, 0x0); +} + +static unsigned long mpd4_lvds_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); + return lvds_pll->pixclk; +} + +static long mpd4_lvds_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + const struct pll_rate *pll_rate = find_rate(rate); + return pll_rate->rate; +} + +static int mpd4_lvds_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); + lvds_pll->pixclk = rate; + return 0; +} + + +static const struct clk_ops mpd4_lvds_pll_ops = { + .enable = mpd4_lvds_pll_enable, + .disable = mpd4_lvds_pll_disable, + .recalc_rate = mpd4_lvds_pll_recalc_rate, + .round_rate = mpd4_lvds_pll_round_rate, + .set_rate = mpd4_lvds_pll_set_rate, +}; + +static const char *mpd4_lvds_pll_parents[] = { + "pxo", +}; + +static struct clk_init_data pll_init = { + .name = "mpd4_lvds_pll", + .ops = &mpd4_lvds_pll_ops, + .parent_names = mpd4_lvds_pll_parents, + .num_parents = ARRAY_SIZE(mpd4_lvds_pll_parents), +}; + +struct clk *mpd4_lvds_pll_init(struct drm_device *dev) +{ + struct mdp4_lvds_pll *lvds_pll; + struct clk *clk; + int ret; + + lvds_pll = devm_kzalloc(dev->dev, sizeof(*lvds_pll), GFP_KERNEL); + if (!lvds_pll) { + ret = -ENOMEM; + goto fail; + } + + lvds_pll->dev = dev; + + lvds_pll->pll_hw.init = &pll_init; + clk = devm_clk_register(dev->dev, &lvds_pll->pll_hw); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + goto fail; + } + + return clk; + +fail: + return ERR_PTR(ret); +} |