diff mbox

[v3,3/7] drm/tinydrm: Add MIPI DBI support

Message ID 20170131160319.9695-4-noralf@tronnes.org (mailing list archive)
State New, archived
Headers show

Commit Message

Noralf Trønnes Jan. 31, 2017, 4:03 p.m. UTC
Add support for MIPI DBI compatible controllers.
Interface type C option 1 and 3 are supported (SPI).

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 Documentation/gpu/tinydrm.rst      |   12 +
 drivers/gpu/drm/tinydrm/Kconfig    |    3 +
 drivers/gpu/drm/tinydrm/Makefile   |    3 +
 drivers/gpu/drm/tinydrm/mipi-dbi.c | 1005 ++++++++++++++++++++++++++++++++++++
 include/drm/tinydrm/mipi-dbi.h     |  107 ++++
 5 files changed, 1130 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
 create mode 100644 include/drm/tinydrm/mipi-dbi.h

Comments

Thierry Reding Feb. 6, 2017, 8:48 a.m. UTC | #1
On Tue, Jan 31, 2017 at 05:03:15PM +0100, Noralf Trønnes wrote:
> Add support for MIPI DBI compatible controllers.
> Interface type C option 1 and 3 are supported (SPI).
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
>  Documentation/gpu/tinydrm.rst      |   12 +
>  drivers/gpu/drm/tinydrm/Kconfig    |    3 +
>  drivers/gpu/drm/tinydrm/Makefile   |    3 +
>  drivers/gpu/drm/tinydrm/mipi-dbi.c | 1005 ++++++++++++++++++++++++++++++++++++
>  include/drm/tinydrm/mipi-dbi.h     |  107 ++++
>  5 files changed, 1130 insertions(+)
>  create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
>  create mode 100644 include/drm/tinydrm/mipi-dbi.h

Any reason why this is in the tinydrm subdirectory? Looks like this
could be useful to drivers outside of it.

> diff --git a/drivers/gpu/drm/tinydrm/mipi-dbi.c b/drivers/gpu/drm/tinydrm/mipi-dbi.c
> new file mode 100644
> index 0000000..5ded299
> --- /dev/null
> +++ b/drivers/gpu/drm/tinydrm/mipi-dbi.c
> @@ -0,0 +1,1005 @@
> +/*
> + * MIPI Display Bus Interface (DBI) LCD controller support
> + *
> + * Copyright 2016 Noralf Trønnes
> + *
> + * 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.
> + */
> +
> +#include <drm/tinydrm/mipi-dbi.h>
> +#include <drm/tinydrm/tinydrm-helpers.h>
> +#include <linux/debugfs.h>
> +#include <linux/dma-buf.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/module.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>
> +#include <video/mipi_display.h>
> +
> +#define MIPI_DBI_MAX_SPI_READ_SPEED 2000000 /* 2MHz */
> +
> +#define DCS_POWER_MODE_DISPLAY			BIT(2)
> +#define DCS_POWER_MODE_DISPLAY_NORMAL_MODE	BIT(3)
> +#define DCS_POWER_MODE_SLEEP_MODE		BIT(4)
> +#define DCS_POWER_MODE_PARTIAL_MODE		BIT(5)
> +#define DCS_POWER_MODE_IDLE_MODE		BIT(6)
> +#define DCS_POWER_MODE_RESERVED_MASK		(BIT(0) | BIT(1) | BIT(7))

Should these perhaps be defined in include/video/mipi_display.h since
that already defines the MIPI_DCS_GET_POWER_MODE that this is used with?

> +/**
> + * mipi_dbi_command - MIPI DCS command with optional parameter(s)
> + * @mipi: MIPI structure
> + * @cmd: Command
> + * @seq...: Optional parameter(s)
> + *
> + * Send MIPI DCS command to the controller. Use mipi_dbi_command_read() for
> + * get/read.
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +#define mipi_dbi_command(mipi, cmd, seq...) \
> +({ \
> +	u8 d[] = { seq }; \
> +	mipi_dbi_command_buf(mipi, cmd, d, ARRAY_SIZE(d)); \
> +})

I feel obligated to object to this because I objected to the same macro
when Andrzej wanted to add the same macro for MIPI DSI. But since I'm
apparently the only one that doesn't like this, maybe it's time for me
to embrace this.

Thierry
Jani Nikula Feb. 6, 2017, 11:25 a.m. UTC | #2
On Tue, 31 Jan 2017, Noralf Trønnes <noralf@tronnes.org> wrote:
> +static bool mipi_dbi_command_is_read(struct mipi_dbi *mipi, u8 cmd)

Okay this is one giant bikeshedding nitpick, but here goes anyway. Why
do you call MIPI DBI just "mipi", here and all around? Why not
"mipi_dbi" or "dbi"? We never talk about "vesa" displays or use that as
a shorthand when we refer to Display Port either.

BR,
Jani.
Jani Nikula Feb. 6, 2017, 11:30 a.m. UTC | #3
On Mon, 06 Feb 2017, Thierry Reding <thierry.reding@gmail.com> wrote:
> On Tue, Jan 31, 2017 at 05:03:15PM +0100, Noralf Trønnes wrote:
>> Add support for MIPI DBI compatible controllers.
>> Interface type C option 1 and 3 are supported (SPI).
>> 
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>> ---
>>  Documentation/gpu/tinydrm.rst      |   12 +
>>  drivers/gpu/drm/tinydrm/Kconfig    |    3 +
>>  drivers/gpu/drm/tinydrm/Makefile   |    3 +
>>  drivers/gpu/drm/tinydrm/mipi-dbi.c | 1005 ++++++++++++++++++++++++++++++++++++
>>  include/drm/tinydrm/mipi-dbi.h     |  107 ++++
>>  5 files changed, 1130 insertions(+)
>>  create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
>>  create mode 100644 include/drm/tinydrm/mipi-dbi.h
>
> Any reason why this is in the tinydrm subdirectory? Looks like this
> could be useful to drivers outside of it.
>
>> diff --git a/drivers/gpu/drm/tinydrm/mipi-dbi.c b/drivers/gpu/drm/tinydrm/mipi-dbi.c
>> new file mode 100644
>> index 0000000..5ded299
>> --- /dev/null
>> +++ b/drivers/gpu/drm/tinydrm/mipi-dbi.c
>> @@ -0,0 +1,1005 @@
>> +/*
>> + * MIPI Display Bus Interface (DBI) LCD controller support
>> + *
>> + * Copyright 2016 Noralf Trønnes
>> + *
>> + * 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.
>> + */
>> +
>> +#include <drm/tinydrm/mipi-dbi.h>
>> +#include <drm/tinydrm/tinydrm-helpers.h>
>> +#include <linux/debugfs.h>
>> +#include <linux/dma-buf.h>
>> +#include <linux/gpio/consumer.h>
>> +#include <linux/module.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/spi/spi.h>
>> +#include <video/mipi_display.h>
>> +
>> +#define MIPI_DBI_MAX_SPI_READ_SPEED 2000000 /* 2MHz */
>> +
>> +#define DCS_POWER_MODE_DISPLAY			BIT(2)
>> +#define DCS_POWER_MODE_DISPLAY_NORMAL_MODE	BIT(3)
>> +#define DCS_POWER_MODE_SLEEP_MODE		BIT(4)
>> +#define DCS_POWER_MODE_PARTIAL_MODE		BIT(5)
>> +#define DCS_POWER_MODE_IDLE_MODE		BIT(6)
>> +#define DCS_POWER_MODE_RESERVED_MASK		(BIT(0) | BIT(1) | BIT(7))
>
> Should these perhaps be defined in include/video/mipi_display.h since
> that already defines the MIPI_DCS_GET_POWER_MODE that this is used with?

Agreed.

>
>> +/**
>> + * mipi_dbi_command - MIPI DCS command with optional parameter(s)
>> + * @mipi: MIPI structure
>> + * @cmd: Command
>> + * @seq...: Optional parameter(s)
>> + *
>> + * Send MIPI DCS command to the controller. Use mipi_dbi_command_read() for
>> + * get/read.
>> + *
>> + * Returns:
>> + * Zero on success, negative error code on failure.
>> + */
>> +#define mipi_dbi_command(mipi, cmd, seq...) \
>> +({ \
>> +	u8 d[] = { seq }; \
>> +	mipi_dbi_command_buf(mipi, cmd, d, ARRAY_SIZE(d)); \
>> +})
>
> I feel obligated to object to this because I objected to the same macro
> when Andrzej wanted to add the same macro for MIPI DSI. But since I'm
> apparently the only one that doesn't like this, maybe it's time for me
> to embrace this.

I think that's less interesting for MIPI DSI now that we have specific
functions for most standard DCS commands anyway. That also avoids the
need to have a list of read functions like here.

I guess the question is if it feels like too much duplication to have
the DCS commands as functions also for DBI. And whether it's worth
trying to deduplicate DSI and DBI in this case, or if it gets just too
confusing/complicated.

BR,
Jani.


>
> Thierry
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
Thierry Reding Feb. 6, 2017, 11:53 a.m. UTC | #4
On Mon, Feb 06, 2017 at 01:30:09PM +0200, Jani Nikula wrote:
> On Mon, 06 Feb 2017, Thierry Reding <thierry.reding@gmail.com> wrote:
> > On Tue, Jan 31, 2017 at 05:03:15PM +0100, Noralf Trønnes wrote:
> >> Add support for MIPI DBI compatible controllers.
> >> Interface type C option 1 and 3 are supported (SPI).
> >> 
> >> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> >> ---
> >>  Documentation/gpu/tinydrm.rst      |   12 +
> >>  drivers/gpu/drm/tinydrm/Kconfig    |    3 +
> >>  drivers/gpu/drm/tinydrm/Makefile   |    3 +
> >>  drivers/gpu/drm/tinydrm/mipi-dbi.c | 1005 ++++++++++++++++++++++++++++++++++++
> >>  include/drm/tinydrm/mipi-dbi.h     |  107 ++++
> >>  5 files changed, 1130 insertions(+)
> >>  create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
> >>  create mode 100644 include/drm/tinydrm/mipi-dbi.h
> >
> > Any reason why this is in the tinydrm subdirectory? Looks like this
> > could be useful to drivers outside of it.
> >
> >> diff --git a/drivers/gpu/drm/tinydrm/mipi-dbi.c b/drivers/gpu/drm/tinydrm/mipi-dbi.c
> >> new file mode 100644
> >> index 0000000..5ded299
> >> --- /dev/null
> >> +++ b/drivers/gpu/drm/tinydrm/mipi-dbi.c
> >> @@ -0,0 +1,1005 @@
> >> +/*
> >> + * MIPI Display Bus Interface (DBI) LCD controller support
> >> + *
> >> + * Copyright 2016 Noralf Trønnes
> >> + *
> >> + * 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.
> >> + */
> >> +
> >> +#include <drm/tinydrm/mipi-dbi.h>
> >> +#include <drm/tinydrm/tinydrm-helpers.h>
> >> +#include <linux/debugfs.h>
> >> +#include <linux/dma-buf.h>
> >> +#include <linux/gpio/consumer.h>
> >> +#include <linux/module.h>
> >> +#include <linux/regulator/consumer.h>
> >> +#include <linux/spi/spi.h>
> >> +#include <video/mipi_display.h>
> >> +
> >> +#define MIPI_DBI_MAX_SPI_READ_SPEED 2000000 /* 2MHz */
> >> +
> >> +#define DCS_POWER_MODE_DISPLAY			BIT(2)
> >> +#define DCS_POWER_MODE_DISPLAY_NORMAL_MODE	BIT(3)
> >> +#define DCS_POWER_MODE_SLEEP_MODE		BIT(4)
> >> +#define DCS_POWER_MODE_PARTIAL_MODE		BIT(5)
> >> +#define DCS_POWER_MODE_IDLE_MODE		BIT(6)
> >> +#define DCS_POWER_MODE_RESERVED_MASK		(BIT(0) | BIT(1) | BIT(7))
> >
> > Should these perhaps be defined in include/video/mipi_display.h since
> > that already defines the MIPI_DCS_GET_POWER_MODE that this is used with?
> 
> Agreed.
> 
> >
> >> +/**
> >> + * mipi_dbi_command - MIPI DCS command with optional parameter(s)
> >> + * @mipi: MIPI structure
> >> + * @cmd: Command
> >> + * @seq...: Optional parameter(s)
> >> + *
> >> + * Send MIPI DCS command to the controller. Use mipi_dbi_command_read() for
> >> + * get/read.
> >> + *
> >> + * Returns:
> >> + * Zero on success, negative error code on failure.
> >> + */
> >> +#define mipi_dbi_command(mipi, cmd, seq...) \
> >> +({ \
> >> +	u8 d[] = { seq }; \
> >> +	mipi_dbi_command_buf(mipi, cmd, d, ARRAY_SIZE(d)); \
> >> +})
> >
> > I feel obligated to object to this because I objected to the same macro
> > when Andrzej wanted to add the same macro for MIPI DSI. But since I'm
> > apparently the only one that doesn't like this, maybe it's time for me
> > to embrace this.
> 
> I think that's less interesting for MIPI DSI now that we have specific
> functions for most standard DCS commands anyway. That also avoids the
> need to have a list of read functions like here.
> 
> I guess the question is if it feels like too much duplication to have
> the DCS commands as functions also for DBI. And whether it's worth
> trying to deduplicate DSI and DBI in this case, or if it gets just too
> confusing/complicated.

The problem with this is that we can't possible have all accesses
covered by specific functions. Most of the calls using this function or
macro are for panel-specific commands that often take an arbitrary
number of arguments.

I personally think that Maxime's implementation for ST7789V is somewhat
nicer, though it would require some more work for DSI and DBI to have it
all stashed into a single buffer for the transfer.

Anyway, my main concern with these is that we need to allow for proper
error handling, and the above correctly propagates error codes, so the
matter that it's a macro with a variable argument list and might store
potentially huge arrays on the stack could be addressed when they
become really problematic, rather than just annoying me because they
aren't pretty.

Thierry
Andrzej Hajda Feb. 6, 2017, 12:34 p.m. UTC | #5
On 06.02.2017 12:53, Thierry Reding wrote:
> On Mon, Feb 06, 2017 at 01:30:09PM +0200, Jani Nikula wrote:
>> On Mon, 06 Feb 2017, Thierry Reding <thierry.reding@gmail.com> wrote:
>>> On Tue, Jan 31, 2017 at 05:03:15PM +0100, Noralf Trønnes wrote:
>>>> Add support for MIPI DBI compatible controllers.
>>>> Interface type C option 1 and 3 are supported (SPI).
>>>>
>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>>>> ---
>>>>  Documentation/gpu/tinydrm.rst      |   12 +
>>>>  drivers/gpu/drm/tinydrm/Kconfig    |    3 +
>>>>  drivers/gpu/drm/tinydrm/Makefile   |    3 +
>>>>  drivers/gpu/drm/tinydrm/mipi-dbi.c | 1005 ++++++++++++++++++++++++++++++++++++
>>>>  include/drm/tinydrm/mipi-dbi.h     |  107 ++++
>>>>  5 files changed, 1130 insertions(+)
>>>>  create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
>>>>  create mode 100644 include/drm/tinydrm/mipi-dbi.h
>>> Any reason why this is in the tinydrm subdirectory? Looks like this
>>> could be useful to drivers outside of it.
>>>
>>>> diff --git a/drivers/gpu/drm/tinydrm/mipi-dbi.c b/drivers/gpu/drm/tinydrm/mipi-dbi.c
>>>> new file mode 100644
>>>> index 0000000..5ded299
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/tinydrm/mipi-dbi.c
>>>> @@ -0,0 +1,1005 @@
>>>> +/*
>>>> + * MIPI Display Bus Interface (DBI) LCD controller support
>>>> + *
>>>> + * Copyright 2016 Noralf Trønnes
>>>> + *
>>>> + * 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.
>>>> + */
>>>> +
>>>> +#include <drm/tinydrm/mipi-dbi.h>
>>>> +#include <drm/tinydrm/tinydrm-helpers.h>
>>>> +#include <linux/debugfs.h>
>>>> +#include <linux/dma-buf.h>
>>>> +#include <linux/gpio/consumer.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/regulator/consumer.h>
>>>> +#include <linux/spi/spi.h>
>>>> +#include <video/mipi_display.h>
>>>> +
>>>> +#define MIPI_DBI_MAX_SPI_READ_SPEED 2000000 /* 2MHz */
>>>> +
>>>> +#define DCS_POWER_MODE_DISPLAY			BIT(2)
>>>> +#define DCS_POWER_MODE_DISPLAY_NORMAL_MODE	BIT(3)
>>>> +#define DCS_POWER_MODE_SLEEP_MODE		BIT(4)
>>>> +#define DCS_POWER_MODE_PARTIAL_MODE		BIT(5)
>>>> +#define DCS_POWER_MODE_IDLE_MODE		BIT(6)
>>>> +#define DCS_POWER_MODE_RESERVED_MASK		(BIT(0) | BIT(1) | BIT(7))
>>> Should these perhaps be defined in include/video/mipi_display.h since
>>> that already defines the MIPI_DCS_GET_POWER_MODE that this is used with?
>> Agreed.
>>
>>>> +/**
>>>> + * mipi_dbi_command - MIPI DCS command with optional parameter(s)
>>>> + * @mipi: MIPI structure
>>>> + * @cmd: Command
>>>> + * @seq...: Optional parameter(s)
>>>> + *
>>>> + * Send MIPI DCS command to the controller. Use mipi_dbi_command_read() for
>>>> + * get/read.
>>>> + *
>>>> + * Returns:
>>>> + * Zero on success, negative error code on failure.
>>>> + */
>>>> +#define mipi_dbi_command(mipi, cmd, seq...) \
>>>> +({ \
>>>> +	u8 d[] = { seq }; \
>>>> +	mipi_dbi_command_buf(mipi, cmd, d, ARRAY_SIZE(d)); \
>>>> +})
>>> I feel obligated to object to this because I objected to the same macro
>>> when Andrzej wanted to add the same macro for MIPI DSI. But since I'm
>>> apparently the only one that doesn't like this, maybe it's time for me
>>> to embrace this.
>> I think that's less interesting for MIPI DSI now that we have specific
>> functions for most standard DCS commands anyway. That also avoids the
>> need to have a list of read functions like here.
>>
>> I guess the question is if it feels like too much duplication to have
>> the DCS commands as functions also for DBI. And whether it's worth
>> trying to deduplicate DSI and DBI in this case, or if it gets just too
>> confusing/complicated.
> The problem with this is that we can't possible have all accesses
> covered by specific functions. Most of the calls using this function or
> macro are for panel-specific commands that often take an arbitrary
> number of arguments.
>
> I personally think that Maxime's implementation for ST7789V is somewhat
> nicer, though it would require some more work for DSI and DBI to have it
> all stashed into a single buffer for the transfer.
>
> Anyway, my main concern with these is that we need to allow for proper
> error handling, and the above correctly propagates error codes, so the
> matter that it's a macro with a variable argument list and might store
> potentially huge arrays on the stack could be addressed when they
> become really problematic, rather than just annoying me because they
> aren't pretty.

You can always add compile time check for sequence length to the macro
[1]. Other thing is that sequence is often constant, in such case, to
save few bits of space, it can be stored in 'static const' array, but
for this array passed to mipi_dbi_command_buf should be constant. And of
course it requires another macro.

[1]:
http://lxr.free-electrons.com/source/drivers/gpu/drm/panel/panel-samsung-s6e8aa0.c?v=4.4#L169

Regards
Andrzej

>
> Thierry
>
>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
Noralf Trønnes Feb. 6, 2017, 3:45 p.m. UTC | #6
(Adding Maxime)

Den 06.02.2017 13.34, skrev Andrzej Hajda:
> On 06.02.2017 12:53, Thierry Reding wrote:
>> On Mon, Feb 06, 2017 at 01:30:09PM +0200, Jani Nikula wrote:
>>> On Mon, 06 Feb 2017, Thierry Reding <thierry.reding@gmail.com> wrote:
>>>> On Tue, Jan 31, 2017 at 05:03:15PM +0100, Noralf Trønnes wrote:
>>>>> Add support for MIPI DBI compatible controllers.
>>>>> Interface type C option 1 and 3 are supported (SPI).
>>>>>
>>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>>>>> ---
>>>>>   Documentation/gpu/tinydrm.rst      |   12 +
>>>>>   drivers/gpu/drm/tinydrm/Kconfig    |    3 +
>>>>>   drivers/gpu/drm/tinydrm/Makefile   |    3 +
>>>>>   drivers/gpu/drm/tinydrm/mipi-dbi.c | 1005 ++++++++++++++++++++++++++++++++++++
>>>>>   include/drm/tinydrm/mipi-dbi.h     |  107 ++++
>>>>>   5 files changed, 1130 insertions(+)
>>>>>   create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
>>>>>   create mode 100644 include/drm/tinydrm/mipi-dbi.h
>>>> Any reason why this is in the tinydrm subdirectory? Looks like this
>>>> could be useful to drivers outside of it.

I did consider having it outside, but I couldn't find any users in drm
that could benefit from it (there is one backlight driver).
But now there's Maxime's panel driver.

How about something like this:
(I have not included the framebuffer dirty function since it will only
be used in tinydrm anyway)

include/drm/drm_mipi_dbi.h:

struct mipi_dbi_device;

/**
  * mipi_dbi_dcs_write - MIPI DCS command with optional parameter(s)
  * @dbi: MIPI DBI structure
  * @cmd: Command
  * @seq...: Optional parameter(s)
  *
  * Send MIPI DCS command to the controller. Use 
mipi_dbi_dcs_read_buffer() for
  * get/read commands.
  *
  * Returns:
  * Zero on success, negative error code on failure.
  */
#define mipi_dbi_dcs_write(dbi, cmd, seq...) \
({ \
     const u8 d[] = { seq }; \
     BUILD_BUG_ON_MSG(ARRAY_SIZE(d) > 64, "DCS sequence too big for 
stack");\
     mipi_dbi_dcs_write_buffer(dbi, cmd, d, ARRAY_SIZE(d)); \
})

...

drivers/gpu/drm/drm_mipi_dbi.c:

struct mipi_dbi_device {
     struct mutex lock;
     int (*write)(struct mipi_dbi_device *dbi, u8 cmd, const u8 *par,
              size_t num);
     int (*read)(struct mipi_dbi_device *dbi, u8 cmd, u8 *par, size_t num);
     bool swap16;
};

/* MIPI DBI Type C options 1 and 3 */
struct mipi_dbi_spi_device {
     struct mipi_dbi_device dbi;
     struct spi_device *spi;
     struct gpio_desc *dc;
};

/*
  * MIPI DBI Type B - Intel 8080 type parallel bus
  * I need this to fully convert staging/fbtft to drm
  */
struct mipi_dbi_i80_device {
     struct mipi_dbi_device dbi;
     struct device *dev;
     struct gpio_desc *cs;
     struct gpio_desc *dc;
     struct gpio_desc *wr;
     struct gpio_descs *db;
};

/**
  * mipi_dbi_get_swap16 - Is byteswapping 16-bit pixel data needed?
  * @dbi: MIPI DBI device
  *
  * Byte swapping 16-bit pixel data is necessary if the bus can't 
support 16-bit
  * big endian transfers (e.g. if SPI can only do 8-bit and the machine is
  * little endian). This applies to the MIPI_DCS_WRITE_MEMORY_START command.
  *
  * Returns:
  * True if it's neccesary to swap bytes, false otherwise.
  */
bool mipi_dbi_get_swap16(struct mipi_dbi_device *dbi)
{
     return dbi->swap16;
}

/**
  * mipi_dbi_spi_init - Initialize MIPI DBI SPI device
  * @spi: SPI device
  * @dc: D/C gpio (optional)
  * @writeonly: True if it's not possible to read from the controller.
  *
  * If @dc is set, a Type C Option 3 interface is assumed, if not
  * Type C Option 1 (9-bit).
  *
  * If the SPI master driver doesn't support the necessary bits per word,
  * the following transformation is used:
  *
  * - 9-bit: reorder buffer as 9x 8-bit words, padded with no-op command.
  * - 16-bit (pixel data): if machine is big endian send as 8-bit, if little
  *   endian the user is responsible for swapping the bytes.
  *   See mipi_dbi_get_swap_pixel_bytes().
  *
  * Returns:
  * Pointer to &mipi_dbi_device on success, ERR_PTR on failure.
  */
struct mipi_dbi_device *mipi_dbi_spi_init(struct spi_device *spi,
                       struct gpio_desc *dc, bool writeonly)
{
...
}

struct mipi_dbi_device *mipi_dbi_i80_init(...)


Noralf.
diff mbox

Patch

diff --git a/Documentation/gpu/tinydrm.rst b/Documentation/gpu/tinydrm.rst
index fb256d2..a913644 100644
--- a/Documentation/gpu/tinydrm.rst
+++ b/Documentation/gpu/tinydrm.rst
@@ -28,3 +28,15 @@  Additional helpers
 
 .. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
    :export:
+
+MIPI DBI Compatible Controllers
+===============================
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/mipi-dbi.c
+   :doc: overview
+
+.. kernel-doc:: include/drm/tinydrm/mipi-dbi.h
+   :internal:
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/mipi-dbi.c
+   :export:
diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index ffb873f..128d2f3 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -6,3 +6,6 @@  menuconfig DRM_TINYDRM
 	help
 	  Choose this option if you have a tinydrm supported display.
 	  If M is selected the module will be called tinydrm.
+
+config TINYDRM_MIPI_DBI
+	tristate
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
index 7476ed1..fe5d4c6 100644
--- a/drivers/gpu/drm/tinydrm/Makefile
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -1 +1,4 @@ 
 obj-$(CONFIG_DRM_TINYDRM)		+= core/
+
+# Controllers
+obj-$(CONFIG_TINYDRM_MIPI_DBI)		+= mipi-dbi.o
diff --git a/drivers/gpu/drm/tinydrm/mipi-dbi.c b/drivers/gpu/drm/tinydrm/mipi-dbi.c
new file mode 100644
index 0000000..5ded299
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/mipi-dbi.c
@@ -0,0 +1,1005 @@ 
+/*
+ * MIPI Display Bus Interface (DBI) LCD controller support
+ *
+ * Copyright 2016 Noralf Trønnes
+ *
+ * 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.
+ */
+
+#include <drm/tinydrm/mipi-dbi.h>
+#include <drm/tinydrm/tinydrm-helpers.h>
+#include <linux/debugfs.h>
+#include <linux/dma-buf.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <video/mipi_display.h>
+
+#define MIPI_DBI_MAX_SPI_READ_SPEED 2000000 /* 2MHz */
+
+#define DCS_POWER_MODE_DISPLAY			BIT(2)
+#define DCS_POWER_MODE_DISPLAY_NORMAL_MODE	BIT(3)
+#define DCS_POWER_MODE_SLEEP_MODE		BIT(4)
+#define DCS_POWER_MODE_PARTIAL_MODE		BIT(5)
+#define DCS_POWER_MODE_IDLE_MODE		BIT(6)
+#define DCS_POWER_MODE_RESERVED_MASK		(BIT(0) | BIT(1) | BIT(7))
+
+/**
+ * DOC: overview
+ *
+ * This library provides helpers for MIPI Display Bus Interface (DBI)
+ * compatible display controllers.
+ *
+ * Many controllers for tiny lcd displays are MIPI compliant and can use this
+ * library. If a controller uses registers 0x2A and 0x2B to set the area to
+ * update and uses register 0x2C to write to frame memory, it is most likely
+ * MIPI compliant.
+ *
+ * Only MIPI Type 1 displays are supported since a full frame memory is needed.
+ *
+ * There are 3 MIPI DBI implementation types:
+ *
+ * A. Motorola 6800 type parallel bus
+ *
+ * B. Intel 8080 type parallel bus
+ *
+ * C. SPI type with 3 options:
+ *
+ *    1. 9-bit with the Data/Command signal as the ninth bit
+ *    2. Same as above except it's sent as 16 bits
+ *    3. 8-bit with the Data/Command signal as a separate D/CX pin
+ *
+ * Currently mipi_dbi only supports Type C options 1 and 3 with
+ * mipi_dbi_spi_init().
+ */
+
+#define MIPI_DBI_DEBUG_COMMAND(cmd, data, len) \
+({ \
+	if (!len) \
+		DRM_DEBUG_DRIVER("cmd=%02x\n", cmd); \
+	else if (len <= 32) \
+		DRM_DEBUG_DRIVER("cmd=%02x, par=%*ph\n", cmd, len, data); \
+	else \
+		DRM_DEBUG_DRIVER("cmd=%02x, len=%zu\n", cmd, len); \
+})
+
+static const u8 mipi_dbi_dcs_read_commands[] = {
+	MIPI_DCS_GET_DISPLAY_ID,
+	MIPI_DCS_GET_RED_CHANNEL,
+	MIPI_DCS_GET_GREEN_CHANNEL,
+	MIPI_DCS_GET_BLUE_CHANNEL,
+	MIPI_DCS_GET_DISPLAY_STATUS,
+	MIPI_DCS_GET_POWER_MODE,
+	MIPI_DCS_GET_ADDRESS_MODE,
+	MIPI_DCS_GET_PIXEL_FORMAT,
+	MIPI_DCS_GET_DISPLAY_MODE,
+	MIPI_DCS_GET_SIGNAL_MODE,
+	MIPI_DCS_GET_DIAGNOSTIC_RESULT,
+	MIPI_DCS_READ_MEMORY_START,
+	MIPI_DCS_READ_MEMORY_CONTINUE,
+	MIPI_DCS_GET_SCANLINE,
+	MIPI_DCS_GET_DISPLAY_BRIGHTNESS,
+	MIPI_DCS_GET_CONTROL_DISPLAY,
+	MIPI_DCS_GET_POWER_SAVE,
+	MIPI_DCS_GET_CABC_MIN_BRIGHTNESS,
+	MIPI_DCS_READ_DDB_START,
+	MIPI_DCS_READ_DDB_CONTINUE,
+	0, /* sentinel */
+};
+
+static bool mipi_dbi_command_is_read(struct mipi_dbi *mipi, u8 cmd)
+{
+	unsigned int i;
+
+	if (!mipi->read_commands)
+		return false;
+
+	for (i = 0; i < 0xff; i++) {
+		if (!mipi->read_commands[i])
+			return false;
+		if (cmd == mipi->read_commands[i])
+			return true;
+	}
+
+	return false;
+}
+
+/**
+ * mipi_dbi_command_read - MIPI DCS read command
+ * @mipi: MIPI structure
+ * @cmd: Command
+ * @val: Value read
+ *
+ * Send MIPI DCS read command to the controller.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_command_read(struct mipi_dbi *mipi, u8 cmd, u8 *val)
+{
+	if (!mipi->read_commands)
+		return -EACCES;
+
+	if (!mipi_dbi_command_is_read(mipi, cmd))
+		return -EINVAL;
+
+	return mipi_dbi_command_buf(mipi, cmd, val, 1);
+}
+EXPORT_SYMBOL(mipi_dbi_command_read);
+
+/**
+ * mipi_dbi_command_buf - MIPI DCS command with parameter(s) in an array
+ * @mipi: MIPI structure
+ * @cmd: Command
+ * @data: Parameter buffer
+ * @len: Buffer length
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_command_buf(struct mipi_dbi *mipi, u8 cmd, u8 *data, size_t len)
+{
+	int ret;
+
+	mutex_lock(&mipi->cmdlock);
+	ret = mipi->command(mipi, cmd, data, len);
+	mutex_unlock(&mipi->cmdlock);
+
+	return ret;
+}
+EXPORT_SYMBOL(mipi_dbi_command_buf);
+
+static int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb,
+				struct drm_clip_rect *clip, bool swap)
+{
+	struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+	struct dma_buf_attachment *import_attach = cma_obj->base.import_attach;
+	struct drm_format_name_buf format_name;
+	void *src = cma_obj->vaddr;
+	int ret = 0;
+
+	if (import_attach) {
+		ret = dma_buf_begin_cpu_access(import_attach->dmabuf,
+					       DMA_FROM_DEVICE);
+		if (ret)
+			return ret;
+	}
+
+	switch (fb->format->format) {
+	case DRM_FORMAT_RGB565:
+		if (swap)
+			tinydrm_swab16(dst, src, fb, clip);
+		else
+			tinydrm_memcpy(dst, src, fb, clip);
+		break;
+	case DRM_FORMAT_XRGB8888:
+		tinydrm_xrgb8888_to_rgb565(dst, src, fb, clip, swap);
+		break;
+	default:
+		dev_err_once(fb->dev->dev, "Format is not supported: %s\n",
+			     drm_get_format_name(fb->format->format,
+						 &format_name));
+		return -EINVAL;
+	}
+
+	if (import_attach)
+		ret = dma_buf_end_cpu_access(import_attach->dmabuf,
+					     DMA_FROM_DEVICE);
+	return ret;
+}
+
+static int mipi_dbi_fb_dirty(struct drm_framebuffer *fb,
+			     struct drm_file *file_priv,
+			     unsigned int flags, unsigned int color,
+			     struct drm_clip_rect *clips,
+			     unsigned int num_clips)
+{
+	struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+	struct tinydrm_device *tdev = fb->dev->dev_private;
+	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
+	bool swap = mipi->swap_bytes;
+	struct drm_clip_rect clip;
+	int ret = 0;
+	bool full;
+	void *tr;
+
+	mutex_lock(&tdev->dirty_lock);
+
+	if (!mipi->enabled)
+		goto out_unlock;
+
+	/* fbdev can flush even when we're not interested */
+	if (tdev->pipe.plane.fb != fb)
+		goto out_unlock;
+
+	full = tinydrm_merge_clips(&clip, clips, num_clips, flags,
+				   fb->width, fb->height);
+
+	DRM_DEBUG("Flushing [FB:%d] x1=%u, x2=%u, y1=%u, y2=%u\n", fb->base.id,
+		  clip.x1, clip.x2, clip.y1, clip.y2);
+
+	if (!mipi->dc || !full || swap ||
+	    fb->format->format == DRM_FORMAT_XRGB8888) {
+		tr = mipi->tx_buf;
+		ret = mipi_dbi_buf_copy(mipi->tx_buf, fb, &clip, swap);
+		if (ret)
+			goto out_unlock;
+	} else {
+		tr = cma_obj->vaddr;
+	}
+
+	mipi_dbi_command(mipi, MIPI_DCS_SET_COLUMN_ADDRESS,
+			 (clip.x1 >> 8) & 0xFF, clip.x1 & 0xFF,
+			 (clip.x2 >> 8) & 0xFF, (clip.x2 - 1) & 0xFF);
+	mipi_dbi_command(mipi, MIPI_DCS_SET_PAGE_ADDRESS,
+			 (clip.y1 >> 8) & 0xFF, clip.y1 & 0xFF,
+			 (clip.y2 >> 8) & 0xFF, (clip.y2 - 1) & 0xFF);
+
+	ret = mipi_dbi_command_buf(mipi, MIPI_DCS_WRITE_MEMORY_START, tr,
+				(clip.x2 - clip.x1) * (clip.y2 - clip.y1) * 2);
+
+out_unlock:
+	mutex_unlock(&tdev->dirty_lock);
+
+	if (ret)
+		dev_err_once(fb->dev->dev, "Failed to update display %d\n",
+			     ret);
+
+	return ret;
+}
+
+static const struct drm_framebuffer_funcs mipi_dbi_fb_funcs = {
+	.destroy	= drm_fb_cma_destroy,
+	.create_handle	= drm_fb_cma_create_handle,
+	.dirty		= mipi_dbi_fb_dirty,
+};
+
+/**
+ * mipi_dbi_pipe_enable - MIPI DBI pipe enable helper
+ * @pipe: Display pipe
+ * @crtc_state: CRTC state
+ *
+ * This function enables backlight. Drivers can use this as their
+ * &drm_simple_display_pipe_funcs->enable callback.
+ */
+void mipi_dbi_pipe_enable(struct drm_simple_display_pipe *pipe,
+			  struct drm_crtc_state *crtc_state)
+{
+	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
+	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
+	struct drm_framebuffer *fb = pipe->plane.fb;
+
+	DRM_DEBUG_KMS("\n");
+
+	mipi->enabled = true;
+	if (fb)
+		fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
+
+	tinydrm_enable_backlight(mipi->backlight);
+}
+EXPORT_SYMBOL(mipi_dbi_pipe_enable);
+
+static void mipi_dbi_blank(struct mipi_dbi *mipi)
+{
+	struct drm_device *drm = mipi->tinydrm.drm;
+	u16 height = drm->mode_config.min_height;
+	u16 width = drm->mode_config.min_width;
+	size_t len = width * height * 2;
+
+	memset(mipi->tx_buf, 0, len);
+
+	mipi_dbi_command(mipi, MIPI_DCS_SET_COLUMN_ADDRESS, 0, 0,
+			 (width >> 8) & 0xFF, (width - 1) & 0xFF);
+	mipi_dbi_command(mipi, MIPI_DCS_SET_PAGE_ADDRESS, 0, 0,
+			 (height >> 8) & 0xFF, (height - 1) & 0xFF);
+	mipi_dbi_command_buf(mipi, MIPI_DCS_WRITE_MEMORY_START,
+			     (u8 *)mipi->tx_buf, len);
+}
+
+/**
+ * mipi_dbi_pipe_disable - MIPI DBI pipe disable helper
+ * @pipe: Display pipe
+ *
+ * This function disables backlight if present or if not the
+ * display memory is blanked. Drivers can use this as their
+ * &drm_simple_display_pipe_funcs->disable callback.
+ */
+void mipi_dbi_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
+	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
+
+	DRM_DEBUG_KMS("\n");
+
+	mipi->enabled = false;
+
+	if (mipi->backlight)
+		tinydrm_disable_backlight(mipi->backlight);
+	else
+		mipi_dbi_blank(mipi);
+}
+EXPORT_SYMBOL(mipi_dbi_pipe_disable);
+
+static const uint32_t mipi_dbi_formats[] = {
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_XRGB8888,
+};
+
+/**
+ * mipi_dbi_init - MIPI DBI initialization
+ * @dev: Parent device
+ * @mipi: &mipi_dbi structure to initialize
+ * @pipe_funcs: Display pipe functions
+ * @driver: DRM driver
+ * @mode: Display mode
+ * @rotation: Initial rotation in degrees Counter Clock Wise
+ *
+ * This function initializes a &mipi_dbi structure and it's underlying
+ * @tinydrm_device. It also sets up the display pipeline.
+ *
+ * Supported formats: Native RGB565 and emulated XRGB8888.
+ *
+ * Objects created by this function will be automatically freed on driver
+ * detach (devres).
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_init(struct device *dev, struct mipi_dbi *mipi,
+		  const struct drm_simple_display_pipe_funcs *pipe_funcs,
+		  struct drm_driver *driver,
+		  const struct drm_display_mode *mode, unsigned int rotation)
+{
+	size_t bufsize = mode->vdisplay * mode->hdisplay * sizeof(u16);
+	struct tinydrm_device *tdev = &mipi->tinydrm;
+	int ret;
+
+	if (!mipi->command)
+		return -EINVAL;
+
+	mutex_init(&mipi->cmdlock);
+
+	mipi->tx_buf = devm_kmalloc(dev, bufsize, GFP_KERNEL);
+	if (!mipi->tx_buf)
+		return -ENOMEM;
+
+	ret = devm_tinydrm_init(dev, tdev, &mipi_dbi_fb_funcs, driver);
+	if (ret)
+		return ret;
+
+	/* TODO: Maybe add DRM_MODE_CONNECTOR_SPI */
+	ret = tinydrm_display_pipe_init(tdev, pipe_funcs,
+					DRM_MODE_CONNECTOR_VIRTUAL,
+					mipi_dbi_formats,
+					ARRAY_SIZE(mipi_dbi_formats), mode,
+					rotation);
+	if (ret)
+		return ret;
+
+	tdev->drm->mode_config.preferred_depth = 16;
+	mipi->rotation = rotation;
+
+	drm_mode_config_reset(tdev->drm);
+
+	DRM_DEBUG_KMS("preferred_depth=%u, rotation = %u\n",
+		      tdev->drm->mode_config.preferred_depth, rotation);
+
+	return 0;
+}
+EXPORT_SYMBOL(mipi_dbi_init);
+
+/**
+ * mipi_dbi_hw_reset - Hardware reset of controller
+ * @mipi: MIPI DBI structure
+ *
+ * Reset controller if the &mipi_dbi->reset gpio is set.
+ */
+void mipi_dbi_hw_reset(struct mipi_dbi *mipi)
+{
+	if (!mipi->reset)
+		return;
+
+	gpiod_set_value_cansleep(mipi->reset, 0);
+	msleep(20);
+	gpiod_set_value_cansleep(mipi->reset, 1);
+	msleep(120);
+}
+EXPORT_SYMBOL(mipi_dbi_hw_reset);
+
+/**
+ * mipi_dbi_display_is_on - Check if display is on
+ * @mipi: MIPI DBI structure
+ *
+ * This function checks the Power Mode register (if readable) to see if
+ * display output is turned on. This can be used to see if the bootloader
+ * has already turned on the display avoiding flicker when the pipeline is
+ * enabled.
+ *
+ * Returns:
+ * true if the display can be verified to be on, false otherwise.
+ */
+bool mipi_dbi_display_is_on(struct mipi_dbi *mipi)
+{
+	u8 val;
+
+	if (mipi_dbi_command_read(mipi, MIPI_DCS_GET_POWER_MODE, &val))
+		return false;
+
+	val &= ~DCS_POWER_MODE_RESERVED_MASK;
+
+	if (val != (DCS_POWER_MODE_DISPLAY |
+	    DCS_POWER_MODE_DISPLAY_NORMAL_MODE | DCS_POWER_MODE_SLEEP_MODE))
+		return false;
+
+	DRM_DEBUG_DRIVER("Display is ON\n");
+
+	return true;
+}
+EXPORT_SYMBOL(mipi_dbi_display_is_on);
+
+#ifdef CONFIG_SPI
+
+/*
+ * Many controllers have a max speed of 10MHz, but can be pushed way beyond
+ * that. Increase reliability by running pixel data at max speed and the rest
+ * at 10MHz, preventing transfer glitches from messing up the init settings.
+ */
+static u32 mipi_dbi_spi_cmd_max_speed(struct spi_device *spi, size_t len)
+{
+	if (len > 64)
+		return 0; /* use default */
+
+	return min_t(u32, 10000000, spi->max_speed_hz);
+}
+
+/*
+ * MIPI DBI Type C Option 1
+ *
+ * If the SPI controller doesn't have 9 bits per word support,
+ * use blocks of 9 bytes to send 8x 9-bit words using a 8-bit SPI transfer.
+ * Pad partial blocks with MIPI_DCS_NOP (zero).
+ * This is how the D/C bit (x) is added:
+ *     x7654321
+ *     0x765432
+ *     10x76543
+ *     210x7654
+ *     3210x765
+ *     43210x76
+ *     543210x7
+ *     6543210x
+ *     76543210
+ */
+
+static int mipi_dbi_spi1e_transfer(struct mipi_dbi *mipi, int dc,
+				   const void *buf, size_t len,
+				   unsigned int bpw)
+{
+	bool swap_bytes = (bpw == 16 && tinydrm_machine_little_endian());
+	size_t chunk, max_chunk = mipi->tx_buf9_len;
+	struct spi_device *spi = mipi->spi;
+	struct spi_transfer tr = {
+		.tx_buf = mipi->tx_buf9,
+		.bits_per_word = 8,
+	};
+	struct spi_message m;
+	const u8 *src = buf;
+	int i, ret;
+	u8 *dst;
+
+	if (drm_debug & DRM_UT_DRIVER)
+		pr_debug("[drm:%s] dc=%d, max_chunk=%zu, transfers:\n",
+			 __func__, dc, max_chunk);
+
+	tr.speed_hz = mipi_dbi_spi_cmd_max_speed(spi, len);
+	spi_message_init_with_transfers(&m, &tr, 1);
+
+	if (!dc) {
+		if (WARN_ON_ONCE(len != 1))
+			return -EINVAL;
+
+		/* Command: pad no-op's (zeroes) at beginning of block */
+		dst = mipi->tx_buf9;
+		memset(dst, 0, 9);
+		dst[8] = *src;
+		tr.len = 9;
+
+		tinydrm_dbg_spi_message(spi, &m);
+
+		return spi_sync(spi, &m);
+	}
+
+	/* max with room for adding one bit per byte */
+	max_chunk = max_chunk / 9 * 8;
+	/* but no bigger than len */
+	max_chunk = min(max_chunk, len);
+	/* 8 byte blocks */
+	max_chunk = max_t(size_t, 8, max_chunk & ~0x7);
+
+	while (len) {
+		size_t added = 0;
+
+		chunk = min(len, max_chunk);
+		len -= chunk;
+		dst = mipi->tx_buf9;
+
+		if (chunk < 8) {
+			u8 val, carry = 0;
+
+			/* Data: pad no-op's (zeroes) at end of block */
+			memset(dst, 0, 9);
+
+			if (swap_bytes) {
+				for (i = 1; i < (chunk + 1); i++) {
+					val = src[1];
+					*dst++ = carry | BIT(8 - i) | (val >> i);
+					carry = val << (8 - i);
+					i++;
+					val = src[0];
+					*dst++ = carry | BIT(8 - i) | (val >> i);
+					carry = val << (8 - i);
+					src += 2;
+				}
+				*dst++ = carry;
+			} else {
+				for (i = 1; i < (chunk + 1); i++) {
+					val = *src++;
+					*dst++ = carry | BIT(8 - i) | (val >> i);
+					carry = val << (8 - i);
+				}
+				*dst++ = carry;
+			}
+
+			chunk = 8;
+			added = 1;
+		} else {
+			for (i = 0; i < chunk; i += 8) {
+				if (swap_bytes) {
+					*dst++ =                 BIT(7) | (src[1] >> 1);
+					*dst++ = (src[1] << 7) | BIT(6) | (src[0] >> 2);
+					*dst++ = (src[0] << 6) | BIT(5) | (src[3] >> 3);
+					*dst++ = (src[3] << 5) | BIT(4) | (src[2] >> 4);
+					*dst++ = (src[2] << 4) | BIT(3) | (src[5] >> 5);
+					*dst++ = (src[5] << 3) | BIT(2) | (src[4] >> 6);
+					*dst++ = (src[4] << 2) | BIT(1) | (src[7] >> 7);
+					*dst++ = (src[7] << 1) | BIT(0);
+					*dst++ = src[6];
+				} else {
+					*dst++ =                 BIT(7) | (src[0] >> 1);
+					*dst++ = (src[0] << 7) | BIT(6) | (src[1] >> 2);
+					*dst++ = (src[1] << 6) | BIT(5) | (src[2] >> 3);
+					*dst++ = (src[2] << 5) | BIT(4) | (src[3] >> 4);
+					*dst++ = (src[3] << 4) | BIT(3) | (src[4] >> 5);
+					*dst++ = (src[4] << 3) | BIT(2) | (src[5] >> 6);
+					*dst++ = (src[5] << 2) | BIT(1) | (src[6] >> 7);
+					*dst++ = (src[6] << 1) | BIT(0);
+					*dst++ = src[7];
+				}
+
+				src += 8;
+				added++;
+			}
+		}
+
+		tr.len = chunk + added;
+
+		tinydrm_dbg_spi_message(spi, &m);
+		ret = spi_sync(spi, &m);
+		if (ret)
+			return ret;
+	};
+
+	return 0;
+}
+
+static int mipi_dbi_spi1_transfer(struct mipi_dbi *mipi, int dc,
+				  const void *buf, size_t len,
+				  unsigned int bpw)
+{
+	struct spi_device *spi = mipi->spi;
+	struct spi_transfer tr = {
+		.bits_per_word = 9,
+	};
+	const u16 *src16 = buf;
+	const u8 *src8 = buf;
+	struct spi_message m;
+	size_t max_chunk;
+	u16 *dst16;
+	int ret;
+
+	if (!tinydrm_spi_bpw_supported(spi, 9))
+		return mipi_dbi_spi1e_transfer(mipi, dc, buf, len, bpw);
+
+	tr.speed_hz = mipi_dbi_spi_cmd_max_speed(spi, len);
+	max_chunk = mipi->tx_buf9_len;
+	dst16 = mipi->tx_buf9;
+
+	if (drm_debug & DRM_UT_DRIVER)
+		pr_debug("[drm:%s] dc=%d, max_chunk=%zu, transfers:\n",
+			 __func__, dc, max_chunk);
+
+	max_chunk = min(max_chunk / 2, len);
+
+	spi_message_init_with_transfers(&m, &tr, 1);
+	tr.tx_buf = dst16;
+
+	while (len) {
+		size_t chunk = min(len, max_chunk);
+		unsigned int i;
+
+		if (bpw == 16 && tinydrm_machine_little_endian()) {
+			for (i = 0; i < (chunk * 2); i += 2) {
+				dst16[i]     = *src16 >> 8;
+				dst16[i + 1] = *src16++ & 0xFF;
+				if (dc) {
+					dst16[i]     |= 0x0100;
+					dst16[i + 1] |= 0x0100;
+				}
+			}
+		} else {
+			for (i = 0; i < chunk; i++) {
+				dst16[i] = *src8++;
+				if (dc)
+					dst16[i] |= 0x0100;
+			}
+		}
+
+		tr.len = chunk;
+		len -= chunk;
+
+		tinydrm_dbg_spi_message(spi, &m);
+		ret = spi_sync(spi, &m);
+		if (ret)
+			return ret;
+	};
+
+	return 0;
+}
+
+static int mipi_dbi_typec1_command(struct mipi_dbi *mipi, u8 cmd,
+				   u8 *parameters, size_t num)
+{
+	unsigned int bpw = (cmd == MIPI_DCS_WRITE_MEMORY_START) ? 16 : 8;
+	int ret;
+
+	if (mipi_dbi_command_is_read(mipi, cmd))
+		return -ENOTSUPP;
+
+	MIPI_DBI_DEBUG_COMMAND(cmd, parameters, num);
+
+	ret = mipi_dbi_spi1_transfer(mipi, 0, &cmd, 1, 8);
+	if (ret || !num)
+		return ret;
+
+	return mipi_dbi_spi1_transfer(mipi, 1, parameters, num, bpw);
+}
+
+/* MIPI DBI Type C Option 3 */
+
+static int mipi_dbi_typec3_command_read(struct mipi_dbi *mipi, u8 cmd,
+					u8 *data, size_t len)
+{
+	struct spi_device *spi = mipi->spi;
+	u32 speed_hz = min_t(u32, MIPI_DBI_MAX_SPI_READ_SPEED,
+			     spi->max_speed_hz / 2);
+	struct spi_transfer tr[2] = {
+		{
+			.speed_hz = speed_hz,
+			.tx_buf = &cmd,
+			.len = 1,
+		}, {
+			.speed_hz = speed_hz,
+			.len = len,
+		},
+	};
+	struct spi_message m;
+	u8 *buf;
+	int ret;
+
+	if (!len)
+		return -EINVAL;
+
+	/*
+	 * Support non-standard 24-bit and 32-bit Nokia read commands which
+	 * start with a dummy clock, so we need to read an extra byte.
+	 */
+	if (cmd == MIPI_DCS_GET_DISPLAY_ID ||
+	    cmd == MIPI_DCS_GET_DISPLAY_STATUS) {
+		if (!(len == 3 || len == 4))
+			return -EINVAL;
+
+		tr[1].len = len + 1;
+	}
+
+	buf = kmalloc(tr[1].len, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	tr[1].rx_buf = buf;
+	gpiod_set_value_cansleep(mipi->dc, 0);
+
+	spi_message_init_with_transfers(&m, tr, ARRAY_SIZE(tr));
+	ret = spi_sync(spi, &m);
+	if (ret)
+		goto err_free;
+
+	tinydrm_dbg_spi_message(spi, &m);
+
+	if (tr[1].len == len) {
+		memcpy(data, buf, len);
+	} else {
+		unsigned int i;
+
+		for (i = 0; i < len; i++)
+			data[i] = (buf[i] << 1) | !!(buf[i + 1] & BIT(7));
+	}
+
+	MIPI_DBI_DEBUG_COMMAND(cmd, data, len);
+
+err_free:
+	kfree(buf);
+
+	return ret;
+}
+
+static int mipi_dbi_typec3_command(struct mipi_dbi *mipi, u8 cmd,
+				   u8 *par, size_t num)
+{
+	struct spi_device *spi = mipi->spi;
+	unsigned int bpw = 8;
+	u32 speed_hz;
+	int ret;
+
+	if (mipi_dbi_command_is_read(mipi, cmd))
+		return mipi_dbi_typec3_command_read(mipi, cmd, par, num);
+
+	MIPI_DBI_DEBUG_COMMAND(cmd, par, num);
+
+	gpiod_set_value_cansleep(mipi->dc, 0);
+	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, 1);
+	ret = tinydrm_spi_transfer(spi, speed_hz, NULL, 8, &cmd, 1);
+	if (ret || !num)
+		return ret;
+
+	if (cmd == MIPI_DCS_WRITE_MEMORY_START && !mipi->swap_bytes)
+		bpw = 16;
+
+	gpiod_set_value_cansleep(mipi->dc, 1);
+	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, num);
+
+	return tinydrm_spi_transfer(spi, speed_hz, NULL, bpw, par, num);
+}
+
+/**
+ * mipi_dbi_spi_init - Initialize MIPI DBI SPI interfaced controller
+ * @spi: SPI device
+ * @dc: D/C gpio (optional)
+ * @mipi: &mipi_dbi structure to initialize
+ * @pipe_funcs: Display pipe functions
+ * @driver: DRM driver
+ * @mode: Display mode
+ * @rotation: Initial rotation in degrees Counter Clock Wise
+ *
+ * This function sets &mipi_dbi->command, enables &mipi->read_commands for the
+ * usual read commands and initializes @mipi using mipi_dbi_init().
+ *
+ * If @dc is set, a Type C Option 3 interface is assumed, if not
+ * Type C Option 1.
+ *
+ * If the SPI master driver doesn't support the necessary bits per word,
+ * the following transformation is used:
+ *
+ * - 9-bit: reorder buffer as 9x 8-bit words, padded with no-op command.
+ * - 16-bit: if big endian send as 8-bit, if little endian swap bytes
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_spi_init(struct spi_device *spi, struct mipi_dbi *mipi,
+		      struct gpio_desc *dc,
+		      const struct drm_simple_display_pipe_funcs *pipe_funcs,
+		      struct drm_driver *driver,
+		      const struct drm_display_mode *mode,
+		      unsigned int rotation)
+{
+	size_t tx_size = tinydrm_spi_max_transfer_size(spi, 0);
+	struct device *dev = &spi->dev;
+	int ret;
+
+	if (tx_size < 16) {
+		DRM_ERROR("SPI transmit buffer too small: %zu\n", tx_size);
+		return -EINVAL;
+	}
+
+	/*
+	 * Even though it's not the SPI device that does DMA (the master does),
+	 * the dma mask is necessary for the dma_alloc_wc() in
+	 * drm_gem_cma_create(). The dma_addr returned will be a physical
+	 * adddress which might be different from the bus address, but this is
+	 * not a problem since the address will not be used.
+	 * The virtual address is used in the transfer and the SPI core
+	 * re-maps it on the SPI master device using the DMA streaming API
+	 * (spi_map_buf()).
+	 */
+	if (!dev->coherent_dma_mask) {
+		ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
+		if (ret) {
+			dev_warn(dev, "Failed to set dma mask %d\n", ret);
+			return ret;
+		}
+	}
+
+	mipi->spi = spi;
+	mipi->read_commands = mipi_dbi_dcs_read_commands;
+
+	if (dc) {
+		mipi->command = mipi_dbi_typec3_command;
+		mipi->dc = dc;
+		if (tinydrm_machine_little_endian() &&
+		    !tinydrm_spi_bpw_supported(spi, 16))
+			mipi->swap_bytes = true;
+	} else {
+		mipi->command = mipi_dbi_typec1_command;
+		mipi->tx_buf9_len = tx_size;
+		mipi->tx_buf9 = devm_kmalloc(dev, tx_size, GFP_KERNEL);
+		if (!mipi->tx_buf9)
+			return -ENOMEM;
+	}
+
+	return mipi_dbi_init(dev, mipi, pipe_funcs, driver, mode, rotation);
+}
+EXPORT_SYMBOL(mipi_dbi_spi_init);
+
+#endif /* CONFIG_SPI */
+
+#ifdef CONFIG_DEBUG_FS
+
+static ssize_t mipi_dbi_debugfs_command_write(struct file *file,
+					      const char __user *ubuf,
+					      size_t count, loff_t *ppos)
+{
+	struct seq_file *m = file->private_data;
+	struct mipi_dbi *mipi = m->private;
+	u8 val, cmd, parameters[64];
+	char *buf, *pos, *token;
+	unsigned int i;
+	int ret;
+
+	buf = memdup_user_nul(ubuf, count);
+	if (IS_ERR(buf))
+		return PTR_ERR(buf);
+
+	/* strip trailing whitespace */
+	for (i = count - 1; i > 0; i--)
+		if (isspace(buf[i]))
+			buf[i] = '\0';
+		else
+			break;
+	i = 0;
+	pos = buf;
+	while (pos) {
+		token = strsep(&pos, " ");
+		if (!token) {
+			ret = -EINVAL;
+			goto err_free;
+		}
+
+		ret = kstrtou8(token, 16, &val);
+		if (ret < 0)
+			goto err_free;
+
+		if (token == buf)
+			cmd = val;
+		else
+			parameters[i++] = val;
+
+		if (i == 64) {
+			ret = -E2BIG;
+			goto err_free;
+		}
+	}
+
+	ret = mipi_dbi_command_buf(mipi, cmd, parameters, i);
+
+err_free:
+	kfree(buf);
+
+	return ret < 0 ? ret : count;
+}
+
+static int mipi_dbi_debugfs_command_show(struct seq_file *m, void *unused)
+{
+	struct mipi_dbi *mipi = m->private;
+	u8 cmd, val[4];
+	size_t len, i;
+	int ret;
+
+	for (cmd = 0; cmd < 255; cmd++) {
+		if (!mipi_dbi_command_is_read(mipi, cmd))
+			continue;
+
+		switch (cmd) {
+		case MIPI_DCS_READ_MEMORY_START:
+		case MIPI_DCS_READ_MEMORY_CONTINUE:
+			len = 2;
+			break;
+		case MIPI_DCS_GET_DISPLAY_ID:
+			len = 3;
+			break;
+		case MIPI_DCS_GET_DISPLAY_STATUS:
+			len = 4;
+			break;
+		default:
+			len = 1;
+			break;
+		}
+
+		seq_printf(m, "%02x: ", cmd);
+		ret = mipi_dbi_command_buf(mipi, cmd, val, len);
+		if (ret) {
+			seq_puts(m, "XX\n");
+			continue;
+		}
+
+		for (i = 0; i < len; i++)
+			seq_printf(m, "%02x", val[i]);
+		seq_puts(m, "\n");
+	}
+
+	return 0;
+}
+
+static int mipi_dbi_debugfs_command_open(struct inode *inode,
+					 struct file *file)
+{
+	return single_open(file, mipi_dbi_debugfs_command_show,
+			   inode->i_private);
+}
+
+static const struct file_operations mipi_dbi_debugfs_command_fops = {
+	.owner = THIS_MODULE,
+	.open = mipi_dbi_debugfs_command_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+	.write = mipi_dbi_debugfs_command_write,
+};
+
+static const struct drm_info_list mipi_dbi_debugfs_list[] = {
+	{ "fb",   drm_fb_cma_debugfs_show, 0 },
+};
+
+/**
+ * mipi_dbi_debugfs_init - Create debugfs entries
+ * @minor: DRM minor
+ *
+ * This function creates a 'command' debugfs file for sending commands to the
+ * controller or getting the read command values.
+ * Drivers can use this as their &drm_driver->debugfs_init callback.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_debugfs_init(struct drm_minor *minor)
+{
+	struct tinydrm_device *tdev = minor->dev->dev_private;
+	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
+	umode_t mode = S_IFREG | S_IWUSR;
+
+	if (mipi->read_commands)
+		mode |= S_IRUGO;
+	debugfs_create_file("command", mode, minor->debugfs_root, mipi,
+			    &mipi_dbi_debugfs_command_fops);
+
+	return drm_debugfs_create_files(mipi_dbi_debugfs_list,
+					ARRAY_SIZE(mipi_dbi_debugfs_list),
+					minor->debugfs_root, minor);
+}
+EXPORT_SYMBOL(mipi_dbi_debugfs_init);
+
+#endif
+
+MODULE_LICENSE("GPL");
diff --git a/include/drm/tinydrm/mipi-dbi.h b/include/drm/tinydrm/mipi-dbi.h
new file mode 100644
index 0000000..d137b16
--- /dev/null
+++ b/include/drm/tinydrm/mipi-dbi.h
@@ -0,0 +1,107 @@ 
+/*
+ * MIPI Display Bus Interface (DBI) LCD controller support
+ *
+ * Copyright 2016 Noralf Trønnes
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_MIPI_DBI_H
+#define __LINUX_MIPI_DBI_H
+
+#include <drm/tinydrm/tinydrm.h>
+
+struct spi_device;
+struct gpio_desc;
+struct regulator;
+
+/**
+ * struct mipi_dbi - MIPI DBI controller
+ * @tinydrm: tinydrm base
+ * @spi: SPI device
+ * @enabled: Pipeline is enabled
+ * @cmdlock: Command lock
+ * @command: Bus specific callback executing commands.
+ * @read_commands: Array of read commands terminated by a zero entry.
+ *                 Reading is disabled if this is NULL.
+ * @dc: Optional D/C gpio.
+ * @tx_buf: Buffer used for transfer (copy clip rect area)
+ * @tx_buf9: Buffer used for Option 1 9-bit conversion
+ * @tx_buf9_len: Size of tx_buf9.
+ * @swap_bytes: Swap bytes in buffer before transfer
+ * @reset: Optional reset gpio
+ * @rotation: initial rotation in degrees Counter Clock Wise
+ * @backlight: backlight device (optional)
+ * @regulator: power regulator (optional)
+ */
+struct mipi_dbi {
+	struct tinydrm_device tinydrm;
+	struct spi_device *spi;
+	bool enabled;
+	struct mutex cmdlock;
+	int (*command)(struct mipi_dbi *mipi, u8 cmd, u8 *param, size_t num);
+	const u8 *read_commands;
+	struct gpio_desc *dc;
+	u16 *tx_buf;
+	void *tx_buf9;
+	size_t tx_buf9_len;
+	bool swap_bytes;
+	struct gpio_desc *reset;
+	unsigned int rotation;
+	struct backlight_device *backlight;
+	struct regulator *regulator;
+};
+
+static inline struct mipi_dbi *
+mipi_dbi_from_tinydrm(struct tinydrm_device *tdev)
+{
+	return container_of(tdev, struct mipi_dbi, tinydrm);
+}
+
+int mipi_dbi_spi_init(struct spi_device *spi, struct mipi_dbi *mipi,
+		      struct gpio_desc *dc,
+		      const struct drm_simple_display_pipe_funcs *pipe_funcs,
+		      struct drm_driver *driver,
+		      const struct drm_display_mode *mode,
+		      unsigned int rotation);
+int mipi_dbi_init(struct device *dev, struct mipi_dbi *mipi,
+		  const struct drm_simple_display_pipe_funcs *pipe_funcs,
+		  struct drm_driver *driver,
+		  const struct drm_display_mode *mode, unsigned int rotation);
+void mipi_dbi_pipe_enable(struct drm_simple_display_pipe *pipe,
+			  struct drm_crtc_state *crtc_state);
+void mipi_dbi_pipe_disable(struct drm_simple_display_pipe *pipe);
+void mipi_dbi_hw_reset(struct mipi_dbi *mipi);
+bool mipi_dbi_display_is_on(struct mipi_dbi *mipi);
+
+int mipi_dbi_command_read(struct mipi_dbi *mipi, u8 cmd, u8 *val);
+int mipi_dbi_command_buf(struct mipi_dbi *mipi, u8 cmd, u8 *data, size_t len);
+
+/**
+ * mipi_dbi_command - MIPI DCS command with optional parameter(s)
+ * @mipi: MIPI structure
+ * @cmd: Command
+ * @seq...: Optional parameter(s)
+ *
+ * Send MIPI DCS command to the controller. Use mipi_dbi_command_read() for
+ * get/read.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+#define mipi_dbi_command(mipi, cmd, seq...) \
+({ \
+	u8 d[] = { seq }; \
+	mipi_dbi_command_buf(mipi, cmd, d, ARRAY_SIZE(d)); \
+})
+
+#ifdef CONFIG_DEBUG_FS
+int mipi_dbi_debugfs_init(struct drm_minor *minor);
+#else
+#define mipi_dbi_debugfs_init		NULL
+#endif
+
+#endif /* __LINUX_MIPI_DBI_H */