[PATCH RFC 09/15] drm: imx: Add MIPI DSI host controller driver
Liu Ying
Ying.Liu at freescale.com
Wed Dec 17 01:44:33 PST 2014
Hi Thierry,
Sorry for the late response.
I tried to address almost all your comments locally first.
More feedback below.
On 12/10/2014 09:16 PM, Thierry Reding wrote:
> On Wed, Dec 10, 2014 at 04:37:22PM +0800, Liu Ying wrote:
>> This patch adds i.MX MIPI DSI host controller driver support.
>> Currently, the driver supports the burst with sync pulses mode only.
>>
>> Signed-off-by: Liu Ying <Ying.Liu at freescale.com>
>> ---
>> .../devicetree/bindings/drm/imx/mipi_dsi.txt | 81 ++
>> drivers/gpu/drm/imx/Kconfig | 6 +
>> drivers/gpu/drm/imx/Makefile | 1 +
>> drivers/gpu/drm/imx/imx-mipi-dsi.c | 1017 ++++++++++++++++++++
>> 4 files changed, 1105 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
>> create mode 100644 drivers/gpu/drm/imx/imx-mipi-dsi.c
>>
>> diff --git a/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt b/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
>> new file mode 100644
>> index 0000000..3d07fd7
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/drm/imx/mipi_dsi.txt
>> @@ -0,0 +1,81 @@
>> +Device-Tree bindings for MIPI DSI host controller
>> +
>> +MIPI DSI host controller
>> +========================
>> +
>> +The MIPI DSI host controller is a Synopsys DesignWare IP.
>> +It is a digital core that implements all protocol functions defined
>> +in the MIPI DSI specification, providing an interface between the
>> +system and the MIPI DPHY, and allowing communication with a MIPI DSI
>> +compliant display.
>> +
>> +Required properties:
>> + - #address-cells : Should be <1>.
>> + - #size-cells : Should be <0>.
>> + - compatible : Should be "fsl,imx6q-mipi-dsi" for i.MX6q/sdl SoCs.
>> + - reg : Physical base address of the controller and length of memory
>> + mapped region.
>> + - interrupts : The controller's interrupt number to the CPU(s).
>> + - gpr : Should be <&gpr>.
>> + The phandle points to the iomuxc-gpr region containing the
>> + multiplexer control register for the controller.
>
> Side-note: Shouldn't this really be a pinmux, then?
No. The muxing is inside the i.MX SoC.
There is a DT binding documentation for the system controller node(gpr)
at [1]. And, for i.MX DT sources, there are several existing use cases
in which the gpr node is referred by other nodes.
[1] Documentation/devicetree/bindings/mfd/syscon.txt.
>
>> + - clocks, clock-names : Phandles to the controller pllref, pllref_gate
>> + and core_cfg clocks, as described in [1] and [2].
>> + - panel at 0 : A panel node which contains a display-timings child node as
>> + defined in [3].
>
> There's no need for these to be named panel@*. They could be bridges for
> example. And no, they shouldn't contain a display-timings child node
> either. Panels should have a proper driver and the driver being device
> specific it should have the timings embedded.
Ok, I'll move the timing to the panel driver.
>
>> + - port@[0-4] : Up to four port nodes with endpoint definitions as defined
>> + in [4], corresponding to the four inputs to the controller multiplexer.
>> + Note that each port node should contain the input-port property to
>> + distinguish it from the panel node, as described in [5].
>
> [4] says that you can group all port nodes under a ports parent node. I
> think this is really what you want to do here to make it clear that the
> ports aren't part of the DSI host binding part of the device.
>
Accepted.
>> diff --git a/drivers/gpu/drm/imx/imx-mipi-dsi.c b/drivers/gpu/drm/imx/imx-mipi-dsi.c
> [...]
>> +/*
>> + * i.MX drm driver - MIPI DSI Host Controller
>> + *
>> + * Copyright (C) 2011-2014 Freescale Semiconductor, Inc.
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License
>> + * as published by the Free Software Foundation; either version 2
>> + * of the License, or (at your option) any later version.
>> + *
>> + * 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.
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/component.h>
>> +#include <linux/mfd/syscon.h>
>> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
>> +#include <linux/module.h>
>> +#include <linux/of_address.h>
>> +#include <linux/of_device.h>
>> +#include <linux/regmap.h>
>> +#include <linux/videodev2.h>
>> +#include <asm/div64.h>
>
> Don't you want the more generic linux/math64.h here?
I'll use linux/math64.h.
>
>> +#include <drm/drmP.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_fb_helper.h>
>
> I don't see any of the functions defined in that header used here.
I'll remove this.
>
>> +#include <drm/drm_mipi_dsi.h>
>> +#include <drm/drm_panel.h>
>> +#include <video/mipi_display.h>
>> +#include <video/of_videomode.h>
>> +#include <video/videomode.h>
>> +
>> +#include "imx-drm.h"
>> +
>> +#define DRIVER_NAME "imx-mipi-dsi"
>> +
>> +#define DSI_VERSION 0x00
>> +
>> +#define DSI_PWR_UP 0x04
>> +#define RESET 0
>> +#define POWERUP BIT(0)
>> +
>> +#define DSI_CLKMGR_CFG 0x08
>> +#define TO_CLK_DIVIDSION(div) (((div) & 0xff) << 8)
>> +#define TX_ESC_CLK_DIVIDSION(div) (((div) & 0xff) << 0)
>
> Your indentation here is... weird.
I'll fix this.
>
>> +#define IMX_MIPI_DSI_MAX_DATA_LANES 2
>> +
>> +#define PHY_STATUS_TIMEOUT 10
>> +#define CMD_PKT_STATUS_TIMEOUT 20
>> +
>> +#define IMX_MIPI_DSI_PLD_DATA_BUF_SIZE 4
>> +
>> +#define MHZ 1000000
>
> Why not reuse the existing USEC_PER_SEC?
Accepted.
>
>> +#define host_to_dsi(host) container_of(host, struct imx_mipi_dsi, dsi_host)
>> +#define con_to_dsi(x) container_of(x, struct imx_mipi_dsi, connector)
>> +#define enc_to_dsi(x) container_of(x, struct imx_mipi_dsi, encoder)
>
> These should really be static inline functions for proper type safety.
Ok, I'll change to use functions.
>
>> +struct imx_mipi_dsi {
>> + struct mipi_dsi_host dsi_host;
>> + struct drm_connector connector;
>> + struct drm_encoder encoder;
>> + struct device_node *panel_node;
>> + struct drm_panel *panel;
>> + struct device *dev;
>> +
>> + struct regmap *regmap;
>> + void __iomem *base;
>> +
>> + struct clk *pllref_clk;
>> + struct clk *pllref_gate_clk;
>> + struct clk *cfg_clk;
>> +
>> + unsigned int lane_mbps; /* per lane */
>> + u32 channel;
>> + u32 lanes;
>> + u32 format;
>> + struct videomode vm;
>> +
>> + bool enabled;
>
> Do you really need this flag?
I'll remove this flag.
>
>> +};
>> +
>> +enum {
>> + STATUS_TO_CLEAR,
>> + STATUS_TO_SET,
>> +};
>> +
>> +struct dphy_pll_testdin_map {
>> + int max_mbps;
>
> unsigned?
Accepted.
>
>> + u8 testdin;
>> +};
>> +
>> +/* The table is based on 27MHz DPHY pll reference clock. */
>> +static const struct dphy_pll_testdin_map dptdin_map[] = {
>> + {160, 0x04}, {180, 0x24}, {200, 0x44}, {210, 0x06},
>> + {240, 0x26}, {250, 0x46}, {270, 0x08}, {300, 0x28},
>> + {330, 0x48}, {360, 0x2a}, {400, 0x4a}, {450, 0x0c},
>> + {500, 0x2c}, {550, 0x0e}, {600, 0x2e}, {650, 0x10},
>> + {700, 0x30}, {750, 0x12}, {800, 0x32}, {850, 0x14},
>> + {900, 0x34}, {950, 0x54}, {1000, 0x74}
>> +};
>> +
>> +static int max_mbps_to_testdin(unsigned int max_mbps)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < ARRAY_SIZE(dptdin_map); i++)
>> + if (dptdin_map[i].max_mbps == max_mbps)
>> + return dptdin_map[i].testdin;
>> +
>> + return -EINVAL;
>> +}
>> +
>> +static int format_to_bpp(struct imx_mipi_dsi *dsi)
>> +{
>> + switch (dsi->format) {
>> + case MIPI_DSI_FMT_RGB888:
>> + case MIPI_DSI_FMT_RGB666:
>> + return 24;
>> + case MIPI_DSI_FMT_RGB666_PACKED:
>> + return 18;
>> + case MIPI_DSI_FMT_RGB565:
>> + return 16;
>> + default:
>> + dev_err(dsi->dev, "unsupported pixel format\n");
>> + return -EINVAL;
>> + }
>> +}
>
> Is there a reason why this can't be a standard MIPI DSI function?
How about moving this to include/drm/drm_mipi_dsi.h as an inline
function?
>
>> +
>> +static int check_status(struct imx_mipi_dsi *dsi, u32 reg, u32 status,
>> + int timeout, bool to_set)
>> +{
>> + u32 val;
>> + bool out = false;
>> +
>> + val = dsi_read(dsi, reg);
>> + for (;;) {
>> + out = to_set ? (val & status) : !(val & status);
>> + if (out)
>> + break;
>> +
>> + if (!timeout--)
>> + return -EFAULT;
>> +
>> + msleep(1);
>> + val = dsi_read(dsi, reg);
>> + }
>> + return 0;
>> +}
>
> You should probably use a properly timed loop here. msleep() isn't
> guaranteed to return after exactly one millisecond, so your timeout is
> never going to be accurate. Something like the following would be better
> in my opinion:
>
> timeout = jiffies + msecs_to_jiffies(timeout);
>
> while (time_before(jiffies, timeout)) {
> ...
> }
>
> Also timeout should be unsigned long in that case.
Accepted.
>
>> +static int imx_mipi_dsi_host_attach(struct mipi_dsi_host *host,
>> + struct mipi_dsi_device *device)
>> +{
>> + struct imx_mipi_dsi *dsi = host_to_dsi(host);
>> + int ret;
>> +
>> + if (device->lanes > IMX_MIPI_DSI_MAX_DATA_LANES) {
>> + dev_err(dsi->dev, "the number of data lanes(%d) is too many\n",
>> + device->lanes);
>> + return -EINVAL;
>> + }
>> +
>> + if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) ||
>> + !(device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)) {
>> + dev_err(dsi->dev, "device mode is unsupported\n");
>> + return -EINVAL;
>> + }
>> +
>> + dsi->lanes = device->lanes;
>> + dsi->channel = device->channel;
>> + dsi->format = device->format;
>> + dsi->panel_node = device->dev.of_node;
>> + of_get_videomode(dsi->panel_node, &dsi->vm, 0);
>
> No, you shouldn't use this. Query the mode from the panel instead. Or
> rather, encode this requirement in ->mode_valid() since that'll give you
> direct access to the mode.
>
> That way, ->host_attach() becomes a filter for compatible devices, yet
> devices may support multiple modes, therefore ->mode_valid() can be used
> to filter out those that don't work with your DSI host controller. There
> is a subtle difference here between devices that are compatible with the
> controller and modes that the controller doesn't support.
Accepted.
In the next version ->host_attach(), I will check the peripheral
compatibility. Also, I'll get the panel by calling of_drm_find_panel(),
and then call the drm_panel_attach() function.
Do you think this is okay?
>
>> +
>> + ret = imx_mipi_dsi_get_lane_bps(dsi, &dsi->lane_mbps);
>> + if (ret < 0)
>> + return ret;
>> +
>> + return 0;
>> +}
>> +
>> +static int imx_mipi_dsi_host_detach(struct mipi_dsi_host *host,
>> + struct mipi_dsi_device *device)
>> +{
>> + struct imx_mipi_dsi *dsi = host_to_dsi(host);
>> +
>> + dsi->panel_node = NULL;
>> + dsi->panel = NULL;
>> +
>> + return 0;
>> +}
>
> You'll want to detach from the panel here as well.
Ok. I'll call drm_panel_detach() here.
>
>> +
>> +static int imx_mipi_dsi_gen_pkt_hdr_write(struct imx_mipi_dsi *dsi, u32 val)
>> +{
>> + int ret;
>> +
>> + ret = check_status(dsi, DSI_CMD_PKT_STATUS, GEN_CMD_FULL,
>> + CMD_PKT_STATUS_TIMEOUT, STATUS_TO_CLEAR);
>> + if (ret < 0) {
>> + dev_err(dsi->dev, "failed to get avaliable command FIFO\n");
>> + return ret;
>> + }
>> +
>> + dsi_write(dsi, DSI_GEN_HDR, val);
>> +
>> + ret = check_status(dsi, DSI_CMD_PKT_STATUS,
>> + GEN_CMD_EMPTY | GEN_PLD_W_EMPTY,
>> + CMD_PKT_STATUS_TIMEOUT, STATUS_TO_SET);
>> + if (ret < 0) {
>> + dev_err(dsi->dev, "failed to write command FIFO\n");
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int imx_mipi_dsi_dcs_short_write(struct imx_mipi_dsi *dsi,
>> + const struct mipi_dsi_msg *msg)
>> +{
>> + const u16 *tx_buf = msg->tx_buf;
>> + u32 val = GEN_HDATA(*tx_buf) | GEN_HTYPE(msg->type);
>> +
>> + if (msg->tx_len > 2) {
>> + dev_err(dsi->dev, "too long tx buf length %d for short write\n",
>> + msg->tx_len);
>> + return -EINVAL;
>> + }
>> +
>> + return imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
>> +}
>
> It seems like you would profit from converting to the newly introduced
> mipi_dsi_create_packet() helper which does most of the packing along
> with some sanity checking for you.
I looked at the helper. It seems it's not quite straightforward for me
to use it here. The message type here defined by GEN_HTYPE() is 8bit
long, while the helper seems to take it as 6bit long?
>
>> +
>> +static int imx_mipi_dsi_dcs_long_write(struct imx_mipi_dsi *dsi,
>> + const struct mipi_dsi_msg *msg)
>> +{
>> + const u32 *tx_buf = msg->tx_buf;
>> + int len = msg->tx_len, ret;
>> + u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type);
>> +
>> + if (msg->tx_len < 3) {
>> + dev_err(dsi->dev, "wrong tx buf length %d for long write\n",
>> + msg->tx_len);
>> + return -EINVAL;
>> + }
>> +
>> + /* write the bulks */
>> + while (len / IMX_MIPI_DSI_PLD_DATA_BUF_SIZE) {
>> + dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
>> + tx_buf++;
>> + len -= IMX_MIPI_DSI_PLD_DATA_BUF_SIZE;
>
> This is confusing. Maybe a better option would be to use sizeof(*tx_buf)
> instead of this macro. You're effectively consuming one u32 with each
> write, irrespective of what the value of the macro is.
Ok. I'll use sizeof(*tx_buf).
>
>> + ret = check_status(dsi, DSI_CMD_PKT_STATUS, GEN_PLD_W_FULL,
>> + CMD_PKT_STATUS_TIMEOUT, STATUS_TO_CLEAR);
>> + if (ret < 0) {
>> + dev_err(dsi->dev, "failed to get avaliable "
>> + "write payload FIFO\n");
>> + return ret;
>> + }
>> + }
>> +
>> + /* write the remainder */
>> + if (len > 0) {
>> + ret = check_status(dsi, DSI_CMD_PKT_STATUS, GEN_PLD_W_FULL,
>> + CMD_PKT_STATUS_TIMEOUT, STATUS_TO_CLEAR);
>> + if (ret < 0) {
>> + dev_err(dsi->dev, "failed to get avaliable "
>> + "write payload FIFO\n");
>> + return ret;
>> + }
>> + dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
>> + }
>
> This is really duplicating what the above loop does. Why can't you do it
> in a single loop? Also the transmission buffer isn't guaranteed to be a
> multiple of 4 bytes, so you could have the situation where len > 0 and
> len < 4 and yet you'll be reading 4 bytes by doing *tx_buf and accessing
> memory that you shouldn't.
Good catch. I'll make it to be a single loop and avoid accessing memory
that I shouldn't in the next version.
>
>> + return imx_mipi_dsi_gen_pkt_hdr_write(dsi, val);
>> +}
>> +
>> +static ssize_t imx_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
>> + const struct mipi_dsi_msg *msg)
>> +{
>> + struct imx_mipi_dsi *dsi = host_to_dsi(host);
>> + int ret;
>> +
>> + switch (msg->type) {
>> + case MIPI_DSI_DCS_SHORT_WRITE:
>> + case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
>> + case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE:
>> + ret = imx_mipi_dsi_dcs_short_write(dsi, msg);
>> + break;
>> + case MIPI_DSI_DCS_LONG_WRITE:
>> + ret = imx_mipi_dsi_dcs_long_write(dsi, msg);
>> + break;
>> + default:
>> + dev_err(dsi->dev, "unsupported message type\n");
>> + ret = -EFAULT;
>
> EFAULT really isn't appropriate here.
I'll change it to EINVAL.
>
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +static const struct mipi_dsi_host_ops imx_mipi_dsi_host_ops = {
>> + .attach = imx_mipi_dsi_host_attach,
>> + .detach = imx_mipi_dsi_host_detach,
>> + .transfer = imx_mipi_dsi_host_transfer,
>> +};
>> +
>> +static enum drm_connector_status
>> +imx_mipi_dsi_detect(struct drm_connector *connector, bool force)
>> +{
>> + struct imx_mipi_dsi *dsi = con_to_dsi(connector);
>> +
>> + if (!dsi->panel) {
>> + dsi->panel = of_drm_find_panel(dsi->panel_node);
>> + if (dsi->panel)
>> + drm_panel_attach(dsi->panel, &dsi->connector);
>> + }
>> +
>> + if (dsi->panel)
>> + return connector_status_connected;
>> +
>> + return connector_status_disconnected;
>> +
>> +}
>
> You really shouldn't be doing that here. of_drm_find_panel() returning
> NULL should be considered cause for deferring probe. I suspect that the
> driver doesn't really handle that very well at all, though, since if it
> did you would've run into the same issue that Benjamin ran into this
> morning.
I'll return connector_status_disconnected directly here. Is it okay?
>
>> +static void imx_mipi_dsi_encoder_prepare(struct drm_encoder *encoder)
>> +{
>> + struct imx_mipi_dsi *dsi = enc_to_dsi(encoder);
>> + u32 interface_pix_fmt;
>> +
>> + switch (dsi->format) {
>> + case MIPI_DSI_FMT_RGB888:
>> + interface_pix_fmt = V4L2_PIX_FMT_RGB24;
>> + break;
>> + case MIPI_DSI_FMT_RGB565:
>> + interface_pix_fmt = V4L2_PIX_FMT_RGB565;
>> + break;
>> + default:
>> + dev_err(dsi->dev, "unsupported DSI pixel format\n");
>> + return;
>
> Why even try doing this if you know upfront that it can't be done. You
> know much earlier than this that you can't drive the pixel format, why
> not abort then?
>
> People are much more likely to notice that the panel isn't supported if
> the DSI output doesn't even show up (or doesn't expose any modes) rather
> than if they have to find this error message in dmesg.
Accepted. I'll check the dsi format compatibility in ->host_attach().
And, how about changing the default patch to be this?
default:
BUG();
return;
>
>> +static void imx_mipi_dsi_video_mode_config(struct imx_mipi_dsi *dsi)
>> +{
>> + u32 val;
>> +
>> + val = VID_MODE_TYPE_BURST_SYNC_PULSES | ENABLE_LOW_POWER;
>> +
>> + dsi_write(dsi, DSI_VID_MODE_CFG, val);
>> +}
>
> You probably want to parameterize based on the DSI peripheral's flags. I
> see that you do reject devices with any other modes, so this may not be
> necessary now. Out of curiosity, the hardware supports other modes,
> right?
Yes, the hardware supports other modes according to it's register
definition.
I prefer not to parameterize at present as I do reject devices with
other modes now.
>
> [...]
>> +MODULE_LICENSE("GPL v2");
>
> Sigh... according to your header comment this needs to be "GPL".
I'll change it to be "GPL".
Thanks,
Liu Ying
>
> Thierry
>
More information about the linux-arm-kernel
mailing list