Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
LUU HOAI | 4006 | 94.33% | 1 | 14.29% |
Tomi Valkeinen | 237 | 5.58% | 4 | 57.14% |
Marek Vašut | 3 | 0.07% | 1 | 14.29% |
Laurent Pinchart | 1 | 0.02% | 1 | 14.29% |
Total | 4247 | 7 |
// SPDX-License-Identifier: GPL-2.0 /* * R-Car MIPI DSI Encoder * * Copyright (C) 2020 Renesas Electronics Corporation */ #include <linux/clk.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/iopoll.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_graph.h> #include <linux/platform_device.h> #include <linux/reset.h> #include <linux/slab.h> #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_bridge.h> #include <drm/drm_mipi_dsi.h> #include <drm/drm_of.h> #include <drm/drm_panel.h> #include <drm/drm_probe_helper.h> #include "rcar_mipi_dsi.h" #include "rcar_mipi_dsi_regs.h" struct rcar_mipi_dsi { struct device *dev; const struct rcar_mipi_dsi_device_info *info; struct reset_control *rstc; struct mipi_dsi_host host; struct drm_bridge bridge; struct drm_bridge *next_bridge; struct drm_connector connector; void __iomem *mmio; struct { struct clk *mod; struct clk *pll; struct clk *dsi; } clocks; enum mipi_dsi_pixel_format format; unsigned int num_data_lanes; unsigned int lanes; }; static inline struct rcar_mipi_dsi * bridge_to_rcar_mipi_dsi(struct drm_bridge *bridge) { return container_of(bridge, struct rcar_mipi_dsi, bridge); } static inline struct rcar_mipi_dsi * host_to_rcar_mipi_dsi(struct mipi_dsi_host *host) { return container_of(host, struct rcar_mipi_dsi, host); } static const u32 phtw[] = { 0x01020114, 0x01600115, /* General testing */ 0x01030116, 0x0102011d, /* General testing */ 0x011101a4, 0x018601a4, /* 1Gbps testing */ 0x014201a0, 0x010001a3, /* 1Gbps testing */ 0x0101011f, /* 1Gbps testing */ }; static const u32 phtw2[] = { 0x010c0130, 0x010c0140, /* General testing */ 0x010c0150, 0x010c0180, /* General testing */ 0x010c0190, 0x010a0160, 0x010a0170, 0x01800164, 0x01800174, /* 1Gbps testing */ }; static const u32 hsfreqrange_table[][2] = { { 80000000U, 0x00 }, { 90000000U, 0x10 }, { 100000000U, 0x20 }, { 110000000U, 0x30 }, { 120000000U, 0x01 }, { 130000000U, 0x11 }, { 140000000U, 0x21 }, { 150000000U, 0x31 }, { 160000000U, 0x02 }, { 170000000U, 0x12 }, { 180000000U, 0x22 }, { 190000000U, 0x32 }, { 205000000U, 0x03 }, { 220000000U, 0x13 }, { 235000000U, 0x23 }, { 250000000U, 0x33 }, { 275000000U, 0x04 }, { 300000000U, 0x14 }, { 325000000U, 0x25 }, { 350000000U, 0x35 }, { 400000000U, 0x05 }, { 450000000U, 0x16 }, { 500000000U, 0x26 }, { 550000000U, 0x37 }, { 600000000U, 0x07 }, { 650000000U, 0x18 }, { 700000000U, 0x28 }, { 750000000U, 0x39 }, { 800000000U, 0x09 }, { 850000000U, 0x19 }, { 900000000U, 0x29 }, { 950000000U, 0x3a }, { 1000000000U, 0x0a }, { 1050000000U, 0x1a }, { 1100000000U, 0x2a }, { 1150000000U, 0x3b }, { 1200000000U, 0x0b }, { 1250000000U, 0x1b }, { 1300000000U, 0x2b }, { 1350000000U, 0x3c }, { 1400000000U, 0x0c }, { 1450000000U, 0x1c }, { 1500000000U, 0x2c }, { 1550000000U, 0x3d }, { 1600000000U, 0x0d }, { 1650000000U, 0x1d }, { 1700000000U, 0x2e }, { 1750000000U, 0x3e }, { 1800000000U, 0x0e }, { 1850000000U, 0x1e }, { 1900000000U, 0x2f }, { 1950000000U, 0x3f }, { 2000000000U, 0x0f }, { 2050000000U, 0x40 }, { 2100000000U, 0x41 }, { 2150000000U, 0x42 }, { 2200000000U, 0x43 }, { 2250000000U, 0x44 }, { 2300000000U, 0x45 }, { 2350000000U, 0x46 }, { 2400000000U, 0x47 }, { 2450000000U, 0x48 }, { 2500000000U, 0x49 }, { /* sentinel */ }, }; struct vco_cntrl_value { u32 min_freq; u32 max_freq; u16 value; }; static const struct vco_cntrl_value vco_cntrl_table[] = { { .min_freq = 40000000U, .max_freq = 55000000U, .value = 0x3f }, { .min_freq = 52500000U, .max_freq = 80000000U, .value = 0x39 }, { .min_freq = 80000000U, .max_freq = 110000000U, .value = 0x2f }, { .min_freq = 105000000U, .max_freq = 160000000U, .value = 0x29 }, { .min_freq = 160000000U, .max_freq = 220000000U, .value = 0x1f }, { .min_freq = 210000000U, .max_freq = 320000000U, .value = 0x19 }, { .min_freq = 320000000U, .max_freq = 440000000U, .value = 0x0f }, { .min_freq = 420000000U, .max_freq = 660000000U, .value = 0x09 }, { .min_freq = 630000000U, .max_freq = 1149000000U, .value = 0x03 }, { .min_freq = 1100000000U, .max_freq = 1152000000U, .value = 0x01 }, { .min_freq = 1150000000U, .max_freq = 1250000000U, .value = 0x01 }, { /* sentinel */ }, }; static void rcar_mipi_dsi_write(struct rcar_mipi_dsi *dsi, u32 reg, u32 data) { iowrite32(data, dsi->mmio + reg); } static u32 rcar_mipi_dsi_read(struct rcar_mipi_dsi *dsi, u32 reg) { return ioread32(dsi->mmio + reg); } static void rcar_mipi_dsi_clr(struct rcar_mipi_dsi *dsi, u32 reg, u32 clr) { rcar_mipi_dsi_write(dsi, reg, rcar_mipi_dsi_read(dsi, reg) & ~clr); } static void rcar_mipi_dsi_set(struct rcar_mipi_dsi *dsi, u32 reg, u32 set) { rcar_mipi_dsi_write(dsi, reg, rcar_mipi_dsi_read(dsi, reg) | set); } static int rcar_mipi_dsi_phtw_test(struct rcar_mipi_dsi *dsi, u32 phtw) { u32 status; int ret; rcar_mipi_dsi_write(dsi, PHTW, phtw); ret = read_poll_timeout(rcar_mipi_dsi_read, status, !(status & (PHTW_DWEN | PHTW_CWEN)), 2000, 10000, false, dsi, PHTW); if (ret < 0) { dev_err(dsi->dev, "PHY test interface write timeout (0x%08x)\n", phtw); return ret; } return ret; } /* ----------------------------------------------------------------------------- * Hardware Setup */ struct dsi_setup_info { unsigned long fout; u16 vco_cntrl; u16 prop_cntrl; u16 hsfreqrange; u16 div; unsigned int m; unsigned int n; }; static void rcar_mipi_dsi_parameters_calc(struct rcar_mipi_dsi *dsi, struct clk *clk, unsigned long target, struct dsi_setup_info *setup_info) { const struct vco_cntrl_value *vco_cntrl; unsigned long fout_target; unsigned long fin, fout; unsigned long hsfreq; unsigned int best_err = -1; unsigned int divider; unsigned int n; unsigned int i; unsigned int err; /* * Calculate Fout = dot clock * ColorDepth / (2 * Lane Count) * The range out Fout is [40 - 1250] Mhz */ fout_target = target * mipi_dsi_pixel_format_to_bpp(dsi->format) / (2 * dsi->lanes); if (fout_target < 40000000 || fout_target > 1250000000) return; /* Find vco_cntrl */ for (vco_cntrl = vco_cntrl_table; vco_cntrl->min_freq != 0; vco_cntrl++) { if (fout_target > vco_cntrl->min_freq && fout_target <= vco_cntrl->max_freq) { setup_info->vco_cntrl = vco_cntrl->value; if (fout_target >= 1150000000) setup_info->prop_cntrl = 0x0c; else setup_info->prop_cntrl = 0x0b; break; } } /* Add divider */ setup_info->div = (setup_info->vco_cntrl & 0x30) >> 4; /* Find hsfreqrange */ hsfreq = fout_target * 2; for (i = 0; i < ARRAY_SIZE(hsfreqrange_table); i++) { if (hsfreqrange_table[i][0] >= hsfreq) { setup_info->hsfreqrange = hsfreqrange_table[i][1]; break; } } /* * Calculate n and m for PLL clock * Following the HW manual the ranges of n and m are * n = [3-8] and m = [64-625] */ fin = clk_get_rate(clk); divider = 1 << setup_info->div; for (n = 3; n < 9; n++) { unsigned long fpfd; unsigned int m; fpfd = fin / n; for (m = 64; m < 626; m++) { fout = fpfd * m / divider; err = abs((long)(fout - fout_target) * 10000 / (long)fout_target); if (err < best_err) { setup_info->m = m - 2; setup_info->n = n - 1; setup_info->fout = fout; best_err = err; if (err == 0) goto done; } } } done: dev_dbg(dsi->dev, "%pC %lu Hz -> Fout %lu Hz (target %lu Hz, error %d.%02u%%), PLL M/N/DIV %u/%u/%u\n", clk, fin, setup_info->fout, fout_target, best_err / 100, best_err % 100, setup_info->m, setup_info->n, setup_info->div); dev_dbg(dsi->dev, "vco_cntrl = 0x%x\tprop_cntrl = 0x%x\thsfreqrange = 0x%x\n", setup_info->vco_cntrl, setup_info->prop_cntrl, setup_info->hsfreqrange); } static void rcar_mipi_dsi_set_display_timing(struct rcar_mipi_dsi *dsi, const struct drm_display_mode *mode) { u32 setr; u32 vprmset0r; u32 vprmset1r; u32 vprmset2r; u32 vprmset3r; u32 vprmset4r; /* Configuration for Pixel Stream and Packet Header */ if (mipi_dsi_pixel_format_to_bpp(dsi->format) == 24) rcar_mipi_dsi_write(dsi, TXVMPSPHSETR, TXVMPSPHSETR_DT_RGB24); else if (mipi_dsi_pixel_format_to_bpp(dsi->format) == 18) rcar_mipi_dsi_write(dsi, TXVMPSPHSETR, TXVMPSPHSETR_DT_RGB18); else if (mipi_dsi_pixel_format_to_bpp(dsi->format) == 16) rcar_mipi_dsi_write(dsi, TXVMPSPHSETR, TXVMPSPHSETR_DT_RGB16); else { dev_warn(dsi->dev, "unsupported format"); return; } /* Configuration for Blanking sequence and Input Pixel */ setr = TXVMSETR_HSABPEN_EN | TXVMSETR_HBPBPEN_EN | TXVMSETR_HFPBPEN_EN | TXVMSETR_SYNSEQ_PULSES | TXVMSETR_PIXWDTH | TXVMSETR_VSTPM; rcar_mipi_dsi_write(dsi, TXVMSETR, setr); /* Configuration for Video Parameters */ vprmset0r = (mode->flags & DRM_MODE_FLAG_PVSYNC ? TXVMVPRMSET0R_VSPOL_HIG : TXVMVPRMSET0R_VSPOL_LOW) | (mode->flags & DRM_MODE_FLAG_PHSYNC ? TXVMVPRMSET0R_HSPOL_HIG : TXVMVPRMSET0R_HSPOL_LOW) | TXVMVPRMSET0R_CSPC_RGB | TXVMVPRMSET0R_BPP_24; vprmset1r = TXVMVPRMSET1R_VACTIVE(mode->vdisplay) | TXVMVPRMSET1R_VSA(mode->vsync_end - mode->vsync_start); vprmset2r = TXVMVPRMSET2R_VFP(mode->vsync_start - mode->vdisplay) | TXVMVPRMSET2R_VBP(mode->vtotal - mode->vsync_end); vprmset3r = TXVMVPRMSET3R_HACTIVE(mode->hdisplay) | TXVMVPRMSET3R_HSA(mode->hsync_end - mode->hsync_start); vprmset4r = TXVMVPRMSET4R_HFP(mode->hsync_start - mode->hdisplay) | TXVMVPRMSET4R_HBP(mode->htotal - mode->hsync_end); rcar_mipi_dsi_write(dsi, TXVMVPRMSET0R, vprmset0r); rcar_mipi_dsi_write(dsi, TXVMVPRMSET1R, vprmset1r); rcar_mipi_dsi_write(dsi, TXVMVPRMSET2R, vprmset2r); rcar_mipi_dsi_write(dsi, TXVMVPRMSET3R, vprmset3r); rcar_mipi_dsi_write(dsi, TXVMVPRMSET4R, vprmset4r); } static int rcar_mipi_dsi_startup(struct rcar_mipi_dsi *dsi, const struct drm_display_mode *mode) { struct dsi_setup_info setup_info = {}; unsigned int timeout; int ret, i; int dsi_format; u32 phy_setup; u32 clockset2, clockset3; u32 ppisetr; u32 vclkset; /* Checking valid format */ dsi_format = mipi_dsi_pixel_format_to_bpp(dsi->format); if (dsi_format < 0) { dev_warn(dsi->dev, "invalid format"); return -EINVAL; } /* Parameters Calculation */ rcar_mipi_dsi_parameters_calc(dsi, dsi->clocks.pll, mode->clock * 1000, &setup_info); /* LPCLK enable */ rcar_mipi_dsi_set(dsi, LPCLKSET, LPCLKSET_CKEN); /* CFGCLK enabled */ rcar_mipi_dsi_set(dsi, CFGCLKSET, CFGCLKSET_CKEN); rcar_mipi_dsi_clr(dsi, PHYSETUP, PHYSETUP_RSTZ); rcar_mipi_dsi_clr(dsi, PHYSETUP, PHYSETUP_SHUTDOWNZ); rcar_mipi_dsi_set(dsi, PHTC, PHTC_TESTCLR); rcar_mipi_dsi_clr(dsi, PHTC, PHTC_TESTCLR); /* PHY setting */ phy_setup = rcar_mipi_dsi_read(dsi, PHYSETUP); phy_setup &= ~PHYSETUP_HSFREQRANGE_MASK; phy_setup |= PHYSETUP_HSFREQRANGE(setup_info.hsfreqrange); rcar_mipi_dsi_write(dsi, PHYSETUP, phy_setup); for (i = 0; i < ARRAY_SIZE(phtw); i++) { ret = rcar_mipi_dsi_phtw_test(dsi, phtw[i]); if (ret < 0) return ret; } /* PLL Clock Setting */ rcar_mipi_dsi_clr(dsi, CLOCKSET1, CLOCKSET1_SHADOW_CLEAR); rcar_mipi_dsi_set(dsi, CLOCKSET1, CLOCKSET1_SHADOW_CLEAR); rcar_mipi_dsi_clr(dsi, CLOCKSET1, CLOCKSET1_SHADOW_CLEAR); clockset2 = CLOCKSET2_M(setup_info.m) | CLOCKSET2_N(setup_info.n) | CLOCKSET2_VCO_CNTRL(setup_info.vco_cntrl); clockset3 = CLOCKSET3_PROP_CNTRL(setup_info.prop_cntrl) | CLOCKSET3_INT_CNTRL(0) | CLOCKSET3_CPBIAS_CNTRL(0x10) | CLOCKSET3_GMP_CNTRL(1); rcar_mipi_dsi_write(dsi, CLOCKSET2, clockset2); rcar_mipi_dsi_write(dsi, CLOCKSET3, clockset3); rcar_mipi_dsi_clr(dsi, CLOCKSET1, CLOCKSET1_UPDATEPLL); rcar_mipi_dsi_set(dsi, CLOCKSET1, CLOCKSET1_UPDATEPLL); udelay(10); rcar_mipi_dsi_clr(dsi, CLOCKSET1, CLOCKSET1_UPDATEPLL); ppisetr = PPISETR_DLEN_3 | PPISETR_CLEN; rcar_mipi_dsi_write(dsi, PPISETR, ppisetr); rcar_mipi_dsi_set(dsi, PHYSETUP, PHYSETUP_SHUTDOWNZ); rcar_mipi_dsi_set(dsi, PHYSETUP, PHYSETUP_RSTZ); usleep_range(400, 500); /* Checking PPI clock status register */ for (timeout = 10; timeout > 0; --timeout) { if ((rcar_mipi_dsi_read(dsi, PPICLSR) & PPICLSR_STPST) && (rcar_mipi_dsi_read(dsi, PPIDLSR) & PPIDLSR_STPST) && (rcar_mipi_dsi_read(dsi, CLOCKSET1) & CLOCKSET1_LOCK)) break; usleep_range(1000, 2000); } if (!timeout) { dev_err(dsi->dev, "failed to enable PPI clock\n"); return -ETIMEDOUT; } for (i = 0; i < ARRAY_SIZE(phtw2); i++) { ret = rcar_mipi_dsi_phtw_test(dsi, phtw2[i]); if (ret < 0) return ret; } /* Enable DOT clock */ vclkset = VCLKSET_CKEN; rcar_mipi_dsi_write(dsi, VCLKSET, vclkset); if (dsi_format == 24) vclkset |= VCLKSET_BPP_24; else if (dsi_format == 18) vclkset |= VCLKSET_BPP_18; else if (dsi_format == 16) vclkset |= VCLKSET_BPP_16; else { dev_warn(dsi->dev, "unsupported format"); return -EINVAL; } vclkset |= VCLKSET_COLOR_RGB | VCLKSET_DIV(setup_info.div) | VCLKSET_LANE(dsi->lanes - 1); rcar_mipi_dsi_write(dsi, VCLKSET, vclkset); /* After setting VCLKSET register, enable VCLKEN */ rcar_mipi_dsi_set(dsi, VCLKEN, VCLKEN_CKEN); dev_dbg(dsi->dev, "DSI device is started\n"); return 0; } static void rcar_mipi_dsi_shutdown(struct rcar_mipi_dsi *dsi) { /* Disable VCLKEN */ rcar_mipi_dsi_write(dsi, VCLKSET, 0); /* Disable DOT clock */ rcar_mipi_dsi_write(dsi, VCLKSET, 0); rcar_mipi_dsi_clr(dsi, PHYSETUP, PHYSETUP_RSTZ); rcar_mipi_dsi_clr(dsi, PHYSETUP, PHYSETUP_SHUTDOWNZ); /* CFGCLK disable */ rcar_mipi_dsi_clr(dsi, CFGCLKSET, CFGCLKSET_CKEN); /* LPCLK disable */ rcar_mipi_dsi_clr(dsi, LPCLKSET, LPCLKSET_CKEN); dev_dbg(dsi->dev, "DSI device is shutdown\n"); } static int rcar_mipi_dsi_clk_enable(struct rcar_mipi_dsi *dsi) { int ret; reset_control_deassert(dsi->rstc); ret = clk_prepare_enable(dsi->clocks.mod); if (ret < 0) goto err_reset; ret = clk_prepare_enable(dsi->clocks.dsi); if (ret < 0) goto err_clock; return 0; err_clock: clk_disable_unprepare(dsi->clocks.mod); err_reset: reset_control_assert(dsi->rstc); return ret; } static void rcar_mipi_dsi_clk_disable(struct rcar_mipi_dsi *dsi) { clk_disable_unprepare(dsi->clocks.dsi); clk_disable_unprepare(dsi->clocks.mod); reset_control_assert(dsi->rstc); } static int rcar_mipi_dsi_start_hs_clock(struct rcar_mipi_dsi *dsi) { /* * In HW manual, we need to check TxDDRClkHS-Q Stable? but it dont * write how to check. So we skip this check in this patch */ u32 status; int ret; /* Start HS clock. */ rcar_mipi_dsi_set(dsi, PPICLCR, PPICLCR_TXREQHS); ret = read_poll_timeout(rcar_mipi_dsi_read, status, status & PPICLSR_TOHS, 2000, 10000, false, dsi, PPICLSR); if (ret < 0) { dev_err(dsi->dev, "failed to enable HS clock\n"); return ret; } rcar_mipi_dsi_set(dsi, PPICLSCR, PPICLSCR_TOHS); return 0; } static int rcar_mipi_dsi_start_video(struct rcar_mipi_dsi *dsi) { u32 status; int ret; /* Wait for the link to be ready. */ ret = read_poll_timeout(rcar_mipi_dsi_read, status, !(status & (LINKSR_LPBUSY | LINKSR_HSBUSY)), 2000, 10000, false, dsi, LINKSR); if (ret < 0) { dev_err(dsi->dev, "Link failed to become ready\n"); return ret; } /* De-assert video FIFO clear. */ rcar_mipi_dsi_clr(dsi, TXVMCR, TXVMCR_VFCLR); ret = read_poll_timeout(rcar_mipi_dsi_read, status, status & TXVMSR_VFRDY, 2000, 10000, false, dsi, TXVMSR); if (ret < 0) { dev_err(dsi->dev, "Failed to de-assert video FIFO clear\n"); return ret; } /* Enable transmission in video mode. */ rcar_mipi_dsi_set(dsi, TXVMCR, TXVMCR_EN_VIDEO); ret = read_poll_timeout(rcar_mipi_dsi_read, status, status & TXVMSR_RDY, 2000, 10000, false, dsi, TXVMSR); if (ret < 0) { dev_err(dsi->dev, "Failed to enable video transmission\n"); return ret; } return 0; } static void rcar_mipi_dsi_stop_video(struct rcar_mipi_dsi *dsi) { u32 status; int ret; /* Disable transmission in video mode. */ rcar_mipi_dsi_clr(dsi, TXVMCR, TXVMCR_EN_VIDEO); ret = read_poll_timeout(rcar_mipi_dsi_read, status, !(status & TXVMSR_ACT), 2000, 100000, false, dsi, TXVMSR); if (ret < 0) { dev_err(dsi->dev, "Failed to disable video transmission\n"); return; } /* Assert video FIFO clear. */ rcar_mipi_dsi_set(dsi, TXVMCR, TXVMCR_VFCLR); ret = read_poll_timeout(rcar_mipi_dsi_read, status, !(status & TXVMSR_VFRDY), 2000, 100000, false, dsi, TXVMSR); if (ret < 0) { dev_err(dsi->dev, "Failed to assert video FIFO clear\n"); return; } } /* ----------------------------------------------------------------------------- * Bridge */ static int rcar_mipi_dsi_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { struct rcar_mipi_dsi *dsi = bridge_to_rcar_mipi_dsi(bridge); return drm_bridge_attach(bridge->encoder, dsi->next_bridge, bridge, flags); } static void rcar_mipi_dsi_atomic_enable(struct drm_bridge *bridge, struct drm_bridge_state *old_bridge_state) { struct rcar_mipi_dsi *dsi = bridge_to_rcar_mipi_dsi(bridge); rcar_mipi_dsi_start_video(dsi); } static void rcar_mipi_dsi_atomic_disable(struct drm_bridge *bridge, struct drm_bridge_state *old_bridge_state) { struct rcar_mipi_dsi *dsi = bridge_to_rcar_mipi_dsi(bridge); rcar_mipi_dsi_stop_video(dsi); } void rcar_mipi_dsi_pclk_enable(struct drm_bridge *bridge, struct drm_atomic_state *state) { struct rcar_mipi_dsi *dsi = bridge_to_rcar_mipi_dsi(bridge); const struct drm_display_mode *mode; struct drm_connector *connector; struct drm_crtc *crtc; int ret; connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); crtc = drm_atomic_get_new_connector_state(state, connector)->crtc; mode = &drm_atomic_get_new_crtc_state(state, crtc)->adjusted_mode; ret = rcar_mipi_dsi_clk_enable(dsi); if (ret < 0) { dev_err(dsi->dev, "failed to enable DSI clocks\n"); return; } ret = rcar_mipi_dsi_startup(dsi, mode); if (ret < 0) goto err_dsi_startup; rcar_mipi_dsi_set_display_timing(dsi, mode); ret = rcar_mipi_dsi_start_hs_clock(dsi); if (ret < 0) goto err_dsi_start_hs; return; err_dsi_start_hs: rcar_mipi_dsi_shutdown(dsi); err_dsi_startup: rcar_mipi_dsi_clk_disable(dsi); } EXPORT_SYMBOL_GPL(rcar_mipi_dsi_pclk_enable); void rcar_mipi_dsi_pclk_disable(struct drm_bridge *bridge) { struct rcar_mipi_dsi *dsi = bridge_to_rcar_mipi_dsi(bridge); rcar_mipi_dsi_shutdown(dsi); rcar_mipi_dsi_clk_disable(dsi); } EXPORT_SYMBOL_GPL(rcar_mipi_dsi_pclk_disable); static enum drm_mode_status rcar_mipi_dsi_bridge_mode_valid(struct drm_bridge *bridge, const struct drm_display_info *info, const struct drm_display_mode *mode) { if (mode->clock > 297000) return MODE_CLOCK_HIGH; return MODE_OK; } static const struct drm_bridge_funcs rcar_mipi_dsi_bridge_ops = { .attach = rcar_mipi_dsi_attach, .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, .atomic_enable = rcar_mipi_dsi_atomic_enable, .atomic_disable = rcar_mipi_dsi_atomic_disable, .mode_valid = rcar_mipi_dsi_bridge_mode_valid, }; /* ----------------------------------------------------------------------------- * Host setting */ static int rcar_mipi_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) { struct rcar_mipi_dsi *dsi = host_to_rcar_mipi_dsi(host); int ret; if (device->lanes > dsi->num_data_lanes) return -EINVAL; dsi->lanes = device->lanes; dsi->format = device->format; dsi->next_bridge = devm_drm_of_get_bridge(dsi->dev, dsi->dev->of_node, 1, 0); if (IS_ERR(dsi->next_bridge)) { ret = PTR_ERR(dsi->next_bridge); dev_err(dsi->dev, "failed to get next bridge: %d\n", ret); return ret; } /* Initialize the DRM bridge. */ dsi->bridge.funcs = &rcar_mipi_dsi_bridge_ops; dsi->bridge.of_node = dsi->dev->of_node; drm_bridge_add(&dsi->bridge); return 0; } static int rcar_mipi_dsi_host_detach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) { struct rcar_mipi_dsi *dsi = host_to_rcar_mipi_dsi(host); drm_bridge_remove(&dsi->bridge); return 0; } static const struct mipi_dsi_host_ops rcar_mipi_dsi_host_ops = { .attach = rcar_mipi_dsi_host_attach, .detach = rcar_mipi_dsi_host_detach, }; /* ----------------------------------------------------------------------------- * Probe & Remove */ static int rcar_mipi_dsi_parse_dt(struct rcar_mipi_dsi *dsi) { int ret; ret = drm_of_get_data_lanes_count_ep(dsi->dev->of_node, 1, 0, 1, 4); if (ret < 0) { dev_err(dsi->dev, "missing or invalid data-lanes property\n"); return ret; } dsi->num_data_lanes = ret; return 0; } static struct clk *rcar_mipi_dsi_get_clock(struct rcar_mipi_dsi *dsi, const char *name, bool optional) { struct clk *clk; clk = devm_clk_get(dsi->dev, name); if (!IS_ERR(clk)) return clk; if (PTR_ERR(clk) == -ENOENT && optional) return NULL; dev_err_probe(dsi->dev, PTR_ERR(clk), "failed to get %s clock\n", name ? name : "module"); return clk; } static int rcar_mipi_dsi_get_clocks(struct rcar_mipi_dsi *dsi) { dsi->clocks.mod = rcar_mipi_dsi_get_clock(dsi, NULL, false); if (IS_ERR(dsi->clocks.mod)) return PTR_ERR(dsi->clocks.mod); dsi->clocks.pll = rcar_mipi_dsi_get_clock(dsi, "pll", true); if (IS_ERR(dsi->clocks.pll)) return PTR_ERR(dsi->clocks.pll); dsi->clocks.dsi = rcar_mipi_dsi_get_clock(dsi, "dsi", true); if (IS_ERR(dsi->clocks.dsi)) return PTR_ERR(dsi->clocks.dsi); if (!dsi->clocks.pll && !dsi->clocks.dsi) { dev_err(dsi->dev, "no input clock (pll, dsi)\n"); return -EINVAL; } return 0; } static int rcar_mipi_dsi_probe(struct platform_device *pdev) { struct rcar_mipi_dsi *dsi; struct resource *mem; int ret; dsi = devm_kzalloc(&pdev->dev, sizeof(*dsi), GFP_KERNEL); if (dsi == NULL) return -ENOMEM; platform_set_drvdata(pdev, dsi); dsi->dev = &pdev->dev; dsi->info = of_device_get_match_data(&pdev->dev); ret = rcar_mipi_dsi_parse_dt(dsi); if (ret < 0) return ret; /* Acquire resources. */ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); dsi->mmio = devm_ioremap_resource(dsi->dev, mem); if (IS_ERR(dsi->mmio)) return PTR_ERR(dsi->mmio); ret = rcar_mipi_dsi_get_clocks(dsi); if (ret < 0) return ret; dsi->rstc = devm_reset_control_get(dsi->dev, NULL); if (IS_ERR(dsi->rstc)) { dev_err(dsi->dev, "failed to get cpg reset\n"); return PTR_ERR(dsi->rstc); } /* Initialize the DSI host. */ dsi->host.dev = dsi->dev; dsi->host.ops = &rcar_mipi_dsi_host_ops; ret = mipi_dsi_host_register(&dsi->host); if (ret < 0) return ret; return 0; } static int rcar_mipi_dsi_remove(struct platform_device *pdev) { struct rcar_mipi_dsi *dsi = platform_get_drvdata(pdev); mipi_dsi_host_unregister(&dsi->host); return 0; } static const struct of_device_id rcar_mipi_dsi_of_table[] = { { .compatible = "renesas,r8a779a0-dsi-csi2-tx" }, { } }; MODULE_DEVICE_TABLE(of, rcar_mipi_dsi_of_table); static struct platform_driver rcar_mipi_dsi_platform_driver = { .probe = rcar_mipi_dsi_probe, .remove = rcar_mipi_dsi_remove, .driver = { .name = "rcar-mipi-dsi", .of_match_table = rcar_mipi_dsi_of_table, }, }; module_platform_driver(rcar_mipi_dsi_platform_driver); MODULE_DESCRIPTION("Renesas R-Car MIPI DSI Encoder Driver"); MODULE_LICENSE("GPL");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1