|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | +/* |
| 3 | + * Copyright (C) 2021 BayLibre, SAS |
| 4 | + * Author: Neil Armstrong <narmstrong@baylibre.com> |
| 5 | + * Copyright (C) 2015 Amlogic, Inc. All rights reserved. |
| 6 | + */ |
| 7 | + |
| 8 | +#include <linux/clk.h> |
| 9 | +#include <linux/kernel.h> |
| 10 | +#include <linux/module.h> |
| 11 | +#include <linux/of_device.h> |
| 12 | +#include <linux/of_graph.h> |
| 13 | +#include <linux/reset.h> |
| 14 | +#include <linux/phy/phy.h> |
| 15 | +#include <linux/bitfield.h> |
| 16 | + |
| 17 | +#include <video/mipi_display.h> |
| 18 | + |
| 19 | +#include <drm/bridge/dw_mipi_dsi.h> |
| 20 | +#include <drm/drm_mipi_dsi.h> |
| 21 | + |
| 22 | +#include <drm/drm_atomic_helper.h> |
| 23 | +#include <drm/drm_device.h> |
| 24 | +#include <drm/drm_probe_helper.h> |
| 25 | +#include <drm/drm_print.h> |
| 26 | + |
| 27 | +#include "meson_drv.h" |
| 28 | +#include "meson_dw_mipi_dsi.h" |
| 29 | +#include "meson_registers.h" |
| 30 | +#include "meson_venc.h" |
| 31 | + |
| 32 | +#define DRIVER_NAME "meson-dw-mipi-dsi" |
| 33 | +#define DRIVER_DESC "Amlogic Meson MIPI-DSI DRM driver" |
| 34 | + |
| 35 | +struct meson_dw_mipi_dsi { |
| 36 | + struct meson_drm *priv; |
| 37 | + struct device *dev; |
| 38 | + void __iomem *base; |
| 39 | + struct phy *phy; |
| 40 | + union phy_configure_opts phy_opts; |
| 41 | + struct dw_mipi_dsi *dmd; |
| 42 | + struct dw_mipi_dsi_plat_data pdata; |
| 43 | + struct mipi_dsi_device *dsi_device; |
| 44 | + const struct drm_display_mode *mode; |
| 45 | + struct clk *bit_clk; |
| 46 | + struct clk *px_clk; |
| 47 | + struct reset_control *top_rst; |
| 48 | +}; |
| 49 | + |
| 50 | +#define encoder_to_meson_dw_mipi_dsi(x) \ |
| 51 | + container_of(x, struct meson_dw_mipi_dsi, encoder) |
| 52 | + |
| 53 | +static void meson_dw_mipi_dsi_hw_init(struct meson_dw_mipi_dsi *mipi_dsi) |
| 54 | +{ |
| 55 | + /* Software reset */ |
| 56 | + writel_bits_relaxed(MIPI_DSI_TOP_SW_RESET_DWC | MIPI_DSI_TOP_SW_RESET_INTR | |
| 57 | + MIPI_DSI_TOP_SW_RESET_DPI | MIPI_DSI_TOP_SW_RESET_TIMING, |
| 58 | + MIPI_DSI_TOP_SW_RESET_DWC | MIPI_DSI_TOP_SW_RESET_INTR | |
| 59 | + MIPI_DSI_TOP_SW_RESET_DPI | MIPI_DSI_TOP_SW_RESET_TIMING, |
| 60 | + mipi_dsi->base + MIPI_DSI_TOP_SW_RESET); |
| 61 | + writel_bits_relaxed(MIPI_DSI_TOP_SW_RESET_DWC | MIPI_DSI_TOP_SW_RESET_INTR | |
| 62 | + MIPI_DSI_TOP_SW_RESET_DPI | MIPI_DSI_TOP_SW_RESET_TIMING, |
| 63 | + 0, mipi_dsi->base + MIPI_DSI_TOP_SW_RESET); |
| 64 | + |
| 65 | + /* Enable clocks */ |
| 66 | + writel_bits_relaxed(MIPI_DSI_TOP_CLK_SYSCLK_EN | MIPI_DSI_TOP_CLK_PIXCLK_EN, |
| 67 | + MIPI_DSI_TOP_CLK_SYSCLK_EN | MIPI_DSI_TOP_CLK_PIXCLK_EN, |
| 68 | + mipi_dsi->base + MIPI_DSI_TOP_CLK_CNTL); |
| 69 | + |
| 70 | + /* Take memory out of power down */ |
| 71 | + writel_relaxed(0, mipi_dsi->base + MIPI_DSI_TOP_MEM_PD); |
| 72 | +} |
| 73 | + |
| 74 | +static int dw_mipi_dsi_phy_init(void *priv_data) |
| 75 | +{ |
| 76 | + struct meson_dw_mipi_dsi *mipi_dsi = priv_data; |
| 77 | + unsigned int dpi_data_format, venc_data_width; |
| 78 | + int ret; |
| 79 | + |
| 80 | + /* Set the bit clock rate to hs_clk_rate */ |
| 81 | + ret = clk_set_rate(mipi_dsi->bit_clk, |
| 82 | + mipi_dsi->phy_opts.mipi_dphy.hs_clk_rate); |
| 83 | + if (ret) { |
| 84 | + dev_err(mipi_dsi->dev, "Failed to set DSI Bit clock rate %lu (ret %d)\n", |
| 85 | + mipi_dsi->phy_opts.mipi_dphy.hs_clk_rate, ret); |
| 86 | + return ret; |
| 87 | + } |
| 88 | + |
| 89 | + /* Make sure the rate of the bit clock is not modified by someone else */ |
| 90 | + ret = clk_rate_exclusive_get(mipi_dsi->bit_clk); |
| 91 | + if (ret) { |
| 92 | + dev_err(mipi_dsi->dev, |
| 93 | + "Failed to set the exclusivity on the bit clock rate (ret %d)\n", ret); |
| 94 | + return ret; |
| 95 | + } |
| 96 | + |
| 97 | + ret = clk_set_rate(mipi_dsi->px_clk, mipi_dsi->mode->clock * 1000); |
| 98 | + |
| 99 | + if (ret) { |
| 100 | + dev_err(mipi_dsi->dev, "Failed to set DSI Pixel clock rate %u (%d)\n", |
| 101 | + mipi_dsi->mode->clock * 1000, ret); |
| 102 | + return ret; |
| 103 | + } |
| 104 | + |
| 105 | + switch (mipi_dsi->dsi_device->format) { |
| 106 | + case MIPI_DSI_FMT_RGB888: |
| 107 | + dpi_data_format = DPI_COLOR_24BIT; |
| 108 | + venc_data_width = VENC_IN_COLOR_24B; |
| 109 | + break; |
| 110 | + case MIPI_DSI_FMT_RGB666: |
| 111 | + dpi_data_format = DPI_COLOR_18BIT_CFG_2; |
| 112 | + venc_data_width = VENC_IN_COLOR_18B; |
| 113 | + break; |
| 114 | + case MIPI_DSI_FMT_RGB666_PACKED: |
| 115 | + case MIPI_DSI_FMT_RGB565: |
| 116 | + return -EINVAL; |
| 117 | + }; |
| 118 | + |
| 119 | + /* Configure color format for DPI register */ |
| 120 | + writel_relaxed(FIELD_PREP(MIPI_DSI_TOP_DPI_COLOR_MODE, dpi_data_format) | |
| 121 | + FIELD_PREP(MIPI_DSI_TOP_IN_COLOR_MODE, venc_data_width) | |
| 122 | + FIELD_PREP(MIPI_DSI_TOP_COMP2_SEL, 2) | |
| 123 | + FIELD_PREP(MIPI_DSI_TOP_COMP1_SEL, 1) | |
| 124 | + FIELD_PREP(MIPI_DSI_TOP_COMP0_SEL, 0), |
| 125 | + mipi_dsi->base + MIPI_DSI_TOP_CNTL); |
| 126 | + |
| 127 | + return phy_configure(mipi_dsi->phy, &mipi_dsi->phy_opts); |
| 128 | +} |
| 129 | + |
| 130 | +static void dw_mipi_dsi_phy_power_on(void *priv_data) |
| 131 | +{ |
| 132 | + struct meson_dw_mipi_dsi *mipi_dsi = priv_data; |
| 133 | + |
| 134 | + if (phy_power_on(mipi_dsi->phy)) |
| 135 | + dev_warn(mipi_dsi->dev, "Failed to power on PHY\n"); |
| 136 | +} |
| 137 | + |
| 138 | +static void dw_mipi_dsi_phy_power_off(void *priv_data) |
| 139 | +{ |
| 140 | + struct meson_dw_mipi_dsi *mipi_dsi = priv_data; |
| 141 | + |
| 142 | + if (phy_power_off(mipi_dsi->phy)) |
| 143 | + dev_warn(mipi_dsi->dev, "Failed to power off PHY\n"); |
| 144 | + |
| 145 | + /* Remove the exclusivity on the bit clock rate */ |
| 146 | + clk_rate_exclusive_put(mipi_dsi->bit_clk); |
| 147 | +} |
| 148 | + |
| 149 | +static int |
| 150 | +dw_mipi_dsi_get_lane_mbps(void *priv_data, const struct drm_display_mode *mode, |
| 151 | + unsigned long mode_flags, u32 lanes, u32 format, |
| 152 | + unsigned int *lane_mbps) |
| 153 | +{ |
| 154 | + struct meson_dw_mipi_dsi *mipi_dsi = priv_data; |
| 155 | + int bpp; |
| 156 | + |
| 157 | + mipi_dsi->mode = mode; |
| 158 | + |
| 159 | + bpp = mipi_dsi_pixel_format_to_bpp(mipi_dsi->dsi_device->format); |
| 160 | + |
| 161 | + phy_mipi_dphy_get_default_config(mode->clock * 1000, |
| 162 | + bpp, mipi_dsi->dsi_device->lanes, |
| 163 | + &mipi_dsi->phy_opts.mipi_dphy); |
| 164 | + |
| 165 | + *lane_mbps = DIV_ROUND_UP(mipi_dsi->phy_opts.mipi_dphy.hs_clk_rate, USEC_PER_SEC); |
| 166 | + |
| 167 | + return 0; |
| 168 | +} |
| 169 | + |
| 170 | +static int |
| 171 | +dw_mipi_dsi_phy_get_timing(void *priv_data, unsigned int lane_mbps, |
| 172 | + struct dw_mipi_dsi_dphy_timing *timing) |
| 173 | +{ |
| 174 | + struct meson_dw_mipi_dsi *mipi_dsi = priv_data; |
| 175 | + |
| 176 | + switch (mipi_dsi->mode->hdisplay) { |
| 177 | + case 240: |
| 178 | + case 768: |
| 179 | + case 1920: |
| 180 | + case 2560: |
| 181 | + timing->clk_lp2hs = 23; |
| 182 | + timing->clk_hs2lp = 38; |
| 183 | + timing->data_lp2hs = 15; |
| 184 | + timing->data_hs2lp = 9; |
| 185 | + break; |
| 186 | + |
| 187 | + default: |
| 188 | + timing->clk_lp2hs = 37; |
| 189 | + timing->clk_hs2lp = 135; |
| 190 | + timing->data_lp2hs = 50; |
| 191 | + timing->data_hs2lp = 3; |
| 192 | + } |
| 193 | + |
| 194 | + return 0; |
| 195 | +} |
| 196 | + |
| 197 | +static int |
| 198 | +dw_mipi_dsi_get_esc_clk_rate(void *priv_data, unsigned int *esc_clk_rate) |
| 199 | +{ |
| 200 | + *esc_clk_rate = 4; /* Mhz */ |
| 201 | + |
| 202 | + return 0; |
| 203 | +} |
| 204 | + |
| 205 | +static const struct dw_mipi_dsi_phy_ops meson_dw_mipi_dsi_phy_ops = { |
| 206 | + .init = dw_mipi_dsi_phy_init, |
| 207 | + .power_on = dw_mipi_dsi_phy_power_on, |
| 208 | + .power_off = dw_mipi_dsi_phy_power_off, |
| 209 | + .get_lane_mbps = dw_mipi_dsi_get_lane_mbps, |
| 210 | + .get_timing = dw_mipi_dsi_phy_get_timing, |
| 211 | + .get_esc_clk_rate = dw_mipi_dsi_get_esc_clk_rate, |
| 212 | +}; |
| 213 | + |
| 214 | +static int meson_dw_mipi_dsi_host_attach(void *priv_data, |
| 215 | + struct mipi_dsi_device *device) |
| 216 | +{ |
| 217 | + struct meson_dw_mipi_dsi *mipi_dsi = priv_data; |
| 218 | + int ret; |
| 219 | + |
| 220 | + mipi_dsi->dsi_device = device; |
| 221 | + |
| 222 | + switch (device->format) { |
| 223 | + case MIPI_DSI_FMT_RGB888: |
| 224 | + break; |
| 225 | + case MIPI_DSI_FMT_RGB666: |
| 226 | + break; |
| 227 | + case MIPI_DSI_FMT_RGB666_PACKED: |
| 228 | + case MIPI_DSI_FMT_RGB565: |
| 229 | + dev_err(mipi_dsi->dev, "invalid pixel format %d\n", device->format); |
| 230 | + return -EINVAL; |
| 231 | + }; |
| 232 | + |
| 233 | + ret = phy_init(mipi_dsi->phy); |
| 234 | + if (ret) |
| 235 | + return ret; |
| 236 | + |
| 237 | + meson_dw_mipi_dsi_hw_init(mipi_dsi); |
| 238 | + |
| 239 | + return 0; |
| 240 | +} |
| 241 | + |
| 242 | +static int meson_dw_mipi_dsi_host_detach(void *priv_data, |
| 243 | + struct mipi_dsi_device *device) |
| 244 | +{ |
| 245 | + struct meson_dw_mipi_dsi *mipi_dsi = priv_data; |
| 246 | + |
| 247 | + if (device == mipi_dsi->dsi_device) |
| 248 | + mipi_dsi->dsi_device = NULL; |
| 249 | + else |
| 250 | + return -EINVAL; |
| 251 | + |
| 252 | + return phy_exit(mipi_dsi->phy); |
| 253 | +} |
| 254 | + |
| 255 | +static const struct dw_mipi_dsi_host_ops meson_dw_mipi_dsi_host_ops = { |
| 256 | + .attach = meson_dw_mipi_dsi_host_attach, |
| 257 | + .detach = meson_dw_mipi_dsi_host_detach, |
| 258 | +}; |
| 259 | + |
| 260 | +static int meson_dw_mipi_dsi_probe(struct platform_device *pdev) |
| 261 | +{ |
| 262 | + struct meson_dw_mipi_dsi *mipi_dsi; |
| 263 | + struct device *dev = &pdev->dev; |
| 264 | + |
| 265 | + mipi_dsi = devm_kzalloc(dev, sizeof(*mipi_dsi), GFP_KERNEL); |
| 266 | + if (!mipi_dsi) |
| 267 | + return -ENOMEM; |
| 268 | + |
| 269 | + mipi_dsi->base = devm_platform_ioremap_resource(pdev, 0); |
| 270 | + if (IS_ERR(mipi_dsi->base)) |
| 271 | + return PTR_ERR(mipi_dsi->base); |
| 272 | + |
| 273 | + mipi_dsi->phy = devm_phy_get(dev, "dphy"); |
| 274 | + if (IS_ERR(mipi_dsi->phy)) |
| 275 | + return dev_err_probe(dev, PTR_ERR(mipi_dsi->phy), |
| 276 | + "failed to get mipi dphy\n"); |
| 277 | + |
| 278 | + mipi_dsi->bit_clk = devm_clk_get_enabled(dev, "bit"); |
| 279 | + if (IS_ERR(mipi_dsi->bit_clk)) { |
| 280 | + int ret = PTR_ERR(mipi_dsi->bit_clk); |
| 281 | + |
| 282 | + /* TOFIX GP0 on some platforms fails to lock in early boot, defer probe */ |
| 283 | + if (ret == -EIO) |
| 284 | + ret = -EPROBE_DEFER; |
| 285 | + |
| 286 | + return dev_err_probe(dev, ret, "Unable to get enabled bit_clk\n"); |
| 287 | + } |
| 288 | + |
| 289 | + mipi_dsi->px_clk = devm_clk_get_enabled(dev, "px"); |
| 290 | + if (IS_ERR(mipi_dsi->px_clk)) |
| 291 | + return dev_err_probe(dev, PTR_ERR(mipi_dsi->px_clk), |
| 292 | + "Unable to get enabled px_clk\n"); |
| 293 | + |
| 294 | + /* |
| 295 | + * We use a TOP reset signal because the APB reset signal |
| 296 | + * is handled by the TOP control registers. |
| 297 | + */ |
| 298 | + mipi_dsi->top_rst = devm_reset_control_get_exclusive(dev, "top"); |
| 299 | + if (IS_ERR(mipi_dsi->top_rst)) |
| 300 | + return dev_err_probe(dev, PTR_ERR(mipi_dsi->top_rst), |
| 301 | + "Unable to get reset control\n"); |
| 302 | + |
| 303 | + reset_control_assert(mipi_dsi->top_rst); |
| 304 | + usleep_range(10, 20); |
| 305 | + reset_control_deassert(mipi_dsi->top_rst); |
| 306 | + |
| 307 | + /* MIPI DSI Controller */ |
| 308 | + |
| 309 | + mipi_dsi->dev = dev; |
| 310 | + mipi_dsi->pdata.base = mipi_dsi->base; |
| 311 | + mipi_dsi->pdata.max_data_lanes = 4; |
| 312 | + mipi_dsi->pdata.phy_ops = &meson_dw_mipi_dsi_phy_ops; |
| 313 | + mipi_dsi->pdata.host_ops = &meson_dw_mipi_dsi_host_ops; |
| 314 | + mipi_dsi->pdata.priv_data = mipi_dsi; |
| 315 | + platform_set_drvdata(pdev, mipi_dsi); |
| 316 | + |
| 317 | + mipi_dsi->dmd = dw_mipi_dsi_probe(pdev, &mipi_dsi->pdata); |
| 318 | + if (IS_ERR(mipi_dsi->dmd)) |
| 319 | + return dev_err_probe(dev, PTR_ERR(mipi_dsi->dmd), |
| 320 | + "Failed to probe dw_mipi_dsi\n"); |
| 321 | + |
| 322 | + return 0; |
| 323 | +} |
| 324 | + |
| 325 | +static int meson_dw_mipi_dsi_remove(struct platform_device *pdev) |
| 326 | +{ |
| 327 | + struct meson_dw_mipi_dsi *mipi_dsi = platform_get_drvdata(pdev); |
| 328 | + |
| 329 | + dw_mipi_dsi_remove(mipi_dsi->dmd); |
| 330 | + |
| 331 | + return 0; |
| 332 | +} |
| 333 | + |
| 334 | +static const struct of_device_id meson_dw_mipi_dsi_of_table[] = { |
| 335 | + { .compatible = "amlogic,meson-g12a-dw-mipi-dsi", }, |
| 336 | + { } |
| 337 | +}; |
| 338 | +MODULE_DEVICE_TABLE(of, meson_dw_mipi_dsi_of_table); |
| 339 | + |
| 340 | +static struct platform_driver meson_dw_mipi_dsi_platform_driver = { |
| 341 | + .probe = meson_dw_mipi_dsi_probe, |
| 342 | + .remove = meson_dw_mipi_dsi_remove, |
| 343 | + .driver = { |
| 344 | + .name = DRIVER_NAME, |
| 345 | + .of_match_table = meson_dw_mipi_dsi_of_table, |
| 346 | + }, |
| 347 | +}; |
| 348 | +module_platform_driver(meson_dw_mipi_dsi_platform_driver); |
| 349 | + |
| 350 | +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); |
| 351 | +MODULE_DESCRIPTION(DRIVER_DESC); |
| 352 | +MODULE_LICENSE("GPL"); |
0 commit comments