* [GIT PULL V2 0/4] ARM: mvebu: changes for v3.9
From: Jason Cooper @ 2013-01-23 2:48 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <pull-1358033364-983365>
The following changes since commit 4b53ab9cb853e446f628e561ce6fcc527df30a76:
Merge branch 'mvebu/fixes' into mvebu/drivers (2013-01-23 01:42:14 +0000)
are available in the git repository at:
git://git.infradead.org/users/jcooper/linux.git mvebu/for-next
for you to fetch changes up to bf70a4aed75cdcfc765f896916cec73d3f94f3ec:
Merge branch 'mvebu/dt' into mvebu/for-next (2013-01-23 02:42:39 +0000)
----------------------------------------------------------------
Andrew Lunn (1):
ARM: Kirkwood: Cleanup unneeded include files
Gregory CLEMENT (2):
arm: mvebu: Update defconfig with Marvell RTC support
arm: mvebu: Add RTC support for Armada 370 and Armada XP
Jason Cooper (4):
Merge branch 'mvebu/cleanup' into mvebu/for-next
Merge branch 'mvebu/drivers' into mvebu/for-next
Merge branch 'mvebu/boards' into mvebu/for-next
Merge branch 'mvebu/dt' into mvebu/for-next
Nobuhiro Iwamatsu (3):
ARM: Kirkwood: Add pinctrl of TWSI1 to 88f6282
ARM: Kirkwood: Add pinctrl of NAND to 88f6282
ARM: Kirkwood: Convert openblocks A6 board to pinctrl
Sebastian Hesselbarth (2):
ARM: Dove: move CuBox led pinctrl to gpio-leds node
ARM: Dove: add fixed regulator for CuBox USB power
Thomas Petazzoni (18):
arm: kirkwood: dockstar: remove useless include of SDIO header
mmc: mvsdio: use slot-gpio infrastructure for write protect gpio
mmc: mvsdio: use slot-gpio for card detect gpio
mmc: mvsdio: implement a Device Tree binding
mmc: mvsdio: add pinctrl integration
arm: mvebu: enable SDIO support in mvebu_defconfig
arm: mvebu: enable mwifiex driver in mvebu_defconfig
arm: mvebu: enable btmrvl driver in mvebu_defconfig
arm: mvebu: add DT information for the SDIO interface of Armada 370/XP
arm: mvebu: add pin muxing options for the SDIO interface on Armada 370
arm: mvebu: add pin muxing options for the SDIO interface on Armada XP
arm: mvebu: enable the SD card slot on Armada XP DB board
arm: mvebu: enable the SD card slot on Armada 370 DB board
arm: mvebu: enable the SDIO interface on the Globalscale Mirabox
arm: kirkwood: add Device Tree informations for the SDIO controller
arm: kirkwood: dreamplug: use Device Tree to probe SDIO
arm: kirkwood: mplcec4: use Device Tree to probe SDIO
arm: kirkwood: add pinmux option for the SDIO interface on 88F6282
.../devicetree/bindings/mmc/orion-sdio.txt | 17 +++
arch/arm/boot/dts/armada-370-db.dts | 15 +++
arch/arm/boot/dts/armada-370-mirabox.dts | 10 ++
arch/arm/boot/dts/armada-370-xp.dtsi | 14 +++
arch/arm/boot/dts/armada-370.dtsi | 12 ++
arch/arm/boot/dts/armada-xp-db.dts | 7 ++
arch/arm/boot/dts/armada-xp-mv78230.dtsi | 6 +
arch/arm/boot/dts/armada-xp-mv78260.dtsi | 6 +
arch/arm/boot/dts/armada-xp-mv78460.dtsi | 6 +
arch/arm/boot/dts/dove-cubox.dts | 28 ++++-
arch/arm/boot/dts/kirkwood-6282.dtsi | 17 +++
arch/arm/boot/dts/kirkwood-dreamplug.dts | 7 ++
arch/arm/boot/dts/kirkwood-mplcec4.dts | 11 +-
arch/arm/boot/dts/kirkwood-openblocks_a6.dts | 116 ++++++++++++++++++
arch/arm/boot/dts/kirkwood.dtsi | 8 ++
arch/arm/configs/mvebu_defconfig | 9 ++
arch/arm/mach-dove/Kconfig | 2 +
arch/arm/mach-kirkwood/board-dreamplug.c | 6 -
arch/arm/mach-kirkwood/board-ib62x0.c | 1 -
arch/arm/mach-kirkwood/board-mplcec4.c | 8 --
arch/arm/mach-kirkwood/board-openblocks_a6.c | 44 -------
arch/arm/mach-kirkwood/dockstar-setup.c | 1 -
drivers/mmc/host/mvsdio.c | 129 ++++++++++-----------
23 files changed, 351 insertions(+), 129 deletions(-)
create mode 100644 Documentation/devicetree/bindings/mmc/orion-sdio.txt
^ permalink raw reply
* [PATCH v2] hardlockup: detect hard lockups without NMIs using secondary cpus
From: Colin Cross @ 2013-01-23 2:38 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAMbhsRRM+60gUTRuYzT5sAgLPP7Fj=T78Z3E_hZVmajY-wqGeQ@mail.gmail.com>
On Mon, Jan 14, 2013 at 4:30 PM, Colin Cross <ccross@android.com> wrote:
> On Mon, Jan 14, 2013 at 4:25 PM, Andrew Morton
> <akpm@linux-foundation.org> wrote:
>> On Mon, 14 Jan 2013 16:19:23 -0800
>> Colin Cross <ccross@android.com> wrote:
>>
>>> >> +static void watchdog_check_hardlockup_other_cpu(void)
>>> >> +{
>>> >> + unsigned int next_cpu;
>>> >> +
>>> >> + /*
>>> >> + * Test for hardlockups every 3 samples. The sample period is
>>> >> + * watchdog_thresh * 2 / 5, so 3 samples gets us back to slightly over
>>> >> + * watchdog_thresh (over by 20%).
>>> >> + */
>>> >> + if (__this_cpu_read(hrtimer_interrupts) % 3 != 0)
>>> >> + return;
>>> >
>>> > The hardwired interval Seems Wrong. watchdog_thresh is tunable at runtime.
>>> >
>>> > The comment could do with some fleshing out. *why* do we want to test
>>> > at an interval "slightly over watchdog_thresh"? What's going on here?
>>>
>>> I'll reword it. We don't want to be slightly over watchdog_thresh,
>>> ideally we would be exactly at watchdog_thresh. However, since this
>>> relies on the hrtimer interrupts that are scheduled at watchdog_thresh
>>> * 2 / 5, there is no multiple of hrtimer_interrupts that will result
>>> in watchdog_thresh. watchdog_thresh * 2 / 5 * 3 (watchdog_thresh *
>>> 1.2) is the closest I can get to testing for a hardlockup once every
>>> watchdog_thresh seconds.
>>
>> It needs more than rewording, doesn't it? What happens if watchdog_thresh is
>> altered at runtime?
>
> I'm not sure what you mean. If watchdog_thresh changes, the next
> hrtimer interrupt on each cpu will move the following hrtimer
> interrupt forward by the new watchdog_thresh * 2 / 5. There may be a
> single cycle of watchdog checks at an intermediate period, but nothing
> bad should happen.
>
> This code doesn't ever deal with watchdog_thresh directly, it is only
> counting hrtimer interrupts. 3 hrtimer interrupts is always a
> reasonable approximation of watchdog_thresh.
I think I see the race you were referring to. When the hrtimer
interrupt fires on the first cpu after changing the watchdog thresh,
it will see the new value and starting firing the timer at the new
rate. The other cpus may not see the new value for as long as the old
sample period. If the new threshold is more than 3 times lower than
the old value, one cpu could easily get 3 hrtimer interrupts while
another cpu doesn't get any, triggering the lockup detector.
The exact same issue is already present in the existing NMI detector,
which is conceptually very similar to this one. It has two
asynchronous timers, the NMI perf counter and the hrtimer. If one
timer sees the new threshold before the other their relative rates
will be wrong and the lockup detector may fire. Setting
watchdog_nmi_touch is not good enough, as one interrupt could fire
enough to trigger the hardlockup detector twice. The only fix I can
think of is to set a timestamp far enough in the future to catch all
the ordering problems, and ignore lockups until sched_clock() is
higher than the timestamp. I think it would need to be a delay of
twice the larger of the old and new watchdog_thresh values.
But its worse than that, the NMI perf counter is never updated when
watchdog_thresh changes, so raising watchdog_thresh more than 2.5x may
trigger the NMI detector.
I'll send a separate patch for the first problem, but I don't have
anything to test resetting the NMI counter on so I'll leave that issue
for someone else.
^ permalink raw reply
* [PATCH v5 00/14] DMA Engine support for AM33XX
From: Mark Brown @ 2013-01-23 2:21 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <50FEB6B2.5020303@ti.com>
On Tue, Jan 22, 2013 at 09:26:34PM +0530, Sekhar Nori wrote:
> On 1/16/2013 2:02 AM, Matt Porter wrote:
> > This series adds DMA Engine support for AM33xx, which uses
> > an EDMA DMAC. The EDMA DMAC has been previously supported by only
> > a private API implementation (much like the situation with OMAP
> > DMA) found on the DaVinci family of SoCs.
> Will you take this series through the OMAP tree? Only 1/14 touches
> mach-davinci and I am mostly okay with it except some changes I just
> requested Matt to make in another thread.
Is this series somewhere near actually getting merged then? It seemed
like there was lots of stuff going on.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20130123/7c6c5165/attachment.sig>
^ permalink raw reply
* [PATCH 1/3] fb: backlight: Add the Himax HX-8357B LCD controller
From: Jingoo Han @ 2013-01-23 1:51 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1358871791-14214-2-git-send-email-maxime.ripard@free-electrons.com>
On Wednesday, January 23, 2013 1:23 AM, Maxime Ripard wrote
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
> drivers/video/backlight/Kconfig | 7 +
> drivers/video/backlight/Makefile | 1 +
> drivers/video/backlight/hx8357.c | 482 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 490 insertions(+)
> create mode 100644 drivers/video/backlight/hx8357.c
>
> diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
> index 765a945..c39bed0 100644
> --- a/drivers/video/backlight/Kconfig
> +++ b/drivers/video/backlight/Kconfig
> @@ -126,6 +126,13 @@ config LCD_AMS369FG06
> If you have an AMS369FG06 AMOLED Panel, say Y to enable its
> LCD control driver.
>
> +config LCD_HX8357
> + tristate "Himax HX-8357 LCD Driver"
> + depends on SPI
> + help
> + If you have a HX-8357 LCD panel, say Y to enable its LCD control
> + driver.
> +
> endif # LCD_CLASS_DEVICE
>
> #
> diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
> index e7ce729..b69d391 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -14,6 +14,7 @@ obj-$(CONFIG_LCD_TOSA) += tosa_lcd.o
> obj-$(CONFIG_LCD_S6E63M0) += s6e63m0.o
> obj-$(CONFIG_LCD_LD9040) += ld9040.o
> obj-$(CONFIG_LCD_AMS369FG06) += ams369fg06.o
> +obj-$(CONFIG_LCD_HX8357) += hx8357.o
>
> obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o
> obj-$(CONFIG_BACKLIGHT_ATMEL_PWM) += atmel-pwm-bl.o
> diff --git a/drivers/video/backlight/hx8357.c b/drivers/video/backlight/hx8357.c
> new file mode 100644
> index 0000000..ee4d607
> --- /dev/null
> +++ b/drivers/video/backlight/hx8357.c
> @@ -0,0 +1,482 @@
> +/*
> + * Driver for the Himax HX-8357 LCD Controller
> + *
> + * Copyright 2012 Free Electrons
> + *
> + * Licensed under the GPLv2 or later.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/lcd.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_gpio.h>
> +#include <linux/spi/spi.h>
> +
> +#define HX8357_NUM_IM_PINS 3
> +
> +#define HX8357_SWRESET 0x01
> +#define HX8357_GET_RED_CHANNEL 0x06
> +#define HX8357_GET_GREEN_CHANNEL 0x07
> +#define HX8357_GET_BLUE_CHANNEL 0x08
> +#define HX8357_GET_POWER_MODE 0x0a
> +#define HX8357_GET_MADCTL 0x0b
> +#define HX8357_GET_PIXEL_FORMAT 0x0c
> +#define HX8357_GET_DISPLAY_MODE 0x0d
> +#define HX8357_GET_SIGNAL_MODE 0x0e
> +#define HX8357_GET_DIAGNOSTIC_RESULT 0x0f
> +#define HX8357_ENTER_SLEEP_MODE 0x10
> +#define HX8357_EXIT_SLEEP_MODE 0x11
> +#define HX8357_ENTER_PARTIAL_MODE 0x12
> +#define HX8357_ENTER_NORMAL_MODE 0x13
> +#define HX8357_EXIT_INVERSION_MODE 0x20
> +#define HX8357_ENTER_INVERSION_MODE 0x21
> +#define HX8357_SET_DISPLAY_OFF 0x28
> +#define HX8357_SET_DISPLAY_ON 0x29
> +#define HX8357_SET_COLUMN_ADDRESS 0x2a
> +#define HX8357_SET_PAGE_ADDRESS 0x2b
> +#define HX8357_WRITE_MEMORY_START 0x2c
> +#define HX8357_READ_MEMORY_START 0x2e
> +#define HX8357_SET_PARTIAL_AREA 0x30
> +#define HX8357_SET_SCROLL_AREA 0x33
> +#define HX8357_SET_TEAR_OFF 0x34
> +#define HX8357_SET_TEAR_ON 0x35
> +#define HX8357_SET_ADDRESS_MODE 0x36
> +#define HX8357_SET_SCROLL_START 0x37
> +#define HX8357_EXIT_IDLE_MODE 0x38
> +#define HX8357_ENTER_IDLE_MOD 0x39
Please change MOD to MODE as below :-)
HX8357_ENTER_IDLE_MODE
> +#define HX8357_SET_PIXEL_FORMAT 0x3a
> +#define HX8357_SET_PIXEL_FORMAT_DBI_3BIT (0x1)
> +#define HX8357_SET_PIXEL_FORMAT_DBI_16BIT (0x5)
> +#define HX8357_SET_PIXEL_FORMAT_DBI_18BIT (0x6)
> +#define HX8357_SET_PIXEL_FORMAT_DPI_3BIT (0x1 << 4)
> +#define HX8357_SET_PIXEL_FORMAT_DPI_16BIT (0x5 << 4)
> +#define HX8357_SET_PIXEL_FORMAT_DPI_18BIT (0x6 << 4)
> +#define HX8357_WRITE_MEMORY_CONTINUE 0x3c
> +#define HX8357_READ_MEMORY_CONTINUE 0x3e
> +#define HX8357_SET_TEAR_SCAN_LINES 0x44
> +#define HX8357_GET_SCAN_LINES 0x45
> +#define HX8357_READ_DDB_START 0xa1
> +#define HX8357_SET_DISPLAY_MODE 0xb4
> +#define HX8357_SET_DISPLAY_MODE_RGB_THROUGH (0x3)
> +#define HX8357_SET_DISPLAY_MODE_RGB_INTERFACE (1 << 4)
> +#define HX8357_SET_PANEL_DRIVING 0xc0
> +#define HX8357_SET_DISPLAY_FRAME 0xc5
> +#define HX8357_SET_RGB 0xc6
> +#define HX8357_SET_RGB_ENABLE_HIGH (1 << 1)
> +#define HX8357_SET_GAMMA 0xc8
> +#define HX8357_SET_POWER 0xd0
> +#define HX8357_SET_VCOM 0xd1
> +#define HX8357_SET_POWER_NORMAL 0xd2
> +#define HX8357_SET_PANEL_RELATED 0xe9
> +
> +struct hx8357_data {
> + unsigned im_pins[HX8357_NUM_IM_PINS];
> + unsigned reset;
> + struct spi_device *spi;
> + int state;
> +};
> +
> +static int hx8357_spi_write_then_read(struct lcd_device *lcdev,
> + void *txbuf, u16 txlen,
> + void *rxbuf, u16 rxlen)
> +{
> + struct hx8357_data *lcd = lcd_get_data(lcdev);
> + struct spi_message msg;
> + struct spi_transfer xfer[2];
> + u16 *local_txbuf = NULL;
> + int ret = 0;
> +
> + memset(xfer, 0, sizeof(xfer));
> + spi_message_init(&msg);
> +
> + if (txlen) {
> + int i;
> +
> + local_txbuf = kcalloc(sizeof(*local_txbuf), txlen, GFP_KERNEL);
> +
> + if (!local_txbuf) {
> + dev_err(&lcdev->dev, "Couldn't allocate data buffer.\n");
> + return -ENOMEM;
> + }
> +
> + for (i = 0; i < txlen; i++) {
> + local_txbuf[i] = ((u8 *)txbuf)[i];
> + if (i > 0)
> + local_txbuf[i] |= 1 << 8;
> + }
> +
> + xfer[0].len = 2 * txlen;
> + xfer[0].bits_per_word = 9;
> + xfer[0].tx_buf = local_txbuf;
> + spi_message_add_tail(&xfer[0], &msg);
> + }
> +
> + if (rxlen) {
> + xfer[1].len = rxlen;
> + xfer[1].bits_per_word = 8;
> + xfer[1].rx_buf = rxbuf;
> + spi_message_add_tail(&xfer[1], &msg);
> + }
> +
> + ret = spi_sync(lcd->spi, &msg);
> + if (ret < 0)
> + dev_err(&lcdev->dev, "Couldn't send SPI data.\n");
> +
> + if (txlen)
> + kfree(local_txbuf);
> +
> + return ret;
> +}
> +
> +static inline int hx8357_spi_write_array(struct lcd_device *lcdev,
> + u8 *value, u8 len)
> +{
> + return hx8357_spi_write_then_read(lcdev, value, len, NULL, 0);
> +}
> +
> +static inline int hx8357_spi_write_byte(struct lcd_device *lcdev,
> + u8 value)
> +{
> + return hx8357_spi_write_then_read(lcdev, &value, 1, NULL, 0);
> +}
> +
> +static int hx8357_enter_standby(struct lcd_device *lcdev)
> +{
> + int ret;
> +
> + ret = hx8357_spi_write_byte(lcdev, HX8357_SET_DISPLAY_OFF);
> + if (ret < 0)
> + return ret;
> +
> + usleep_range(10000, 12000);
> +
> + ret = hx8357_spi_write_byte(lcdev, HX8357_ENTER_SLEEP_MODE);
> + if (ret < 0)
> + return ret;
> +
> + msleep(120);
> +
> + return 0;
> +}
> +
> +static int hx8357_exit_standby(struct lcd_device *lcdev)
> +{
> + int ret;
> +
> + ret = hx8357_spi_write_byte(lcdev, HX8357_EXIT_SLEEP_MODE);
> + if (ret < 0)
> + return ret;
> +
> + msleep(120);
> +
> + ret = hx8357_spi_write_byte(lcdev, HX8357_SET_DISPLAY_ON);
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int hx8357_lcd_init(struct lcd_device *lcdev)
> +{
> + struct hx8357_data *lcd = lcd_get_data(lcdev);
> + u8 buf[16];
> + int ret;
> +
> + /*
> + * Set the interface selection pins to SPI mode, with three
> + * wires
> + */
> + gpio_set_value_cansleep(lcd->im_pins[0], 1);
> + gpio_set_value_cansleep(lcd->im_pins[1], 0);
> + gpio_set_value_cansleep(lcd->im_pins[2], 1);
> +
> + /* Reset the screen */
> + gpio_set_value(lcd->reset, 1);
> + usleep_range(10000, 12000);
> + gpio_set_value(lcd->reset, 0);
> + usleep_range(10000, 12000);
> + gpio_set_value(lcd->reset, 1);
> + msleep(120);
> +
> + buf[0] = HX8357_SET_POWER;
> + buf[1] = 0x44;
> + buf[2] = 0x41;
> + buf[3] = 0x06;
> + ret = hx8357_spi_write_array(lcdev, buf, 4);
> + if (ret < 0)
> + return ret;
> +
> + buf[0] = HX8357_SET_VCOM;
> + buf[1] = 0x40;
> + buf[2] = 0x10;
> + ret = hx8357_spi_write_array(lcdev, buf, 3);
> + if (ret < 0)
> + return ret;
> +
> + buf[0] = HX8357_SET_POWER_NORMAL;
> + buf[1] = 0x05;
> + buf[2] = 0x12;
> + ret = hx8357_spi_write_array(lcdev, buf, 3);
> + if (ret < 0)
> + return ret;
> +
> + buf[0] = HX8357_SET_PANEL_DRIVING;
> + buf[1] = 0x14;
> + buf[2] = 0x3b;
> + buf[3] = 0x00;
> + buf[4] = 0x02;
> + buf[5] = 0x11;
> + ret = hx8357_spi_write_array(lcdev, buf, 6);
> + if (ret < 0)
> + return ret;
> +
> + buf[0] = HX8357_SET_DISPLAY_FRAME;
> + buf[1] = 0x0c;
> + ret = hx8357_spi_write_array(lcdev, buf, 2);
> + if (ret < 0)
> + return ret;
> +
> + buf[0] = HX8357_SET_PANEL_RELATED;
> + buf[1] = 0x01;
> + ret = hx8357_spi_write_array(lcdev, buf, 2);
> + if (ret < 0)
> + return ret;
> +
> + buf[0] = 0xea;
> + buf[1] = 0x03;
> + buf[2] = 0x00;
> + buf[3] = 0x00;
> + ret = hx8357_spi_write_array(lcdev, buf, 4);
> + if (ret < 0)
> + return ret;
> +
> + buf[0] = 0xeb;
> + buf[1] = 0x40;
> + buf[2] = 0x54;
> + buf[3] = 0x26;
> + buf[4] = 0xdb;
> + ret = hx8357_spi_write_array(lcdev, buf, 5);
> + if (ret < 0)
> + return ret;
> +
> + buf[0] = HX8357_SET_GAMMA;
> + buf[1] = 0x00;
> + buf[2] = 0x15;
> + buf[3] = 0x00;
> + buf[4] = 0x22;
> + buf[5] = 0x00;
> + buf[6] = 0x08;
> + buf[7] = 0x77;
> + buf[8] = 0x26;
> + buf[9] = 0x77;
> + buf[10] = 0x22;
> + buf[11] = 0x04;
> + buf[12] = 0x00;
> + ret = hx8357_spi_write_array(lcdev, buf, 13);
> + if (ret < 0)
> + return ret;
> +
> + buf[0] = HX8357_SET_ADDRESS_MODE;
> + buf[1] = 0xc0;
> + ret = hx8357_spi_write_array(lcdev, buf, 2);
> + if (ret < 0)
> + return ret;
> +
> + buf[0] = HX8357_SET_PIXEL_FORMAT;
> + buf[1] = HX8357_SET_PIXEL_FORMAT_DPI_18BIT |
> + HX8357_SET_PIXEL_FORMAT_DBI_18BIT;
> + ret = hx8357_spi_write_array(lcdev, buf, 2);
> + if (ret < 0)
> + return ret;
> +
> + buf[0] = HX8357_SET_COLUMN_ADDRESS;
> + buf[1] = 0x00;
> + buf[2] = 0x00;
> + buf[3] = 0x01;
> + buf[4] = 0x3f;
> + ret = hx8357_spi_write_array(lcdev, buf, 5);
> + if (ret < 0)
> + return ret;
> +
> + buf[0] = HX8357_SET_PAGE_ADDRESS;
> + buf[1] = 0x00;
> + buf[2] = 0x00;
> + buf[3] = 0x01;
> + buf[4] = 0xdf;
> + ret = hx8357_spi_write_array(lcdev, buf, 5);
> + if (ret < 0)
> + return ret;
> +
> + buf[0] = HX8357_SET_RGB;
> + buf[1] = 0x02;
> + ret = hx8357_spi_write_array(lcdev, buf, 2);
> + if (ret < 0)
> + return ret;
> +
> + buf[0] = HX8357_SET_DISPLAY_MODE;
> + buf[1] = HX8357_SET_DISPLAY_MODE_RGB_THROUGH |
> + HX8357_SET_DISPLAY_MODE_RGB_INTERFACE;
> + ret = hx8357_spi_write_array(lcdev, buf, 2);
> + if (ret < 0)
> + return ret;
> +
> + ret = hx8357_spi_write_byte(lcdev, HX8357_EXIT_SLEEP_MODE);
> + if (ret < 0)
> + return ret;
> +
> + msleep(120);
> +
> + ret = hx8357_spi_write_byte(lcdev, HX8357_SET_DISPLAY_ON);
> + if (ret < 0)
> + return ret;
> +
> + usleep_range(5000, 7000);
> +
> + ret = hx8357_spi_write_byte(lcdev, HX8357_WRITE_MEMORY_START);
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +#define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
> +
> +static int hx8357_set_power(struct lcd_device *lcdev, int power)
> +{
> + struct hx8357_data *lcd = lcd_get_data(lcdev);
> + int ret = 0;
> +
> + if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->state))
> + ret = hx8357_exit_standby(lcdev);
> + else if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->state))
> + ret = hx8357_enter_standby(lcdev);
> +
> + if (ret == 0)
> + lcd->state = power;
> + else
> + dev_warn(&lcdev->dev, "failed to set power mode %d\n", power);
> +
> + return ret;
> +}
> +
> +static int hx8357_get_power(struct lcd_device *lcdev)
> +{
> + struct hx8357_data *lcd = lcd_get_data(lcdev);
> +
> + return lcd->state;
> +}
> +
> +static struct lcd_ops hx8357_ops = {
> + .set_power = hx8357_set_power,
> + .get_power = hx8357_get_power,
> +};
> +
> +static int __devinit hx8357_probe(struct spi_device *spi)
Please remove '__devexit', because it is useless.
Also, it makes build error.
(http://comments.gmane.org/gmane.linux.kernel/1395773)
> +{
> + struct lcd_device *lcdev;
> + struct hx8357_data *lcd;
> + int i, ret;
> +
> + lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
> + if (!lcd) {
> + dev_err(&spi->dev, "Couldn't allocate lcd internal structure!\n");
> + return -ENOMEM;
> + }
> +
> + ret = spi_setup(spi);
> + if (ret < 0) {
> + dev_err(&spi->dev, "SPI setup failed.\n");
> + return ret;
> + }
> +
> + lcd->spi = spi;
> +
> + lcd->reset = of_get_named_gpio(spi->dev.of_node, "gpios-reset", 0);
> + if (!gpio_is_valid(lcd->reset)) {
> + dev_err(&spi->dev, "Missing dt property: gpios-reset\n");
> + return -EINVAL;
> + }
> +
> + ret = devm_gpio_request_one(&spi->dev, lcd->reset,
> + GPIOF_OUT_INIT_HIGH,
> + "hx8357-reset");
> + if (ret) {
> + dev_err(&spi->dev,
> + "failed to request gpio %d: %d\n",
> + lcd->reset, ret);
> + return -EINVAL;
> + }
> +
> + for (i = 0; i < HX8357_NUM_IM_PINS; i++) {
> + lcd->im_pins[i] = of_get_named_gpio(spi->dev.of_node,
> + "im-gpios", i);
> + if (lcd->im_pins[i] == -EPROBE_DEFER) {
> + dev_info(&spi->dev, "GPIO requested is not here yet, deferring the probe\n");
> + return -EPROBE_DEFER;
> + }
> + if (!gpio_is_valid(lcd->im_pins[i])) {
> + dev_err(&spi->dev, "Missing dt property: im-gpios\n");
> + return -EINVAL;
> + }
> +
> + ret = devm_gpio_request_one(&spi->dev, lcd->im_pins[i],
> + GPIOF_DIR_OUT, "im_pins");
Please replace GPIOF_DIR_OUT with GPIOF_OUT_INIT_LOW.
If you want to set Low value, please use GPIOF_OUT_INIT_LOW.
> + if (ret) {
> + dev_err(&spi->dev, "failed to request gpio %d: %d\n",
> + lcd->im_pins[i], ret);
> + return -EINVAL;
> + }
> + }
> +
> + lcdev = lcd_device_register("mxsfb", &spi->dev, lcd, &hx8357_ops);
> + if (IS_ERR(lcdev)) {
> + ret = PTR_ERR(lcdev);
> + return ret;
> + }
> + spi_set_drvdata(spi, lcdev);
> +
> + ret = hx8357_lcd_init(lcdev);
> + if (ret) {
> + dev_err(&spi->dev, "Couldn't initialize panel\n");
> + goto init_error;
> + }
> +
> + dev_info(&spi->dev, "Panel probed\n");
> +
> + return 0;
> +
> +init_error:
> + lcd_device_unregister(lcdev);
> + return ret;
> +}
> +
> +static int __devexit hx8357_remove(struct spi_device *spi)
Please remove '__devexit'.
> +{
> + struct lcd_device *lcdev = spi_get_drvdata(spi);
> +
> + lcd_device_unregister(lcdev);
> + return 0;
> +}
> +
> +static const struct of_device_id hx8357_dt_ids[] = {
> + { .compatible = "himax,hx8357" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, hx8357_dt_ids);
> +
> +static struct spi_driver hx8357_driver = {
> + .probe = hx8357_probe,
> + .remove = __devexit_p(hx8357_remove),
Please remove '__devexit_p'.
> + .driver = {
> + .name = "hx8357",
> + .of_match_table = of_match_ptr(hx8357_dt_ids),
> + },
> +};
> +
> +module_spi_driver(hx8357_driver);
> +
> +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
> +MODULE_DESCRIPTION("Himax HX-8357 LCD Driver");
> +MODULE_LICENSE("GPL");
> --
> 1.7.10.4
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH 0/5] mmc: mvsdio: use devm_ API to simplify/correct error paths.
From: Jason Cooper @ 2013-01-23 1:44 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1358342040-7130-1-git-send-email-andrew@lunn.ch>
On Wed, Jan 16, 2013 at 02:13:55PM +0100, Andrew Lunn wrote:
> Russell King pointed out a number of issues with the probe code in the
> mvsdio driver and supplied a patch. This patchset uses a modified
> version, converting resource allocations to devm API calls, and fixes
> the error paths. The patches that then follow are from Thomas
> Petazzoni making other cleanups and adding device tree. These patches
> needed some changes after the swap to devm. The patches here replace
> the earlier submitted patches with the same Subject.
>
> Andrew Lunn (1):
> mmc: mvsdio: use devm_ API to simplify/correct error paths.
Applied to mvebu/fixes.
> Thomas Petazzoni (4):
> mmc: mvsdio: use slot-gpio infrastructure for write protect gpio
> mmc: mvsdio: use slot-gpio for card detect gpio
> mmc: mvsdio: implement a Device Tree binding
> mmc: mvsdio: add pinctrl integration
Re-applied to mvebu/drivers
> .../devicetree/bindings/mmc/orion-sdio.txt | 17 ++
> drivers/mmc/host/mvsdio.c | 187 ++++++++------------
> 2 files changed, 93 insertions(+), 111 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/mmc/orion-sdio.txt
thx,
Jason.
^ permalink raw reply
* [PATCH v5 9/9] ARM: davinci: da850: Added dsp clock definition
From: Tivy, Robert @ 2013-01-23 1:37 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <50FE802E.2020306@ti.com>
> -----Original Message-----
> From: Nori, Sekhar
> Sent: Tuesday, January 22, 2013 4:04 AM
>
> Rob,
>
> On 1/11/2013 5:53 AM, Robert Tivy wrote:
> > Added dsp clock definition, keyed to "davinci-rproc.0".
> >
> > Signed-off-by: Robert Tivy <rtivy@ti.com>
> > ---
> > arch/arm/mach-davinci/da850.c | 9 +++++++++
> > 1 file changed, 9 insertions(+)
> >
> > diff --git a/arch/arm/mach-davinci/da850.c b/arch/arm/mach-
> davinci/da850.c
> > index 097fcb2..50107c5 100644
> > --- a/arch/arm/mach-davinci/da850.c
> > +++ b/arch/arm/mach-davinci/da850.c
> > @@ -375,6 +375,14 @@ static struct clk sata_clk = {
> > .flags = PSC_FORCE,
> > };
> >
> > +static struct clk dsp_clk = {
> > + .name = "dsp",
> > + .parent = &pll0_sysclk1,
> > + .domain = DAVINCI_GPSC_DSPDOMAIN,
> > + .lpsc = DA8XX_LPSC0_GEM,
> > + .flags = PSC_LRST,
> > +};
> > +
> > static struct clk_lookup da850_clks[] = {
> > CLK(NULL, "ref", &ref_clk),
> > CLK(NULL, "pll0", &pll0_clk),
> > @@ -421,6 +429,7 @@ static struct clk_lookup da850_clks[] = {
> > CLK("spi_davinci.1", NULL, &spi1_clk),
> > CLK("vpif", NULL, &vpif_clk),
> > CLK("ahci", NULL, &sata_clk),
> > + CLK("davinci-rproc.0", NULL, &dsp_clk),
>
> Adding this clock node without the having the driver probed leads to
> kernel hang while booting. With CONFIG_DAVINCI_RESET_CLOCKS=n, the
> kernel boot fine. It looks like there is some trouble disabling the
> clock if it is not used. Can you please check this issue?
>
> Thanks,
> Sekhar
Sekhar,
Thankyou for trying that out.
I discovered that the kernel boot hang is caused when trying to disable this clk during init, from within the function davinci_clk_disable_unused(). That function calls davinci_psc_config() which attempts to disable the DSP clock. Since this clk is enabled after reset, disabling it involves a state transition, and therefore the function must wait for GOSTAT[1] to be 0. For most peripherals this happens automatically, but for the DSP (and ARM, too), the DSP must execute the IDLE instruction to allow a clock enable->disable transition. Since this is bootup and there is no real program on the DSP yet, this doesn't happen.
I was able to overcome this by adding PSC_FORCE to the clk->flags for dsp_clk. In fact, without PSC_FORCE I was not able to "rmmod davinci_remoteproc" since the firmware file that I'm loading did not execute IDLE. It feels like for davinci we should have a way to control the clk->flags' PSC_FORCE setting at run-time. The PSC_FORCE would be set initially (during boot) and the user (or system integrator) would have a way to unset it dynamically. That way, when the DSP firmware does actually have a structured way to enter IDLE, the user can say "I'd like a structured shutdown" and unset PSC_FORCE (via a clean API). But for now I'm just going to turn on PSC_FORCE:
.flags = PSC_LRST | PSC_FORCE,
Thanks & Regards,
- Rob
^ permalink raw reply
* [GIT PULL] ARM: mvebu fixes for v3.8-rc5
From: Jason Cooper @ 2013-01-23 1:29 UTC (permalink / raw)
To: linux-arm-kernel
The following changes since commit 11d5993df2e1f65e3acfeb92a3febe88803b3e7f:
arm: mvebu: Fix memory size for Armada 370 DB (2013-01-10 19:16:51 +0000)
are available in the git repository at:
git://git.infradead.org/users/jcooper/linux.git tags/mvebu_fixes_for_v3.8-rc5
for you to fetch changes up to 09d75bc7d217bd8868899028a98b53423e6b3324:
ARM: kirkwood: fix missing #interrupt-cells property (2013-01-23 01:10:48 +0000)
----------------------------------------------------------------
mvebu fixes for v3.8-rc5
- fix memory leak in mvebu/clk-cpu.c
- use devm_ to correct/simplify error paths in mvsdio
- add missing #interrupt-cells property in kirkwood
----------------------------------------------------------------
Andrew Lunn (1):
mmc: mvsdio: use devm_ API to simplify/correct error paths.
Cong Ding (1):
clk: mvebu/clk-cpu.c: fix memory leakage
Sebastian Hesselbarth (1):
ARM: kirkwood: fix missing #interrupt-cells property
arch/arm/boot/dts/kirkwood.dtsi | 2 +
drivers/clk/mvebu/clk-cpu.c | 9 ++--
drivers/mmc/host/mvsdio.c | 92 ++++++++++++++---------------------------
3 files changed, 38 insertions(+), 65 deletions(-)
^ permalink raw reply
* [PATCH] ARM: kirkwood: fix missing #interrupt-cells property
From: Jason Cooper @ 2013-01-23 1:11 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1358883993-17280-1-git-send-email-sebastian.hesselbarth@gmail.com>
On Tue, Jan 22, 2013 at 08:46:33PM +0100, Sebastian Hesselbarth wrote:
> The gpio controller on kirkwood can provide interrupts but is missing
> the #interrupt-cells property. This patch just adds it to both gpio
> controllers.
>
> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> ---
> Cc: Russell King <linux@arm.linux.org.uk>
> Cc: Andrew Lunn <andrew@lunn.ch>
> Cc: Jason Cooper <jason@lakedaemon.net>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
> Cc: Jamie Lentin <jm@lentin.co.uk>
> Cc: Jean-Francois Moine <moinejf@free.fr>
> Cc: linux-arm-kernel at lists.infradead.org
> Cc: linux-kernel at vger.kernel.org
> ---
> arch/arm/boot/dts/kirkwood.dtsi | 2 ++
> 1 file changed, 2 insertions(+)
Applied to mvebu/fixes
thx,
Jason.
^ permalink raw reply
* [PATCH 1/5] mmc: mvsdio: use devm_ API to simplify/correct error paths.
From: Jason Cooper @ 2013-01-23 1:09 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1358342040-7130-2-git-send-email-andrew@lunn.ch>
On Wed, Jan 16, 2013 at 02:13:56PM +0100, Andrew Lunn wrote:
> There are a number of bugs in the error paths of this driver. Make
> use of devm_ functions to simplify the cleanup on error.
>
> Based on a patch by Russell King.
>
> Signed-off-by: Andrew Lunn <andrew@lunn.ch>
> ---
> drivers/mmc/host/mvsdio.c | 92 +++++++++++++++------------------------------
> 1 file changed, 30 insertions(+), 62 deletions(-)
Applied to mvebu/fixes
thx,
Jason.
^ permalink raw reply
* [PATCH v3] clk: mvebu/clk-cpu.c: fix memory leakage
From: Jason Cooper @ 2013-01-23 1:08 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20130115184423.GC7211@gmail.com>
On Tue, Jan 15, 2013 at 07:44:26PM +0100, Cong Ding wrote:
> the variable cpuclk and clk_name should be properly freed when error happens.
>
> Signed-off-by: Cong Ding <dinggnu@gmail.com>
> Acked-by: Jason Cooper <jason@lakedaemon.net>
> ---
> drivers/clk/mvebu/clk-cpu.c | 9 ++++++---
> 1 file changed, 6 insertions(+), 3 deletions(-)
Applied to mvebu/fixes with MikeT's Ack.
thx,
Jason.
^ permalink raw reply
* [PATCH 1/2] usb: host: tegra: don't touch EMC clock
From: Greg Kroah-Hartman @ 2013-01-23 1:06 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1358900903-27654-1-git-send-email-swarren@wwwdotorg.org>
On Tue, Jan 22, 2013 at 05:28:22PM -0700, Stephen Warren wrote:
> From: Stephen Warren <swarren@nvidia.com>
>
> Clock "emc" is for the External Memory Controller. The USB driver has no
> business touching this clock directly. Remove the code that does so.
>
> Signed-off-by: Stephen Warren <swarren@nvidia.com>
> ---
> Greg, Alan, I'd like to take this patch through the Tegra tree to avoid
> any merge conflicts with the Tegra USB changes that have recently
> happened there.
>
> Venu, When creating your patch to convert the Tegra USB PHY driver to a
> platform driver, can you assume these patches are applied first? Thanks.
> I assume that these patches make sense to you; could you ack them if so.
> ---
> drivers/usb/host/ehci-tegra.c | 17 -----------------
> 1 file changed, 17 deletions(-)
I always love to see code deleted, so feel free to take this through
your tree:
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
^ permalink raw reply
* [PATCH 3/6 v14] gpio: Add userland device interface to block GPIO
From: Jonathan Corbet @ 2013-01-23 1:03 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1358856404-8975-4-git-send-email-stigge@antcom.de>
On Tue, 22 Jan 2013 13:06:41 +0100
Roland Stigge <stigge@antcom.de> wrote:
> This patch adds a character device interface to the block GPIO system.
So I was looking at this, and a couple of things caught my eye...
> +static int gpio_block_fop_open(struct inode *in, struct file *f)
> +{
> + int i;
> + struct gpio_block *block = gpio_block_find_by_minor(MINOR(in->i_rdev));
> + int status;
> + int irq;
> +
> + if (!block)
> + return -ENOENT;
> +
> + block->irq_controlled = false;
> + block->got_int = false;
> + spin_lock_init(&block->lock);
So... there is no protection I can find against multiple opens here.
Meaning that the second process to open the device will reinitialize the
lock (and other variables), regardless of their current state.
More to the point, though, I'm not at all clear on what this lock protects?
It seems to be restricted to the got_int flag, which could be manipulated -
without locks - with bitops? Or am I missing something?
> + init_waitqueue_head(&block->wait_queue);
> + f->private_data = block;
> +
> + for (i = 0; i < block->ngpio; i++) {
> + status = gpio_request(block->gpio[i], block->name);
Hmm... the documentation for the API says that gpio_request() has to be
called separately. But now you're doing it here? That's probably OK, but
calling gpio_free() at close time could lead to interesting results if the
code that set up the block expects them to still be allocated. It seems
like the API should be consistent with regard to this - either call
gpio_request() when the block is created, or always require the caller to
do it.
A quick look shows that the sysfs interface does the same thing? So now
you're already double-requesting and freeing the gpios?
> + if (status)
> + goto err1;
> +
> + irq = gpio_to_irq(block->gpio[i]);
> + if (irq >= 0 &&
> + !test_bit(FLAG_IS_OUT, &gpio_desc[block->gpio[i]].flags) &&
> + !gpio_block_is_irq_duplicate(block, i)) {
> + status = request_irq(irq, gpio_block_irq_handler,
> + IRQF_SHARED,
> + block->name, block);
> + if (status)
> + goto err2;
> +
> + block->irq_controlled = true;
> + }
> + }
This forces the block to work in the IRQ-driven mode if a line is capable
of it, regardless of whether the creator (or the process calling open())
wants that. It seems like that should be controllable separately?
> +
> + return 0;
> +
> +err1:
> + while (i > 0) {
> + i--;
> +
> + irq = gpio_to_irq(block->gpio[i]);
> + if (irq >= 0 &&
> + !test_bit(FLAG_IS_OUT, &gpio_desc[block->gpio[i]].flags) &&
> + !gpio_block_is_irq_duplicate(block, i))
> + free_irq(irq, block);
> +err2:
> + gpio_free(block->gpio[i]);
Um...wait...you're jumping into the middle of the while loop? I guess that
will work, but ... hmm...
> + }
> + return status;
> +}
> +
> +static int gpio_block_fop_release(struct inode *in, struct file *f)
> +{
> + int i;
> + struct gpio_block *block = (struct gpio_block *)f->private_data;
Is there anything that will have prevented a call to gpio_block_free()
while the device is open? This seems like a concern for all of the fops
here.
> + for (i = 0; i < block->ngpio; i++) {
> + int irq = gpio_to_irq(block->gpio[i]);
> +
> + if (irq >= 0 &&
> + !test_bit(FLAG_IS_OUT, &gpio_desc[block->gpio[i]].flags) &&
> + !gpio_block_is_irq_duplicate(block, i))
> + free_irq(irq, block);
> +
> + gpio_free(block->gpio[i]);
> + }
> +
> + return 0;
> +}
> +
> +static int got_int(struct gpio_block *block)
> +{
> + unsigned long flags;
> + int result;
> +
> + spin_lock_irqsave(&block->lock, flags);
> + result = block->got_int;
> + spin_unlock_irqrestore(&block->lock, flags);
The lock doesn't really buy you much here. Might you have wanted to reset
block->got_int here too?
> +
> + return result;
> +}
> +
> +static ssize_t gpio_block_fop_read(struct file *f, char __user *buf, size_t n,
> + loff_t *offset)
> +{
> + struct gpio_block *block = (struct gpio_block *)f->private_data;
> + int err;
> + unsigned long flags;
> +
> + if (block->irq_controlled) {
> + if (!(f->f_flags & O_NONBLOCK))
> + wait_event_interruptible(block->wait_queue,
> + got_int(block));
> + spin_lock_irqsave(&block->lock, flags);
> + block->got_int = 0;
> + spin_unlock_irqrestore(&block->lock, flags);
> + }
If two processes are waiting on the device, they might both wake up on the
same interrupt. The second might even reset block->got_int after *another*
interrupt has arrived, causing it to be lost. Or am I missing something?
> + if (n >= sizeof(unsigned long)) {
> + unsigned long values = gpio_block_get(block, block->cur_mask);
> +
> + err = put_user(values, (unsigned long __user *)buf);
> + if (err)
> + return err;
> +
> + return sizeof(unsigned long);
> + }
> + return 0;
And here you've consumed the interrupt even in the case where you'll not
actually return the gpios or return any data. This one could maybe be
considered to be user-space programmer error, but still...
> +}
> +
> +static ssize_t gpio_block_fop_write(struct file *f, const char __user *buf,
> + size_t n, loff_t *offset)
> +{
> + struct gpio_block *block = (struct gpio_block *)f->private_data;
> + int err;
> +
> + if (n >= sizeof(unsigned long)) {
> + unsigned long values;
> +
> + err = get_user(values, (unsigned long __user *)buf);
> + if (err)
> + return err;
> + if (gpio_block_is_output(block))
> + gpio_block_set(block, block->cur_mask, values);
> + else
> + return -EPERM;
Is EPERM right? Or maybe EINVAL?
> + return sizeof(unsigned long);
> + }
> + return 0;
> +}
> +
> +static long gpio_block_fop_ioctl(struct file *f, unsigned int cmd,
> + unsigned long arg)
> +{
> + struct gpio_block *block = (struct gpio_block *)f->private_data;
> + unsigned long __user *x = (unsigned long __user *)arg;
> +
> + if (cmd == 0)
> + return get_user(block->cur_mask, x);
...and this is a little weird. It seems you should define a proper ioctl()
command code like everybody else does.
> + return -EINVAL;
> +}
> +
> +static unsigned int gpio_block_fop_poll(struct file *f,
> + struct poll_table_struct *pt)
> +{
> + struct gpio_block *block = (struct gpio_block *)f->private_data;
> +
> + if (!block->irq_controlled)
> + return -ENOSYS;
Is that what you want, or should you just return POLLIN|POLLOUT in this
case?
> + if (!got_int(block))
> + poll_wait(f, &block->wait_queue, pt);
> +
> + if (got_int(block))
> + return POLLIN;
How about
if (got_int(block))
return POLLIN;
poll_wait(f, &block->wait_queue, pt);
?
jon
^ permalink raw reply
* [PATCH 2/2] ARM: tegra: add clocks properties to USB PHY nodes
From: Stephen Warren @ 2013-01-23 0:33 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1358900903-27654-2-git-send-email-swarren@wwwdotorg.org>
On 01/22/2013 05:28 PM, Stephen Warren wrote:
> The patch to add USB PHY nodes to device tree was written before Tegra
> supported the clocks property in device tree. Now that it does, add the
> required clocks properties to these nodes.
>
> This will allow all clk_get_sys() calls in tegra_usb_phy.c to be replaced
> by clk_get(phy->dev, clock_name), as part of converting the PHY driver to
> a platform driver.
> diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi
> + clocks = <&tegra_car 22>, <&tegra_car 127>;
> + clock-names = "utmi", "pll_u";
...
> + clocks = <&tegra_car 94>, <&tegra_car 127>;
> + clock-names = "ulpi", "pll_u";
Hmmm. Thinking about that first clock more, if we name it just "phy" in
both the UTMI and ULPI PHY nodes, we could make tegra_phy_init() perform
the clk_get() for all PHY types, and use the same clock name everywhere,
and hence remove the type-specific clk_get()s from tegra_phy_init() and
utmip_pad_open().
Venu, will this work for other chips such as Tegra30/Tegra114 and so on
into the future, or do chips after Tegra20 introduce any new clocks, and
hence break this scheme?
^ permalink raw reply
* [PATCH 2/2] ARM: tegra: add clocks properties to USB PHY nodes
From: Stephen Warren @ 2013-01-23 0:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1358900903-27654-1-git-send-email-swarren@wwwdotorg.org>
From: Stephen Warren <swarren@nvidia.com>
The patch to add USB PHY nodes to device tree was written before Tegra
supported the clocks property in device tree. Now that it does, add the
required clocks properties to these nodes.
This will allow all clk_get_sys() calls in tegra_usb_phy.c to be replaced
by clk_get(phy->dev, clock_name), as part of converting the PHY driver to
a platform driver.
Signed-off-by: Stephen Warren <swarren@nvidia.com>
---
arch/arm/boot/dts/tegra20.dtsi | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi
index 2e9d1f0..d5e72ce 100644
--- a/arch/arm/boot/dts/tegra20.dtsi
+++ b/arch/arm/boot/dts/tegra20.dtsi
@@ -442,18 +442,24 @@
reg = <0xc5000400 0x3c00>;
phy_type = "utmi";
nvidia,has-legacy-mode;
+ clocks = <&tegra_car 22>, <&tegra_car 127>;
+ clock-names = "utmi", "pll_u";
};
phy2: usb-phy at c5004400 {
compatible = "nvidia,tegra20-usb-phy";
reg = <0xc5004400 0x3c00>;
phy_type = "ulpi";
+ clocks = <&tegra_car 94>, <&tegra_car 127>;
+ clock-names = "ulpi", "pll_u";
};
phy3: usb-phy at c5008400 {
compatible = "nvidia,tegra20-usb-phy";
reg = <0xc5008400 0x3C00>;
phy_type = "utmi";
+ clocks = <&tegra_car 22>, <&tegra_car 127>;
+ clock-names = "utmi", "pll_u";
};
usb at c5000000 {
--
1.7.10.4
^ permalink raw reply related
* [PATCH 1/2] usb: host: tegra: don't touch EMC clock
From: Stephen Warren @ 2013-01-23 0:28 UTC (permalink / raw)
To: linux-arm-kernel
From: Stephen Warren <swarren@nvidia.com>
Clock "emc" is for the External Memory Controller. The USB driver has no
business touching this clock directly. Remove the code that does so.
Signed-off-by: Stephen Warren <swarren@nvidia.com>
---
Greg, Alan, I'd like to take this patch through the Tegra tree to avoid
any merge conflicts with the Tegra USB changes that have recently
happened there.
Venu, When creating your patch to convert the Tegra USB PHY driver to a
platform driver, can you assume these patches are applied first? Thanks.
I assume that these patches make sense to you; could you ack them if so.
---
drivers/usb/host/ehci-tegra.c | 17 -----------------
1 file changed, 17 deletions(-)
diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c
index 1f596fb..b02622a 100644
--- a/drivers/usb/host/ehci-tegra.c
+++ b/drivers/usb/host/ehci-tegra.c
@@ -44,7 +44,6 @@ struct tegra_ehci_hcd {
struct ehci_hcd *ehci;
struct tegra_usb_phy *phy;
struct clk *clk;
- struct clk *emc_clk;
struct usb_phy *transceiver;
int host_resumed;
int port_resuming;
@@ -56,7 +55,6 @@ static void tegra_ehci_power_up(struct usb_hcd *hcd)
{
struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller);
- clk_prepare_enable(tegra->emc_clk);
clk_prepare_enable(tegra->clk);
usb_phy_set_suspend(&tegra->phy->u_phy, 0);
tegra->host_resumed = 1;
@@ -69,7 +67,6 @@ static void tegra_ehci_power_down(struct usb_hcd *hcd)
tegra->host_resumed = 0;
usb_phy_set_suspend(&tegra->phy->u_phy, 1);
clk_disable_unprepare(tegra->clk);
- clk_disable_unprepare(tegra->emc_clk);
}
static int tegra_ehci_internal_port_reset(
@@ -694,16 +691,6 @@ static int tegra_ehci_probe(struct platform_device *pdev)
if (err)
goto fail_clk;
- tegra->emc_clk = devm_clk_get(&pdev->dev, "emc");
- if (IS_ERR(tegra->emc_clk)) {
- dev_err(&pdev->dev, "Can't get emc clock\n");
- err = PTR_ERR(tegra->emc_clk);
- goto fail_emc_clk;
- }
-
- clk_prepare_enable(tegra->emc_clk);
- clk_set_rate(tegra->emc_clk, 400000000);
-
tegra->needs_double_reset = of_property_read_bool(pdev->dev.of_node,
"nvidia,needs-double-reset");
@@ -813,8 +800,6 @@ fail:
#endif
usb_phy_shutdown(&tegra->phy->u_phy);
fail_io:
- clk_disable_unprepare(tegra->emc_clk);
-fail_emc_clk:
clk_disable_unprepare(tegra->clk);
fail_clk:
usb_put_hcd(hcd);
@@ -842,8 +827,6 @@ static int tegra_ehci_remove(struct platform_device *pdev)
clk_disable_unprepare(tegra->clk);
- clk_disable_unprepare(tegra->emc_clk);
-
return 0;
}
--
1.7.10.4
^ permalink raw reply related
* [PATCH] ARM: shmobile: ipmmu: Add basic PMB support
From: Laurent Pinchart @ 2013-01-23 0:04 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <50FDFA17.5050107@igel.co.jp>
Hi Damian,
On Tuesday 22 January 2013 11:31:51 Damian Hobson-Garcia wrote:
> On 2013/01/21 22:12, Laurent Pinchart wrote:
> > On Friday 18 January 2013 15:35:10 Damian Hobson-Garcia wrote:
> >> The PMB can be used to remap 16, 64, 128 or 512 MiB pages from
> >> the 0x80000000-0xffffffff address range to anywhere in the
> >> 0x00000000-0x7fffffff range.
> >
> > Isn't it 0x80000000 - 0xbfffffff to 0x00000000 - 0xffffffff ?
>
> Yes, looking again at the spec, your values are the correct ones.
>
> >> It also has the ability to perform tiled-linear address translation,
> >> which can be used to access a memory buffer as a series of n x m tiles,
> >> useful for image encoding/decoding.
> >> Currently only the userspace API via character device is supported.
> >
> > If I understand this correctly, you're allowing userspace to remap a
> > virtual address block to a physical address block without performing any
> > sanity check. Isn't that a major security issue ?
>
> No, not really. The PMB will only remap physical addresses, not virtual
> addresses. Moreover, the remapped address is only accessible from the
> IP blocks which are on the ICB bus, not the CPU. I will update the
> comment to mention this. These IP blocks already have access to the
> entire physical memory address space, so I don't think that adding the
> the PMB into mix introduces any new security issues.
The IP block will be programmed through a driver that will control where it
writes to/reads from. Adding the PMB will remap those read/write operations
without notifying the driver, opening the door to potential issues.
What are the use cases for controlling the PMB from userspace ?
> ...
>
> >> +
> >> +#define PMB_DEVICE_NAME "pmb"
> >> +
> >> +#define PMB_NR 16
> >> +/* the smallest size that can be reserverd in the pmb */
> >> +#define PMB_GRANULARITY (16 << 20)
> >> +#define PMB_START_ADDR 0x80000000
> >> +#define PMB_SIZE 0x40000000
> >> +#define NUM_BITS(x) ((x) / PMB_GRANULARITY)
> >> +#define NR_BITMAPS ((NUM_BITS(PMB_SIZE) + BITS_PER_LONG - 1) \
> >> + >> ilog2(BITS_PER_LONG))
> >
> > Does ilog2(BITS_PER_LONG) resolve to a compile-time constant ?
>
> Yes it does.
>
> Thanks for your other comments too. I'll look into making those changes.
--
Regards,
Laurent Pinchart
^ permalink raw reply
* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
From: Daniel Vetter @ 2013-01-22 23:41 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1358894185-21617-2-git-send-email-robdclark@gmail.com>
On Tue, Jan 22, 2013 at 04:36:22PM -0600, Rob Clark wrote:
> A simple DRM/KMS driver for the TI LCD Controller found in various
> smaller TI parts (AM33xx, OMAPL138, etc). This driver uses the
> CMA helpers. Currently only the TFP410 DVI encoder is supported
> (tested with beaglebone + DVI cape). There are also various LCD
> displays, for which support can be added (as I get hw to test on),
> and an external i2c HDMI encoder found on some boards.
>
> The display controller supports a single CRTC. And the encoder+
> connector are split out into sub-devices. Depending on which LCD
> or external encoder is actually present, the appropriate output
> module(s) will be loaded.
>
> v1: original
> v2: fix fb refcnting and few other cleanups
> v3: get +/- vsync/hsync from timings rather than panel-info, add
> option DT max-bandwidth field so driver doesn't attempt to
> pick a display mode with too high memory bandwidth, and other
> small cleanups
>
> Signed-off-by: Rob Clark <robdclark@gmail.com>
Ok, read through it, looks nice. No idea whether this should use the panel
stuff, but since no-one else does who cares. A few nits and questions
below. Was a nice reading to learn about kfifo and the runtime power
stuff.
Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
> ---
> drivers/gpu/drm/Kconfig | 2 +
> drivers/gpu/drm/Makefile | 1 +
> drivers/gpu/drm/tilcdc/Kconfig | 10 +
> drivers/gpu/drm/tilcdc/Makefile | 8 +
> drivers/gpu/drm/tilcdc/tilcdc_crtc.c | 597 ++++++++++++++++++++++++++++++++
> drivers/gpu/drm/tilcdc/tilcdc_drv.c | 605 +++++++++++++++++++++++++++++++++
> drivers/gpu/drm/tilcdc/tilcdc_drv.h | 159 +++++++++
> drivers/gpu/drm/tilcdc/tilcdc_regs.h | 154 +++++++++
> drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++++++++++
> drivers/gpu/drm/tilcdc/tilcdc_tfp410.h | 26 ++
> 10 files changed, 1985 insertions(+)
> create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
> create mode 100644 drivers/gpu/drm/tilcdc/Makefile
> create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
> create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
> create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
> create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
> create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
> create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
>
[cut]
> +struct tilcdc_crtc {
> + struct drm_crtc base;
> +
> + const struct tilcdc_panel_info *info;
> + uint32_t dirty;
> + dma_addr_t start, end;
> + struct drm_pending_vblank_event *event;
> + int dpms;
> + wait_queue_head_t frame_done_wq;
> + bool frame_done;
> +
> + /* fb currently set to scanout 0/1: */
> + struct drm_framebuffer *scanout[2];
> +
> + /* for deferred fb unref's: */
> + DECLARE_KFIFO_PTR(unref_fifo, struct drm_framebuffer *);
> + struct work_struct work;
> +};
> +#define to_tilcdc_crtc(x) container_of(x, struct tilcdc_crtc, base)
> +
> +static void unref_worker(struct work_struct *work)
> +{
> + struct tilcdc_crtc *tilcdc_crtc = container_of(work, struct tilcdc_crtc, work);
> + struct drm_device *dev = tilcdc_crtc->base.dev;
> + struct drm_framebuffer *fb;
> +
> + mutex_lock(&dev->mode_config.mutex);
> + while (kfifo_get(&tilcdc_crtc->unref_fifo, &fb))
> + drm_framebuffer_unreference(fb);
> + mutex_unlock(&dev->mode_config.mutex);
Hm, just learned about the kfifo api. It looks like the locking here still
works even with the new modeset locking, since kfifo explicitly allows
concurrent readers and writers without locking, as long as there's only on
of each kind. But maybe switch over to crtc->mutex to make things less
tricky.
Also, kfifo seems to have a new api which allows embedding of the kfifo
thing and needs to be used with kfifo_in/out.
[cut]
> +static void update_scanout(struct drm_crtc *crtc)
> +{
> + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> + struct drm_device *dev = crtc->dev;
> + struct drm_framebuffer *fb = crtc->fb;
> + struct drm_gem_cma_object *gem;
> + unsigned int depth, bpp;
> +
> + drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp);
> + gem = drm_fb_cma_get_gem_obj(fb, 0);
> +
> + tilcdc_crtc->start = gem->paddr + fb->offsets[0] +
> + (crtc->y * fb->pitches[0]) + (crtc->x * bpp/8);
> +
> + tilcdc_crtc->end = tilcdc_crtc->start +
> + (crtc->mode.vdisplay * fb->pitches[0]);
> +
> + if (tilcdc_crtc->dpms == DRM_MODE_DPMS_ON) {
> + /* already enabled, so just mark the frames that need
> + * updating and they will be updated on vblank:
> + */
> + tilcdc_crtc->dirty |= LCDC_END_OF_FRAME0 | LCDC_END_OF_FRAME1;
> + drm_vblank_get(dev, 0);
> + } else {
> + /* not enabled yet, so update registers immediately: */
> + set_scanout(crtc, 0);
> + set_scanout(crtc, 1);
At least on intel we disallow pageflips on disabled crtcs since
drm_vblank_get will fail. So dunno whether this is allowed on other
places, but in any case I don't see a drm_vblank_handle call, which means
we'll miss out on the pageflip completion event ...
> + }
> +}
> +
> +static void start(struct drm_crtc *crtc)
> +{
> + struct drm_device *dev = crtc->dev;
> + struct tilcdc_drm_private *priv = dev->dev_private;
> +
> + if (priv->rev == 2) {
> + tilcdc_set(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
> + msleep(1);
> + tilcdc_clear(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
> + msleep(1);
> + }
> +
> + tilcdc_set(dev, LCDC_DMA_CTRL_REG, LCDC_DUAL_FRAME_BUFFER_ENABLE);
> + tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_PALETTE_LOAD_MODE(DATA_ONLY));
> + tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
> +}
> +
> +static void stop(struct drm_crtc *crtc)
> +{
> + struct drm_device *dev = crtc->dev;
> +
> + tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
> +}
> +
> +static void tilcdc_crtc_destroy(struct drm_crtc *crtc)
> +{
> + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +
> + WARN_ON(tilcdc_crtc->dpms == DRM_MODE_DPMS_ON);
> +
> + drm_crtc_cleanup(crtc);
> + WARN_ON(!kfifo_is_empty(&tilcdc_crtc->unref_fifo));
> + kfifo_free(&tilcdc_crtc->unref_fifo);
> + kfree(tilcdc_crtc);
> +}
> +
> +static int tilcdc_crtc_page_flip(struct drm_crtc *crtc,
> + struct drm_framebuffer *fb,
> + struct drm_pending_vblank_event *event)
> +{
> + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> + struct drm_device *dev = crtc->dev;
> +
> + if (tilcdc_crtc->event) {
> + dev_err(dev->dev, "already pending page flip!\n");
> + return -EBUSY;
> + }
> +
> + crtc->fb = fb;
> + tilcdc_crtc->event = event;
> + update_scanout(crtc);
> +
> + return 0;
> +}
> +
> +static void tilcdc_crtc_dpms(struct drm_crtc *crtc, int mode)
> +{
> + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> + struct drm_device *dev = crtc->dev;
> + struct tilcdc_drm_private *priv = dev->dev_private;
> +
> + /* we really only care about on or off: */
> + if (mode != DRM_MODE_DPMS_ON)
> + mode = DRM_MODE_DPMS_OFF;
> +
> + if (tilcdc_crtc->dpms == mode)
> + return;
> +
> + tilcdc_crtc->dpms = mode;
> +
> + pm_runtime_get_sync(dev->dev);
> +
> + if (mode == DRM_MODE_DPMS_ON) {
> + pm_runtime_forbid(dev->dev);
> + start(crtc);
> + } else {
> + tilcdc_crtc->frame_done = false;
> + stop(crtc);
> +
> + /* if necessary wait for framedone irq which will still come
> + * before putting things to sleep..
> + */
> + if (priv->rev == 2) {
> + int ret = wait_event_timeout(
> + tilcdc_crtc->frame_done_wq,
> + tilcdc_crtc->frame_done,
> + msecs_to_jiffies(50));
> + if (ret == 0)
> + dev_err(dev->dev, "timeout waiting for framedone\n");
> + }
> + pm_runtime_allow(dev->dev);
> + }
> +
> + pm_runtime_put_sync(dev->dev);
> +}
> +
> +static bool tilcdc_crtc_mode_fixup(struct drm_crtc *crtc,
> + const struct drm_display_mode *mode,
> + struct drm_display_mode *adjusted_mode)
> +{
> + return true;
> +}
> +
> +static void tilcdc_crtc_prepare(struct drm_crtc *crtc)
> +{
> + tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
> +}
> +
> +static void tilcdc_crtc_commit(struct drm_crtc *crtc)
> +{
> + tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
> +}
> +
> +static int tilcdc_crtc_mode_set(struct drm_crtc *crtc,
> + struct drm_display_mode *mode,
> + struct drm_display_mode *adjusted_mode,
> + int x, int y,
> + struct drm_framebuffer *old_fb)
> +{
> + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> + struct drm_device *dev = crtc->dev;
> + struct tilcdc_drm_private *priv = dev->dev_private;
> + const struct tilcdc_panel_info *info = tilcdc_crtc->info;
> + uint32_t reg, hbp, hfp, hsw, vbp, vfp, vsw;
> + int ret;
> +
> + ret = tilcdc_crtc_mode_valid(crtc, mode);
> + if (WARN_ON(ret))
> + return ret;
> +
> + if (WARN_ON(!info))
> + return -EINVAL;
> +
> + pm_runtime_get_sync(dev->dev);
> +
> + /* Configure the Burst Size and fifo threshold of DMA: */
> + reg = tilcdc_read(dev, LCDC_DMA_CTRL_REG) & ~0x00000770;
> + switch (info->dma_burst_sz) {
> + case 1:
> + reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_1);
> + break;
> + case 2:
> + reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_2);
> + break;
> + case 4:
> + reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_4);
> + break;
> + case 8:
> + reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_8);
> + break;
> + case 16:
> + reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_16);
> + break;
> + default:
> + return -EINVAL;
> + }
> + reg |= (info->fifo_th << 8);
> + tilcdc_write(dev, LCDC_DMA_CTRL_REG, reg);
> +
> + /* Configure the AC Bias Period and Number of Transitions per Interrupt: */
> + reg = tilcdc_read(dev, LCDC_RASTER_TIMING_2_REG) & ~0x000fff00;
> + reg |= LCDC_AC_BIAS_FREQUENCY(info->ac_bias) |
> + LCDC_AC_BIAS_TRANSITIONS_PER_INT(info->ac_bias_intrpt);
> + tilcdc_write(dev, LCDC_RASTER_TIMING_2_REG, reg);
> +
> + /* Configure timings: */
> + hbp = mode->htotal - mode->hsync_end;
> + hfp = mode->hsync_start - mode->hdisplay;
> + hsw = mode->hsync_end - mode->hsync_start;
> + vbp = mode->vtotal - mode->vsync_end;
> + vfp = mode->vsync_start - mode->vdisplay;
> + vsw = mode->vsync_end - mode->vsync_start;
> +
> + DBG("%dx%d, hbp=%u, hfp=%u, hsw=%u, vbp=%u, vfp=%u, vsw=%u",
> + mode->hdisplay, mode->vdisplay, hbp, hfp, hsw, vbp, vfp, vsw);
> +
> + reg = (((mode->hdisplay >> 4) - 1) << 4) |
> + ((hbp & 0xff) << 24) |
> + ((hfp & 0xff) << 16) |
> + ((hsw & 0x3f) << 10);
> + if (priv->rev == 2)
> + reg |= (((mode->hdisplay >> 4) - 1) & 0x40) >> 3;
> + tilcdc_write(dev, LCDC_RASTER_TIMING_0_REG, reg);
> +
> + reg = ((mode->vdisplay - 1) & 0x3ff) |
> + ((vbp & 0xff) << 24) |
> + ((vfp & 0xff) << 16) |
> + ((vsw & 0x3f) << 10);
> + tilcdc_write(dev, LCDC_RASTER_TIMING_1_REG, reg);
> +
> + /* Configure display type: */
> + reg = tilcdc_read(dev, LCDC_RASTER_CTRL_REG) &
> + ~(LCDC_TFT_MODE | LCDC_MONO_8BIT_MODE | LCDC_MONOCHROME_MODE |
> + LCDC_V2_TFT_24BPP_MODE | LCDC_V2_TFT_24BPP_UNPACK | 0x000ff000);
> + reg |= LCDC_TFT_MODE; /* no monochrome/passive support */
> + if (info->tft_alt_mode)
> + reg |= LCDC_TFT_ALT_ENABLE;
> + if (priv->rev == 2) {
> + unsigned int depth, bpp;
> +
> + drm_fb_get_bpp_depth(crtc->fb->pixel_format, &depth, &bpp);
> + switch (bpp) {
> + case 16:
> + break;
> + case 32:
> + reg |= LCDC_V2_TFT_24BPP_UNPACK;
> + /* fallthrough */
> + case 24:
> + reg |= LCDC_V2_TFT_24BPP_MODE;
> + break;
> + default:
> + dev_err(dev->dev, "invalid pixel format\n");
> + return -EINVAL;
> + }
> + }
> + reg |= info->fdd < 12;
> + tilcdc_write(dev, LCDC_RASTER_CTRL_REG, reg);
> +
> + if (info->invert_pxl_clk)
> + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
> + else
> + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
> +
> + if (info->sync_ctrl)
> + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
> + else
> + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
> +
> + if (info->sync_edge)
> + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
> + else
> + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
> +
> + if (mode->flags & DRM_MODE_FLAG_NHSYNC)
> + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
> + else
> + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
> +
> + if (mode->flags & DRM_MODE_FLAG_NVSYNC)
> + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
> + else
> + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
> +
> + if (info->raster_order)
> + tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
> + else
> + tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
> +
> +
> + update_scanout(crtc);
> + tilcdc_crtc_update_clk(crtc);
> +
> + pm_runtime_put_sync(dev->dev);
> +
> + return 0;
> +}
> +
> +static int tilcdc_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
> + struct drm_framebuffer *old_fb)
> +{
> + update_scanout(crtc);
> + return 0;
> +}
> +
> +static void tilcdc_crtc_load_lut(struct drm_crtc *crtc)
> +{
> +}
> +
> +static const struct drm_crtc_funcs tilcdc_crtc_funcs = {
> + .destroy = tilcdc_crtc_destroy,
> + .set_config = drm_crtc_helper_set_config,
> + .page_flip = tilcdc_crtc_page_flip,
> +};
> +
> +static const struct drm_crtc_helper_funcs tilcdc_crtc_helper_funcs = {
> + .dpms = tilcdc_crtc_dpms,
> + .mode_fixup = tilcdc_crtc_mode_fixup,
> + .prepare = tilcdc_crtc_prepare,
> + .commit = tilcdc_crtc_commit,
> + .mode_set = tilcdc_crtc_mode_set,
> + .mode_set_base = tilcdc_crtc_mode_set_base,
> + .load_lut = tilcdc_crtc_load_lut,
> +};
> +
> +int tilcdc_crtc_max_width(struct drm_crtc *crtc)
> +{
> + struct drm_device *dev = crtc->dev;
> + struct tilcdc_drm_private *priv = dev->dev_private;
> + int max_width = 0;
> +
> + if (priv->rev == 1)
> + max_width = 1024;
> + else if (priv->rev == 2)
> + max_width = 2048;
> +
> + return max_width;
> +}
> +
> +int tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode)
> +{
> + struct tilcdc_drm_private *priv = crtc->dev->dev_private;
> + unsigned int bandwidth;
> +
> + if (mode->hdisplay > tilcdc_crtc_max_width(crtc))
> + return MODE_VIRTUAL_X;
> +
> + /* width must be multiple of 16 */
> + if (mode->hdisplay & 0xf)
> + return MODE_VIRTUAL_X;
> +
> + if (mode->vdisplay > 2048)
> + return MODE_VIRTUAL_Y;
> +
> + /* filter out modes that would require too much memory bandwidth: */
> + bandwidth = mode->hdisplay * mode->vdisplay * drm_mode_vrefresh(mode);
> + if (bandwidth > priv->max_bandwidth)
> + return MODE_BAD;
> +
> + return MODE_OK;
> +}
> +
> +void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,
> + const struct tilcdc_panel_info *info)
> +{
> + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> + tilcdc_crtc->info = info;
> +}
> +
> +void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
> +{
> + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> + struct drm_device *dev = crtc->dev;
> + struct tilcdc_drm_private *priv = dev->dev_private;
> + int dpms = tilcdc_crtc->dpms;
> + unsigned int lcd_clk, div;
> + int ret;
> +
> + pm_runtime_get_sync(dev->dev);
> +
> + if (dpms == DRM_MODE_DPMS_ON)
> + tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
> +
> + /* in raster mode, minimum divisor is 2: */
> + ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);
> + if (ret) {
> + dev_err(dev->dev, "failed to set display clock rate to: %d\n",
> + crtc->mode.clock);
> + goto out;
> + }
> +
> + lcd_clk = clk_get_rate(priv->clk);
> + div = lcd_clk / (crtc->mode.clock * 1000);
> +
> + DBG("lcd_clk=%u, mode clock=%d, div=%u", lcd_clk, crtc->mode.clock, div);
> + DBG("fck=%lu, dpll_disp_ck=%lu", clk_get_rate(priv->clk), clk_get_rate(priv->disp_clk));
> +
> + /* Configure the LCD clock divisor. */
> + tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
> + LCDC_RASTER_MODE);
> +
> + if (priv->rev == 2)
> + tilcdc_set(dev, LCDC_CLK_ENABLE_REG,
> + LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
> + LCDC_V2_CORE_CLK_EN);
> +
> + if (dpms == DRM_MODE_DPMS_ON)
> + tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
> +
> +out:
> + pm_runtime_put_sync(dev->dev);
> +}
> +
> +irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc)
> +{
> + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> + struct drm_device *dev = crtc->dev;
> + struct tilcdc_drm_private *priv = dev->dev_private;
> + uint32_t stat = tilcdc_read_irqstatus(dev);
> +
> + if ((stat & LCDC_SYNC_LOST) && (stat & LCDC_FIFO_UNDERFLOW)) {
> + stop(crtc);
> + dev_err(dev->dev, "error: %08x\n", stat);
> + tilcdc_clear_irqstatus(dev, stat);
> + start(crtc);
> + } else if (stat & LCDC_PL_LOAD_DONE) {
> + tilcdc_clear_irqstatus(dev, stat);
> + } else {
> + struct drm_pending_vblank_event *event;
> + unsigned long flags;
> + uint32_t dirty = tilcdc_crtc->dirty & stat;
> +
> + tilcdc_clear_irqstatus(dev, stat);
> +
> + if (dirty & LCDC_END_OF_FRAME0)
> + set_scanout(crtc, 0);
> +
> + if (dirty & LCDC_END_OF_FRAME1)
> + set_scanout(crtc, 1);
> +
> + drm_handle_vblank(dev, 0);
> +
> + spin_lock_irqsave(&dev->event_lock, flags);
> + event = tilcdc_crtc->event;
> + tilcdc_crtc->event = NULL;
> + if (event)
> + drm_send_vblank_event(dev, 0, event);
> + spin_unlock_irqrestore(&dev->event_lock, flags);
> +
> + if (dirty && !tilcdc_crtc->dirty)
> + drm_vblank_put(dev, 0);
> + }
> +
> + if (priv->rev == 2) {
> + if (stat & LCDC_FRAME_DONE) {
> + tilcdc_crtc->frame_done = true;
> + wake_up(&tilcdc_crtc->frame_done_wq);
> + }
> + tilcdc_write(dev, LCDC_END_OF_INT_IND_REG, 0);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file)
> +{
> + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> + struct drm_pending_vblank_event *event;
> + struct drm_device *dev = crtc->dev;
> + unsigned long flags;
> +
> + /* Destroy the pending vertical blanking event associated with the
> + * pending page flip, if any, and disable vertical blanking interrupts.
> + */
> + spin_lock_irqsave(&dev->event_lock, flags);
> + event = tilcdc_crtc->event;
> + if (event && event->base.file_priv == file) {
> + tilcdc_crtc->event = NULL;
> + event->base.destroy(&event->base);
> + drm_vblank_put(dev, 0);
> + }
> + spin_unlock_irqrestore(&dev->event_lock, flags);
> +}
We need some common (helper function) solution for this kind of cleanup -
have the drivers have a copy&pasta version of it, the others miss it
completely. Maybe we need to keep them on a per-crtc list or something
like that ... Volunteered to look a bit into this?
[cut]
> +/*
> + * Power management:
> + */
> +
> +#if CONFIG_PM_SLEEP
> +static int tilcdc_pm_suspend(struct device *dev)
> +{
> + struct drm_device *ddev = dev_get_drvdata(dev);
> + struct tilcdc_drm_private *priv = ddev->dev_private;
> + unsigned i, n = 0;
> +
> + drm_kms_helper_poll_disable(ddev);
> +
> + /* Save register state: */
> + for (i = 0; i < ARRAY_SIZE(registers); i++)
> + if (registers[i].save && (priv->rev >= registers[i].rev))
> + priv->saved_register[n++] = tilcdc_read(ddev, registers[i].reg);
> +
> + return 0;
> +}
> +
> +static int tilcdc_pm_resume(struct device *dev)
> +{
> + struct drm_device *ddev = dev_get_drvdata(dev);
> + struct tilcdc_drm_private *priv = ddev->dev_private;
> + unsigned i, n = 0;
> +
> + /* Restore register state: */
> + for (i = 0; i < ARRAY_SIZE(registers); i++)
> + if (registers[i].save && (priv->rev >= registers[i].rev))
> + tilcdc_write(ddev, registers[i].reg, priv->saved_register[n++]);
> +
> + drm_kms_helper_poll_enable(ddev);
> +
> + return 0;
> +}
> +#endif
> +
> +static const struct dev_pm_ops tilcdc_pm_ops = {
> + SET_SYSTEM_SLEEP_PM_OPS(tilcdc_pm_suspend, tilcdc_pm_resume)
> +};
Mind the clueless but curious about platform pm stuff: Why are those
suspend/resume functions not associated with the platform device?
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
^ permalink raw reply
* [PATCH] ARM: dts: add mshc controller node for Exynos4x12 SoCs
From: Thomas Abraham @ 2013-01-22 23:25 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CADoNuNee7xbRPJupGvUKVRB8TKq0WZMg1XYk24-ks34fbRPt1Q@mail.gmail.com>
Hi Dongjin,
On 22 January 2013 10:15, Dongjin Kim <tobetter@gmail.com> wrote:
> Hi Thomas,
>
> Good to see your patch, actually I had sent similar one before but no
> one care my patch. And now I feel it seems to be wrong.
>
> But I have a couple of question if I use your patch to enable MSHC
> controller work properly on Exynos4412.
>
> What's the exact form of ".compatible" on board file?
> With your patch, MSHC is not probed at all in my board. The
> ".compatible" has to be 'samsung,exynos5250-dw-mshc' and it works.
>
> I also tried '.compatible = "samsung,exynos5250-dw-mshc",
> "samsung,exynos4412-dw-mshc"', it probes the driver but in the
> function 'dw_mci_exynos_priv_init', priv->ctrl_type always becomes
> DW_MCI_TYPE_EXYNOS5250. Because there is a loop and returns each
> compatible strings in alphanumeric order whatever it is ordered in the
> board file.
>
> I also tried below patch to add a compatible for Exynos4412 to
> 'dw_mci_exynos_match' with its specific data, and it works. What's the
> right direction? If I am missing something or wrong, please correct
> me. :)
Yes, your below patch is the correct thing to do. The dt patches for
dwmmc controller driver were only tested on Exynos5250 based board. So
I had not added the compatible string for Exynos4412 in
'of_match_table' of the driver. Please submit the below change as a
patch (minor comment below).
>
> Many thanks,
> Dongjin.
>
> @@ -184,6 +186,25 @@ static int dw_mci_exynos_setup_bus(struct dw_mci *host,
> return 0;
> }
>
> +/* Exynos4412 controller specific capabilities */
> +static unsigned long exynos4412_dwmmc_caps[4] = {
> + MMC_CAP_UHS_DDR50 | MMC_CAP_1_8V_DDR |
> + MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23,
> + MMC_CAP_CMD23,
> + MMC_CAP_CMD23,
> + MMC_CAP_CMD23,
> +};
Since this is same as the 'exynos5250_dwmmc_caps', it can be reused
for 4412 as well, avoiding duplicate copy 'exynos4412_dwmmc_caps'.
> +
> +static const struct dw_mci_drv_data exynos4412_drv_data = {
> + .caps = exynos4412_dwmmc_caps,
> + .init = dw_mci_exynos_priv_init,
> + .setup_clock = dw_mci_exynos_setup_clock,
> + .prepare_command = dw_mci_exynos_prepare_command,
> + .set_ios = dw_mci_exynos_set_ios,
> + .parse_dt = dw_mci_exynos_parse_dt,
> + .setup_bus = dw_mci_exynos_setup_bus,
> +};
If the above change is done, 'exynos4412_drv_data ' also could be avoided.
> +
> /* Exynos5250 controller specific capabilities */
> static unsigned long exynos5250_dwmmc_caps[4] = {
> MMC_CAP_UHS_DDR50 | MMC_CAP_1_8V_DDR |
> @@ -204,6 +225,8 @@ static const struct dw_mci_drv_data exynos5250_drv_data = {
> };
>
> static const struct of_device_id dw_mci_exynos_match[] = {
> + { .compatible = "samsung,exynos4412-dw-mshc",
> + .data = &exynos4412_drv_data, },
> { .compatible = "samsung,exynos5250-dw-mshc",
> .data = &exynos5250_drv_data, },
> {},
Thanks,
Thomas.
>
>
> On Mon, Jan 21, 2013 at 7:39 PM, Thomas Abraham
> <thomas.abraham@linaro.org> wrote:
>> Commit cea0f256 ("ARM: dts: Add board dts file for ODROID-X") includes a node
>> to describe the board level properties for mshc controller. But the mshc
>> controller node was not added in the Exynos4x12 dtsi file which resulted
>> in the following warning when compiling the dtb files.
>>
>> Warning (reg_format): "reg" property in /mshc at 12550000/slot at 0 has invalid length (4 bytes) (#address-cells == 2, #size-cells == 1)
>> Warning (avoid_default_addr_size): Relying on default #address-cells value for /mshc at 12550000/slot at 0
>> Warning (avoid_default_addr_size): Relying on default #size-cells value for /mshc at 12550000/slot at 0
>>
>> Fix this by adding the mshc controller node for Exynos4x12 SoCs.
>>
>> Cc: Dongjin Kim <tobetter@gmail.com>
>> Cc: Kukjin Kim <kgene.kim@samsung.com>
>> Signed-off-by: Thomas Abraham <thomas.abraham@linaro.org>
>> ---
>> arch/arm/boot/dts/exynos4412.dtsi | 8 ++++++++
>> 1 files changed, 8 insertions(+), 0 deletions(-)
>>
>> diff --git a/arch/arm/boot/dts/exynos4412.dtsi b/arch/arm/boot/dts/exynos4412.dtsi
>> index 78ed377..96f5b66 100644
>> --- a/arch/arm/boot/dts/exynos4412.dtsi
>> +++ b/arch/arm/boot/dts/exynos4412.dtsi
>> @@ -32,4 +32,12 @@
>> interrupts = <0 57 0>, <0 0 0>, <0 0 0>, <0 0 0>,
>> <1 12 0>, <1 12 0>, <1 12 0>, <1 12 0>;
>> };
>> +
>> + mshc at 12550000 {
>> + compatible = "samsung,exynos4412-dw-mshc";
>> + reg = <0x12550000 0x1000>;
>> + interrupts = <0 77 0>;
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> + };
>> };
>> --
>> 1.7.5.4
>>
^ permalink raw reply
* [PATCH 4/4] drm/tilcdc: add support for LCD panels (v4)
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1358894185-21617-1-git-send-email-robdclark@gmail.com>
Add an output panel driver for LCD panels. Tested with LCD3 cape on
beaglebone.
v1: original
v2: s/of_find_node_by_name()/of_get_child_by_name()/ from Pantelis
Antoniou
v3: add backlight support
v4: rebase to latest of video timing helpers
Signed-off-by: Rob Clark <robdclark@gmail.com>
---
drivers/gpu/drm/tilcdc/Kconfig | 3 +
drivers/gpu/drm/tilcdc/Makefile | 1 +
drivers/gpu/drm/tilcdc/tilcdc_drv.c | 3 +
drivers/gpu/drm/tilcdc/tilcdc_panel.c | 443 ++++++++++++++++++++++++++++++++++
drivers/gpu/drm/tilcdc/tilcdc_panel.h | 26 ++
5 files changed, 476 insertions(+)
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.c
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.h
diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
index 99beca2..f468b2b 100644
--- a/drivers/gpu/drm/tilcdc/Kconfig
+++ b/drivers/gpu/drm/tilcdc/Kconfig
@@ -4,6 +4,9 @@ config DRM_TILCDC
select DRM_KMS_HELPER
select DRM_KMS_CMA_HELPER
select DRM_GEM_CMA_HELPER
+ select OF_VIDEOMODE
+ select OF_DISPLAY_TIMING
+ select BACKLIGHT_CLASS_DEVICE
help
Choose this option if you have an TI SoC with LCDC display
controller, for example AM33xx in beagle-bone, DA8xx, or
diff --git a/drivers/gpu/drm/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
index aa9097e..deda656 100644
--- a/drivers/gpu/drm/tilcdc/Makefile
+++ b/drivers/gpu/drm/tilcdc/Makefile
@@ -4,6 +4,7 @@ tilcdc-y := \
tilcdc_crtc.o \
tilcdc_tfp410.o \
tilcdc_slave.o \
+ tilcdc_panel.o \
tilcdc_drv.o
obj-$(CONFIG_DRM_TILCDC) += tilcdc.o
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
index ca76dbe..d10858c 100644
--- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c
+++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
@@ -21,6 +21,7 @@
#include "tilcdc_regs.h"
#include "tilcdc_tfp410.h"
#include "tilcdc_slave.h"
+#include "tilcdc_panel.h"
#include "drm_fb_helper.h"
@@ -589,6 +590,7 @@ static int __init tilcdc_drm_init(void)
DBG("init");
tilcdc_tfp410_init();
tilcdc_slave_init();
+ tilcdc_panel_init();
return platform_driver_register(&tilcdc_platform_driver);
}
@@ -597,6 +599,7 @@ static void __exit tilcdc_drm_fini(void)
DBG("fini");
tilcdc_tfp410_fini();
tilcdc_slave_fini();
+ tilcdc_panel_fini();
platform_driver_unregister(&tilcdc_platform_driver);
}
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_panel.c b/drivers/gpu/drm/tilcdc/tilcdc_panel.c
new file mode 100644
index 0000000..a3c7fe93
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_panel.c
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/backlight.h>
+#include <video/display_timing.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+#include "tilcdc_drv.h"
+
+struct panel_module {
+ struct tilcdc_module base;
+ struct tilcdc_panel_info *info;
+ struct display_timings *timings;
+ struct backlight_device *backlight;
+};
+#define to_panel_module(x) container_of(x, struct panel_module, base)
+
+
+/*
+ * Encoder:
+ */
+
+struct panel_encoder {
+ struct drm_encoder base;
+ struct panel_module *mod;
+};
+#define to_panel_encoder(x) container_of(x, struct panel_encoder, base)
+
+
+static void panel_encoder_destroy(struct drm_encoder *encoder)
+{
+ struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
+ drm_encoder_cleanup(encoder);
+ kfree(panel_encoder);
+}
+
+static void panel_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
+ struct backlight_device *backlight = panel_encoder->mod->backlight;
+
+ if (!backlight)
+ return;
+
+ backlight->props.power = mode == DRM_MODE_DPMS_ON
+ ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
+ backlight_update_status(backlight);
+}
+
+static bool panel_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ /* nothing needed */
+ return true;
+}
+
+static void panel_encoder_prepare(struct drm_encoder *encoder)
+{
+ struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
+ panel_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
+ tilcdc_crtc_set_panel_info(encoder->crtc, panel_encoder->mod->info);
+}
+
+static void panel_encoder_commit(struct drm_encoder *encoder)
+{
+ panel_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+static void panel_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ /* nothing needed */
+}
+
+static const struct drm_encoder_funcs panel_encoder_funcs = {
+ .destroy = panel_encoder_destroy,
+};
+
+static const struct drm_encoder_helper_funcs panel_encoder_helper_funcs = {
+ .dpms = panel_encoder_dpms,
+ .mode_fixup = panel_encoder_mode_fixup,
+ .prepare = panel_encoder_prepare,
+ .commit = panel_encoder_commit,
+ .mode_set = panel_encoder_mode_set,
+};
+
+static struct drm_encoder *panel_encoder_create(struct drm_device *dev,
+ struct panel_module *mod)
+{
+ struct panel_encoder *panel_encoder;
+ struct drm_encoder *encoder;
+ int ret;
+
+ panel_encoder = kzalloc(sizeof(*panel_encoder), GFP_KERNEL);
+ if (!panel_encoder) {
+ dev_err(dev->dev, "allocation failed\n");
+ return NULL;
+ }
+
+ panel_encoder->mod = mod;
+
+ encoder = &panel_encoder->base;
+ encoder->possible_crtcs = 1;
+
+ ret = drm_encoder_init(dev, encoder, &panel_encoder_funcs,
+ DRM_MODE_ENCODER_LVDS);
+ if (ret < 0)
+ goto fail;
+
+ drm_encoder_helper_add(encoder, &panel_encoder_helper_funcs);
+
+ return encoder;
+
+fail:
+ panel_encoder_destroy(encoder);
+ return NULL;
+}
+
+/*
+ * Connector:
+ */
+
+struct panel_connector {
+ struct drm_connector base;
+
+ struct drm_encoder *encoder; /* our connected encoder */
+ struct panel_module *mod;
+};
+#define to_panel_connector(x) container_of(x, struct panel_connector, base)
+
+
+static void panel_connector_destroy(struct drm_connector *connector)
+{
+ struct panel_connector *panel_connector = to_panel_connector(connector);
+ drm_connector_cleanup(connector);
+ kfree(panel_connector);
+}
+
+static enum drm_connector_status panel_connector_detect(
+ struct drm_connector *connector,
+ bool force)
+{
+ return connector_status_connected;
+}
+
+static int panel_connector_get_modes(struct drm_connector *connector)
+{
+ struct drm_device *dev = connector->dev;
+ struct panel_connector *panel_connector = to_panel_connector(connector);
+ struct display_timings *timings = panel_connector->mod->timings;
+ int i;
+
+ for (i = 0; i < timings->num_timings; i++) {
+ struct drm_display_mode *mode = drm_mode_create(dev);
+ struct videomode vm;
+
+ if (videomode_from_timing(timings, &vm, i))
+ break;
+
+ drm_display_mode_from_videomode(&vm, mode);
+
+ mode->type = DRM_MODE_TYPE_DRIVER;
+
+ if (timings->native_mode == i)
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+
+ drm_mode_set_name(mode);
+ drm_mode_probed_add(connector, mode);
+ }
+
+ return i;
+}
+
+static int panel_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct tilcdc_drm_private *priv = connector->dev->dev_private;
+ /* our only constraints are what the crtc can generate: */
+ return tilcdc_crtc_mode_valid(priv->crtc, mode);
+}
+
+static struct drm_encoder *panel_connector_best_encoder(
+ struct drm_connector *connector)
+{
+ struct panel_connector *panel_connector = to_panel_connector(connector);
+ return panel_connector->encoder;
+}
+
+static const struct drm_connector_funcs panel_connector_funcs = {
+ .destroy = panel_connector_destroy,
+ .dpms = drm_helper_connector_dpms,
+ .detect = panel_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+};
+
+static const struct drm_connector_helper_funcs panel_connector_helper_funcs = {
+ .get_modes = panel_connector_get_modes,
+ .mode_valid = panel_connector_mode_valid,
+ .best_encoder = panel_connector_best_encoder,
+};
+
+static struct drm_connector *panel_connector_create(struct drm_device *dev,
+ struct panel_module *mod, struct drm_encoder *encoder)
+{
+ struct panel_connector *panel_connector;
+ struct drm_connector *connector;
+ int ret;
+
+ panel_connector = kzalloc(sizeof(*panel_connector), GFP_KERNEL);
+ if (!panel_connector) {
+ dev_err(dev->dev, "allocation failed\n");
+ return NULL;
+ }
+
+ panel_connector->encoder = encoder;
+ panel_connector->mod = mod;
+
+ connector = &panel_connector->base;
+
+ drm_connector_init(dev, connector, &panel_connector_funcs,
+ DRM_MODE_CONNECTOR_LVDS);
+ drm_connector_helper_add(connector, &panel_connector_helper_funcs);
+
+ connector->interlace_allowed = 0;
+ connector->doublescan_allowed = 0;
+
+ ret = drm_mode_connector_attach_encoder(connector, encoder);
+ if (ret)
+ goto fail;
+
+ drm_sysfs_connector_add(connector);
+
+ return connector;
+
+fail:
+ panel_connector_destroy(connector);
+ return NULL;
+}
+
+/*
+ * Module:
+ */
+
+static int panel_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
+{
+ struct panel_module *panel_mod = to_panel_module(mod);
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ struct drm_encoder *encoder;
+ struct drm_connector *connector;
+
+ encoder = panel_encoder_create(dev, panel_mod);
+ if (!encoder)
+ return -ENOMEM;
+
+ connector = panel_connector_create(dev, panel_mod, encoder);
+ if (!connector)
+ return -ENOMEM;
+
+ priv->encoders[priv->num_encoders++] = encoder;
+ priv->connectors[priv->num_connectors++] = connector;
+
+ return 0;
+}
+
+static void panel_destroy(struct tilcdc_module *mod)
+{
+ struct panel_module *panel_mod = to_panel_module(mod);
+
+ if (panel_mod->timings) {
+ display_timings_release(panel_mod->timings);
+ kfree(panel_mod->timings);
+ }
+
+ tilcdc_module_cleanup(mod);
+ kfree(panel_mod->info);
+ kfree(panel_mod);
+}
+
+static const struct tilcdc_module_ops panel_module_ops = {
+ .modeset_init = panel_modeset_init,
+ .destroy = panel_destroy,
+};
+
+/*
+ * Device:
+ */
+
+/* maybe move this somewhere common if it is needed by other outputs? */
+static struct tilcdc_panel_info * of_get_panel_info(struct device_node *np)
+{
+ struct device_node *info_np;
+ struct tilcdc_panel_info *info;
+ int ret = 0;
+
+ if (!np) {
+ pr_err("%s: no devicenode given\n", __func__);
+ return NULL;
+ }
+
+ info_np = of_get_child_by_name(np, "panel-info");
+ if (!info_np) {
+ pr_err("%s: could not find panel-info node\n", __func__);
+ return NULL;
+ }
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ pr_err("%s: allocation failed\n", __func__);
+ return NULL;
+ }
+
+ ret |= of_property_read_u32(info_np, "ac-bias", &info->ac_bias);
+ ret |= of_property_read_u32(info_np, "ac-bias-intrpt", &info->ac_bias_intrpt);
+ ret |= of_property_read_u32(info_np, "dma-burst-sz", &info->dma_burst_sz);
+ ret |= of_property_read_u32(info_np, "bpp", &info->bpp);
+ ret |= of_property_read_u32(info_np, "fdd", &info->fdd);
+ ret |= of_property_read_u32(info_np, "sync-edge", &info->sync_edge);
+ ret |= of_property_read_u32(info_np, "sync-ctrl", &info->sync_ctrl);
+ ret |= of_property_read_u32(info_np, "raster-order", &info->raster_order);
+ ret |= of_property_read_u32(info_np, "fifo-th", &info->fifo_th);
+
+ /* optional: */
+ info->tft_alt_mode = of_property_read_bool(info_np, "tft-alt-mode");
+ info->stn_565_mode = of_property_read_bool(info_np, "stn-565-mode");
+ info->mono_8bit_mode = of_property_read_bool(info_np, "mono-8bit-mode");
+ info->invert_pxl_clk = of_property_read_bool(info_np, "invert-pxl-clk");
+
+ if (of_property_read_u32(info_np, "max-bpp", &info->max_bpp))
+ info->max_bpp = info->bpp;
+ if (of_property_read_u32(info_np, "min-bpp", &info->min_bpp))
+ info->min_bpp = info->bpp;
+
+ if (ret) {
+ pr_err("%s: error reading panel-info properties\n", __func__);
+ kfree(info);
+ return NULL;
+ }
+
+ return info;
+}
+
+static struct of_device_id panel_of_match[];
+
+static int panel_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct panel_module *panel_mod;
+ struct tilcdc_module *mod;
+ struct pinctrl *pinctrl;
+ int ret = -EINVAL;
+
+
+ /* bail out early if no DT data: */
+ if (!node) {
+ dev_err(&pdev->dev, "device-tree data is missing\n");
+ return -ENXIO;
+ }
+
+ panel_mod = kzalloc(sizeof(*panel_mod), GFP_KERNEL);
+ if (!panel_mod)
+ return -ENOMEM;
+
+ mod = &panel_mod->base;
+
+ tilcdc_module_init(mod, "panel", &panel_module_ops);
+
+ pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+ if (IS_ERR(pinctrl))
+ dev_warn(&pdev->dev, "pins are not configured\n");
+
+
+ panel_mod->timings = of_get_display_timings(node);
+ if (!panel_mod->timings) {
+ dev_err(&pdev->dev, "could not get panel timings\n");
+ goto fail;
+ }
+
+ panel_mod->info = of_get_panel_info(node);
+ if (!panel_mod->info) {
+ dev_err(&pdev->dev, "could not get panel info\n");
+ goto fail;
+ }
+
+ panel_mod->backlight = of_find_backlight_by_node(node);
+ if (panel_mod->backlight)
+ dev_info(&pdev->dev, "found backlight\n");
+
+ return 0;
+
+fail:
+ panel_destroy(mod);
+ return ret;
+}
+
+static int panel_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct of_device_id panel_of_match[] = {
+ { .compatible = "tilcdc,panel", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, panel_of_match);
+
+struct platform_driver panel_driver = {
+ .probe = panel_probe,
+ .remove = panel_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "panel",
+ .of_match_table = panel_of_match,
+ },
+};
+
+int __init tilcdc_panel_init(void)
+{
+ return platform_driver_register(&panel_driver);
+}
+
+void __exit tilcdc_panel_fini(void)
+{
+ platform_driver_unregister(&panel_driver);
+}
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_panel.h b/drivers/gpu/drm/tilcdc/tilcdc_panel.h
new file mode 100644
index 0000000..7db40aa
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_panel.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __TILCDC_PANEL_H__
+#define __TILCDC_PANEL_H__
+
+/* sub-module for generic lcd panel output */
+
+int tilcdc_panel_init(void);
+void tilcdc_panel_fini(void);
+
+#endif /* __TILCDC_PANEL_H__ */
--
1.8.1
^ permalink raw reply related
* [PATCH 3/4] drm/tilcdc: add encoder slave
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1358894185-21617-1-git-send-email-robdclark@gmail.com>
Add output panel driver for i2c encoder slaves.
Signed-off-by: Rob Clark <robdclark@gmail.com>
---
drivers/gpu/drm/tilcdc/Kconfig | 12 ++
drivers/gpu/drm/tilcdc/Makefile | 1 +
drivers/gpu/drm/tilcdc/tilcdc_drv.c | 5 +-
drivers/gpu/drm/tilcdc/tilcdc_slave.c | 380 ++++++++++++++++++++++++++++++++++
drivers/gpu/drm/tilcdc/tilcdc_slave.h | 26 +++
5 files changed, 423 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.c
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.h
diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
index ee9b592..99beca2 100644
--- a/drivers/gpu/drm/tilcdc/Kconfig
+++ b/drivers/gpu/drm/tilcdc/Kconfig
@@ -8,3 +8,15 @@ config DRM_TILCDC
Choose this option if you have an TI SoC with LCDC display
controller, for example AM33xx in beagle-bone, DA8xx, or
OMAP-L1xx. This driver replaces the FB_DA8XX fbdev driver.
+
+menu "I2C encoder or helper chips"
+ depends on DRM && DRM_KMS_HELPER && I2C
+
+config DRM_I2C_NXP_TDA998X
+ tristate "NXP Semiconductors TDA998X HDMI encoder"
+ default m if DRM_TILCDC
+ help
+ Support for NXP Semiconductors TDA998X HDMI encoders.
+
+endmenu
+
diff --git a/drivers/gpu/drm/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
index 1359cc2..aa9097e 100644
--- a/drivers/gpu/drm/tilcdc/Makefile
+++ b/drivers/gpu/drm/tilcdc/Makefile
@@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm -Werror
tilcdc-y := \
tilcdc_crtc.o \
tilcdc_tfp410.o \
+ tilcdc_slave.o \
tilcdc_drv.o
obj-$(CONFIG_DRM_TILCDC) += tilcdc.o
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
index cf1fddc..ca76dbe 100644
--- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c
+++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
@@ -20,6 +20,7 @@
#include "tilcdc_drv.h"
#include "tilcdc_regs.h"
#include "tilcdc_tfp410.h"
+#include "tilcdc_slave.h"
#include "drm_fb_helper.h"
@@ -587,6 +588,7 @@ static int __init tilcdc_drm_init(void)
{
DBG("init");
tilcdc_tfp410_init();
+ tilcdc_slave_init();
return platform_driver_register(&tilcdc_platform_driver);
}
@@ -594,10 +596,11 @@ static void __exit tilcdc_drm_fini(void)
{
DBG("fini");
tilcdc_tfp410_fini();
+ tilcdc_slave_fini();
platform_driver_unregister(&tilcdc_platform_driver);
}
-module_init(tilcdc_drm_init);
+late_initcall(tilcdc_drm_init);
module_exit(tilcdc_drm_fini);
MODULE_AUTHOR("Rob Clark <robdclark at gmail.com");
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.c b/drivers/gpu/drm/tilcdc/tilcdc_slave.c
new file mode 100644
index 0000000..b6f3e63
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_slave.c
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/i2c.h>
+#include <linux/of_i2c.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/consumer.h>
+#include <drm/drm_encoder_slave.h>
+
+#include "tilcdc_drv.h"
+
+struct slave_module {
+ struct tilcdc_module base;
+ struct i2c_adapter *i2c;
+};
+#define to_slave_module(x) container_of(x, struct slave_module, base)
+
+static const struct tilcdc_panel_info slave_info = {
+ .min_bpp = 16,
+ .max_bpp = 16,
+ .bpp = 16,
+ .ac_bias = 255,
+ .ac_bias_intrpt = 0,
+ .dma_burst_sz = 16,
+ .fdd = 0x80,
+ .tft_alt_mode = 0,
+ .stn_565_mode = 0,
+ .mono_8bit_mode = 0,
+ .sync_edge = 0,
+ .sync_ctrl = 1,
+ .raster_order = 0,
+};
+
+
+/*
+ * Encoder:
+ */
+
+struct slave_encoder {
+ struct drm_encoder_slave base;
+ struct slave_module *mod;
+};
+#define to_slave_encoder(x) container_of(to_encoder_slave(x), struct slave_encoder, base)
+
+static inline struct drm_encoder_slave_funcs *
+get_slave_funcs(struct drm_encoder *enc)
+{
+ return to_encoder_slave(enc)->slave_funcs;
+}
+
+static void slave_encoder_destroy(struct drm_encoder *encoder)
+{
+ struct slave_encoder *slave_encoder = to_slave_encoder(encoder);
+ if (get_slave_funcs(encoder))
+ get_slave_funcs(encoder)->destroy(encoder);
+ drm_encoder_cleanup(encoder);
+ kfree(slave_encoder);
+}
+
+static void slave_encoder_prepare(struct drm_encoder *encoder)
+{
+ drm_i2c_encoder_prepare(encoder);
+ tilcdc_crtc_set_panel_info(encoder->crtc, &slave_info);
+}
+
+static const struct drm_encoder_funcs slave_encoder_funcs = {
+ .destroy = slave_encoder_destroy,
+};
+
+static const struct drm_encoder_helper_funcs slave_encoder_helper_funcs = {
+ .dpms = drm_i2c_encoder_dpms,
+ .mode_fixup = drm_i2c_encoder_mode_fixup,
+ .prepare = slave_encoder_prepare,
+ .commit = drm_i2c_encoder_commit,
+ .mode_set = drm_i2c_encoder_mode_set,
+ .save = drm_i2c_encoder_save,
+ .restore = drm_i2c_encoder_restore,
+};
+
+static const struct i2c_board_info info = {
+ I2C_BOARD_INFO("tda998x", 0x70)
+};
+
+static struct drm_encoder *slave_encoder_create(struct drm_device *dev,
+ struct slave_module *mod)
+{
+ struct slave_encoder *slave_encoder;
+ struct drm_encoder *encoder;
+ int ret;
+
+ slave_encoder = kzalloc(sizeof(*slave_encoder), GFP_KERNEL);
+ if (!slave_encoder) {
+ dev_err(dev->dev, "allocation failed\n");
+ return NULL;
+ }
+
+ slave_encoder->mod = mod;
+
+ encoder = &slave_encoder->base.base;
+ encoder->possible_crtcs = 1;
+
+ ret = drm_encoder_init(dev, encoder, &slave_encoder_funcs,
+ DRM_MODE_ENCODER_LVDS);
+ if (ret)
+ goto fail;
+
+ drm_encoder_helper_add(encoder, &slave_encoder_helper_funcs);
+
+ ret = drm_i2c_encoder_init(dev, to_encoder_slave(encoder), mod->i2c, &info);
+ if (ret)
+ goto fail;
+
+ return encoder;
+
+fail:
+ slave_encoder_destroy(encoder);
+ return NULL;
+}
+
+/*
+ * Connector:
+ */
+
+struct slave_connector {
+ struct drm_connector base;
+
+ struct drm_encoder *encoder; /* our connected encoder */
+ struct slave_module *mod;
+};
+#define to_slave_connector(x) container_of(x, struct slave_connector, base)
+
+static void slave_connector_destroy(struct drm_connector *connector)
+{
+ struct slave_connector *slave_connector = to_slave_connector(connector);
+ drm_connector_cleanup(connector);
+ kfree(slave_connector);
+}
+
+static enum drm_connector_status slave_connector_detect(
+ struct drm_connector *connector,
+ bool force)
+{
+ struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
+ return get_slave_funcs(encoder)->detect(encoder, connector);
+}
+
+static int slave_connector_get_modes(struct drm_connector *connector)
+{
+ struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
+ return get_slave_funcs(encoder)->get_modes(encoder, connector);
+}
+
+static int slave_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
+ struct tilcdc_drm_private *priv = connector->dev->dev_private;
+ int ret;
+
+ ret = tilcdc_crtc_mode_valid(priv->crtc, mode);
+ if (ret != MODE_OK)
+ return ret;
+
+ return get_slave_funcs(encoder)->mode_valid(encoder, mode);
+}
+
+static struct drm_encoder *slave_connector_best_encoder(
+ struct drm_connector *connector)
+{
+ struct slave_connector *slave_connector = to_slave_connector(connector);
+ return slave_connector->encoder;
+}
+
+static int slave_connector_set_property(struct drm_connector *connector,
+ struct drm_property *property, uint64_t value)
+{
+ struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
+ return get_slave_funcs(encoder)->set_property(encoder,
+ connector, property, value);
+}
+
+static const struct drm_connector_funcs slave_connector_funcs = {
+ .destroy = slave_connector_destroy,
+ .dpms = drm_helper_connector_dpms,
+ .detect = slave_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .set_property = slave_connector_set_property,
+};
+
+static const struct drm_connector_helper_funcs slave_connector_helper_funcs = {
+ .get_modes = slave_connector_get_modes,
+ .mode_valid = slave_connector_mode_valid,
+ .best_encoder = slave_connector_best_encoder,
+};
+
+static struct drm_connector *slave_connector_create(struct drm_device *dev,
+ struct slave_module *mod, struct drm_encoder *encoder)
+{
+ struct slave_connector *slave_connector;
+ struct drm_connector *connector;
+ int ret;
+
+ slave_connector = kzalloc(sizeof(*slave_connector), GFP_KERNEL);
+ if (!slave_connector) {
+ dev_err(dev->dev, "allocation failed\n");
+ return NULL;
+ }
+
+ slave_connector->encoder = encoder;
+ slave_connector->mod = mod;
+
+ connector = &slave_connector->base;
+
+ drm_connector_init(dev, connector, &slave_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA);
+ drm_connector_helper_add(connector, &slave_connector_helper_funcs);
+
+ connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
+
+ connector->interlace_allowed = 0;
+ connector->doublescan_allowed = 0;
+
+ get_slave_funcs(encoder)->create_resources(encoder, connector);
+
+ ret = drm_mode_connector_attach_encoder(connector, encoder);
+ if (ret)
+ goto fail;
+
+ drm_sysfs_connector_add(connector);
+
+ return connector;
+
+fail:
+ slave_connector_destroy(connector);
+ return NULL;
+}
+
+/*
+ * Module:
+ */
+
+static int slave_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
+{
+ struct slave_module *slave_mod = to_slave_module(mod);
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ struct drm_encoder *encoder;
+ struct drm_connector *connector;
+
+ encoder = slave_encoder_create(dev, slave_mod);
+ if (!encoder)
+ return -ENOMEM;
+
+ connector = slave_connector_create(dev, slave_mod, encoder);
+ if (!connector)
+ return -ENOMEM;
+
+ priv->encoders[priv->num_encoders++] = encoder;
+ priv->connectors[priv->num_connectors++] = connector;
+
+ return 0;
+}
+
+static void slave_destroy(struct tilcdc_module *mod)
+{
+ struct slave_module *slave_mod = to_slave_module(mod);
+
+ tilcdc_module_cleanup(mod);
+ kfree(slave_mod);
+}
+
+static const struct tilcdc_module_ops slave_module_ops = {
+ .modeset_init = slave_modeset_init,
+ .destroy = slave_destroy,
+};
+
+/*
+ * Device:
+ */
+
+static struct of_device_id slave_of_match[];
+
+static int slave_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct device_node *i2c_node;
+ struct slave_module *slave_mod;
+ struct tilcdc_module *mod;
+ struct pinctrl *pinctrl;
+ uint32_t i2c_phandle;
+ int ret = -EINVAL;
+
+ /* bail out early if no DT data: */
+ if (!node) {
+ dev_err(&pdev->dev, "device-tree data is missing\n");
+ return -ENXIO;
+ }
+
+ slave_mod = kzalloc(sizeof(*slave_mod), GFP_KERNEL);
+ if (!slave_mod)
+ return -ENOMEM;
+
+ mod = &slave_mod->base;
+
+ tilcdc_module_init(mod, "slave", &slave_module_ops);
+
+ pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+ if (IS_ERR(pinctrl))
+ dev_warn(&pdev->dev, "pins are not configured\n");
+
+ if (of_property_read_u32(node, "i2c", &i2c_phandle)) {
+ dev_err(&pdev->dev, "could not get i2c bus phandle\n");
+ goto fail;
+ }
+
+ i2c_node = of_find_node_by_phandle(i2c_phandle);
+ if (!i2c_node) {
+ dev_err(&pdev->dev, "could not get i2c bus node\n");
+ goto fail;
+ }
+
+ slave_mod->i2c = of_find_i2c_adapter_by_node(i2c_node);
+ if (!slave_mod->i2c) {
+ dev_err(&pdev->dev, "could not get i2c\n");
+ goto fail;
+ }
+
+ of_node_put(i2c_node);
+
+ return 0;
+
+fail:
+ slave_destroy(mod);
+ return ret;
+}
+
+static int slave_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct of_device_id slave_of_match[] = {
+ { .compatible = "tilcdc,slave", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, slave_of_match);
+
+struct platform_driver slave_driver = {
+ .probe = slave_probe,
+ .remove = slave_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "slave",
+ .of_match_table = slave_of_match,
+ },
+};
+
+int __init tilcdc_slave_init(void)
+{
+ return platform_driver_register(&slave_driver);
+}
+
+void __exit tilcdc_slave_fini(void)
+{
+ platform_driver_unregister(&slave_driver);
+}
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.h b/drivers/gpu/drm/tilcdc/tilcdc_slave.h
new file mode 100644
index 0000000..2f85048
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_slave.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __TILCDC_SLAVE_H__
+#define __TILCDC_SLAVE_H__
+
+/* sub-module for i2c slave encoder output */
+
+int tilcdc_slave_init(void);
+void tilcdc_slave_fini(void);
+
+#endif /* __TILCDC_SLAVE_H__ */
--
1.8.1
^ permalink raw reply related
* [PATCH 2/4] drm/i2c: nxp-tda998x (v2)
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1358894185-21617-1-git-send-email-robdclark@gmail.com>
Driver for the NXP TDA998X i2c hdmi encoder slave.
v1: original
v2: fix npix/nline programming
Signed-off-by: Rob Clark <robdclark@gmail.com>
---
drivers/gpu/drm/i2c/Makefile | 3 +
drivers/gpu/drm/i2c/tda998x_drv.c | 908 ++++++++++++++++++++++++++++++++++++++
2 files changed, 911 insertions(+)
create mode 100644 drivers/gpu/drm/i2c/tda998x_drv.c
diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile
index 9286256..43aa33b 100644
--- a/drivers/gpu/drm/i2c/Makefile
+++ b/drivers/gpu/drm/i2c/Makefile
@@ -5,3 +5,6 @@ obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o
sil164-y := sil164_drv.o
obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o
+
+tda998x-y := tda998x_drv.o
+obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
new file mode 100644
index 0000000..02054e8
--- /dev/null
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -0,0 +1,908 @@
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+
+#include <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/drm_edid.h>
+
+
+#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
+
+struct tda998x_priv {
+ struct i2c_client *cec;
+ uint16_t rev;
+ uint8_t current_page;
+ int dpms;
+};
+
+#define to_tda998x_priv(x) ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv)
+
+/* The TDA9988 series of devices use a paged register scheme.. to simplify
+ * things we encode the page # in upper bits of the register #. To read/
+ * write a given register, we need to make sure CURPAGE register is set
+ * appropriately. Which implies reads/writes are not atomic. Fun!
+ */
+
+#define REG(page, addr) (((page) << 8) | (addr))
+#define REG2ADDR(reg) ((reg) & 0xff)
+#define REG2PAGE(reg) (((reg) >> 8) & 0xff)
+
+#define REG_CURPAGE 0xff /* write */
+
+
+/* Page 00h: General Control */
+#define REG_VERSION_LSB REG(0x00, 0x00) /* read */
+#define REG_MAIN_CNTRL0 REG(0x00, 0x01) /* read/write */
+# define MAIN_CNTRL0_SR (1 << 0)
+# define MAIN_CNTRL0_DECS (1 << 1)
+# define MAIN_CNTRL0_DEHS (1 << 2)
+# define MAIN_CNTRL0_CECS (1 << 3)
+# define MAIN_CNTRL0_CEHS (1 << 4)
+# define MAIN_CNTRL0_SCALER (1 << 7)
+#define REG_VERSION_MSB REG(0x00, 0x02) /* read */
+#define REG_SOFTRESET REG(0x00, 0x0a) /* write */
+# define SOFTRESET_AUDIO (1 << 0)
+# define SOFTRESET_I2C_MASTER (1 << 1)
+#define REG_DDC_DISABLE REG(0x00, 0x0b) /* read/write */
+#define REG_CCLK_ON REG(0x00, 0x0c) /* read/write */
+#define REG_I2C_MASTER REG(0x00, 0x0d) /* read/write */
+# define I2C_MASTER_DIS_MM (1 << 0)
+# define I2C_MASTER_DIS_FILT (1 << 1)
+# define I2C_MASTER_APP_STRT_LAT (1 << 2)
+#define REG_INT_FLAGS_0 REG(0x00, 0x0f) /* read/write */
+#define REG_INT_FLAGS_1 REG(0x00, 0x10) /* read/write */
+#define REG_INT_FLAGS_2 REG(0x00, 0x11) /* read/write */
+# define INT_FLAGS_2_EDID_BLK_RD (1 << 1)
+#define REG_ENA_VP_0 REG(0x00, 0x18) /* read/write */
+#define REG_ENA_VP_1 REG(0x00, 0x19) /* read/write */
+#define REG_ENA_VP_2 REG(0x00, 0x1a) /* read/write */
+#define REG_ENA_AP REG(0x00, 0x1e) /* read/write */
+#define REG_VIP_CNTRL_0 REG(0x00, 0x20) /* write */
+# define VIP_CNTRL_0_MIRR_A (1 << 7)
+# define VIP_CNTRL_0_SWAP_A(x) (((x) & 7) << 4)
+# define VIP_CNTRL_0_MIRR_B (1 << 3)
+# define VIP_CNTRL_0_SWAP_B(x) (((x) & 7) << 0)
+#define REG_VIP_CNTRL_1 REG(0x00, 0x21) /* write */
+# define VIP_CNTRL_1_MIRR_C (1 << 7)
+# define VIP_CNTRL_1_SWAP_C(x) (((x) & 7) << 4)
+# define VIP_CNTRL_1_MIRR_D (1 << 3)
+# define VIP_CNTRL_1_SWAP_D(x) (((x) & 7) << 0)
+#define REG_VIP_CNTRL_2 REG(0x00, 0x22) /* write */
+# define VIP_CNTRL_2_MIRR_E (1 << 7)
+# define VIP_CNTRL_2_SWAP_E(x) (((x) & 7) << 4)
+# define VIP_CNTRL_2_MIRR_F (1 << 3)
+# define VIP_CNTRL_2_SWAP_F(x) (((x) & 7) << 0)
+#define REG_VIP_CNTRL_3 REG(0x00, 0x23) /* write */
+# define VIP_CNTRL_3_X_TGL (1 << 0)
+# define VIP_CNTRL_3_H_TGL (1 << 1)
+# define VIP_CNTRL_3_V_TGL (1 << 2)
+# define VIP_CNTRL_3_EMB (1 << 3)
+# define VIP_CNTRL_3_SYNC_DE (1 << 4)
+# define VIP_CNTRL_3_SYNC_HS (1 << 5)
+# define VIP_CNTRL_3_DE_INT (1 << 6)
+# define VIP_CNTRL_3_EDGE (1 << 7)
+#define REG_VIP_CNTRL_4 REG(0x00, 0x24) /* write */
+# define VIP_CNTRL_4_BLC(x) (((x) & 3) << 0)
+# define VIP_CNTRL_4_BLANKIT(x) (((x) & 3) << 2)
+# define VIP_CNTRL_4_CCIR656 (1 << 4)
+# define VIP_CNTRL_4_656_ALT (1 << 5)
+# define VIP_CNTRL_4_TST_656 (1 << 6)
+# define VIP_CNTRL_4_TST_PAT (1 << 7)
+#define REG_VIP_CNTRL_5 REG(0x00, 0x25) /* write */
+# define VIP_CNTRL_5_CKCASE (1 << 0)
+# define VIP_CNTRL_5_SP_CNT(x) (((x) & 3) << 1)
+#define REG_MAT_CONTRL REG(0x00, 0x80) /* write */
+# define MAT_CONTRL_MAT_SC(x) (((x) & 3) << 0)
+# define MAT_CONTRL_MAT_BP (1 << 2)
+#define REG_VIDFORMAT REG(0x00, 0xa0) /* write */
+#define REG_REFPIX_MSB REG(0x00, 0xa1) /* write */
+#define REG_REFPIX_LSB REG(0x00, 0xa2) /* write */
+#define REG_REFLINE_MSB REG(0x00, 0xa3) /* write */
+#define REG_REFLINE_LSB REG(0x00, 0xa4) /* write */
+#define REG_NPIX_MSB REG(0x00, 0xa5) /* write */
+#define REG_NPIX_LSB REG(0x00, 0xa6) /* write */
+#define REG_NLINE_MSB REG(0x00, 0xa7) /* write */
+#define REG_NLINE_LSB REG(0x00, 0xa8) /* write */
+#define REG_VS_LINE_STRT_1_MSB REG(0x00, 0xa9) /* write */
+#define REG_VS_LINE_STRT_1_LSB REG(0x00, 0xaa) /* write */
+#define REG_VS_PIX_STRT_1_MSB REG(0x00, 0xab) /* write */
+#define REG_VS_PIX_STRT_1_LSB REG(0x00, 0xac) /* write */
+#define REG_VS_LINE_END_1_MSB REG(0x00, 0xad) /* write */
+#define REG_VS_LINE_END_1_LSB REG(0x00, 0xae) /* write */
+#define REG_VS_PIX_END_1_MSB REG(0x00, 0xaf) /* write */
+#define REG_VS_PIX_END_1_LSB REG(0x00, 0xb0) /* write */
+#define REG_VS_PIX_STRT_2_MSB REG(0x00, 0xb3) /* write */
+#define REG_VS_PIX_STRT_2_LSB REG(0x00, 0xb4) /* write */
+#define REG_VS_PIX_END_2_MSB REG(0x00, 0xb7) /* write */
+#define REG_VS_PIX_END_2_LSB REG(0x00, 0xb8) /* write */
+#define REG_HS_PIX_START_MSB REG(0x00, 0xb9) /* write */
+#define REG_HS_PIX_START_LSB REG(0x00, 0xba) /* write */
+#define REG_HS_PIX_STOP_MSB REG(0x00, 0xbb) /* write */
+#define REG_HS_PIX_STOP_LSB REG(0x00, 0xbc) /* write */
+#define REG_VWIN_START_1_MSB REG(0x00, 0xbd) /* write */
+#define REG_VWIN_START_1_LSB REG(0x00, 0xbe) /* write */
+#define REG_VWIN_END_1_MSB REG(0x00, 0xbf) /* write */
+#define REG_VWIN_END_1_LSB REG(0x00, 0xc0) /* write */
+#define REG_DE_START_MSB REG(0x00, 0xc5) /* write */
+#define REG_DE_START_LSB REG(0x00, 0xc6) /* write */
+#define REG_DE_STOP_MSB REG(0x00, 0xc7) /* write */
+#define REG_DE_STOP_LSB REG(0x00, 0xc8) /* write */
+#define REG_TBG_CNTRL_0 REG(0x00, 0xca) /* write */
+# define TBG_CNTRL_0_FRAME_DIS (1 << 5)
+# define TBG_CNTRL_0_SYNC_MTHD (1 << 6)
+# define TBG_CNTRL_0_SYNC_ONCE (1 << 7)
+#define REG_TBG_CNTRL_1 REG(0x00, 0xcb) /* write */
+# define TBG_CNTRL_1_VH_TGL_0 (1 << 0)
+# define TBG_CNTRL_1_VH_TGL_1 (1 << 1)
+# define TBG_CNTRL_1_VH_TGL_2 (1 << 2)
+# define TBG_CNTRL_1_VHX_EXT_DE (1 << 3)
+# define TBG_CNTRL_1_VHX_EXT_HS (1 << 4)
+# define TBG_CNTRL_1_VHX_EXT_VS (1 << 5)
+# define TBG_CNTRL_1_DWIN_DIS (1 << 6)
+#define REG_ENABLE_SPACE REG(0x00, 0xd6) /* write */
+#define REG_HVF_CNTRL_0 REG(0x00, 0xe4) /* write */
+# define HVF_CNTRL_0_SM (1 << 7)
+# define HVF_CNTRL_0_RWB (1 << 6)
+# define HVF_CNTRL_0_PREFIL(x) (((x) & 3) << 2)
+# define HVF_CNTRL_0_INTPOL(x) (((x) & 3) << 0)
+#define REG_HVF_CNTRL_1 REG(0x00, 0xe5) /* write */
+# define HVF_CNTRL_1_FOR (1 << 0)
+# define HVF_CNTRL_1_YUVBLK (1 << 1)
+# define HVF_CNTRL_1_VQR(x) (((x) & 3) << 2)
+# define HVF_CNTRL_1_PAD(x) (((x) & 3) << 4)
+# define HVF_CNTRL_1_SEMI_PLANAR (1 << 6)
+#define REG_RPT_CNTRL REG(0x00, 0xf0) /* write */
+
+
+/* Page 02h: PLL settings */
+#define REG_PLL_SERIAL_1 REG(0x02, 0x00) /* read/write */
+# define PLL_SERIAL_1_SRL_FDN (1 << 0)
+# define PLL_SERIAL_1_SRL_IZ(x) (((x) & 3) << 1)
+# define PLL_SERIAL_1_SRL_MAN_IZ (1 << 6)
+#define REG_PLL_SERIAL_2 REG(0x02, 0x01) /* read/write */
+# define PLL_SERIAL_2_SRL_NOSC(x) (((x) & 3) << 0)
+# define PLL_SERIAL_2_SRL_PR(x) (((x) & 0xf) << 4)
+#define REG_PLL_SERIAL_3 REG(0x02, 0x02) /* read/write */
+# define PLL_SERIAL_3_SRL_CCIR (1 << 0)
+# define PLL_SERIAL_3_SRL_DE (1 << 2)
+# define PLL_SERIAL_3_SRL_PXIN_SEL (1 << 4)
+#define REG_SERIALIZER REG(0x02, 0x03) /* read/write */
+#define REG_BUFFER_OUT REG(0x02, 0x04) /* read/write */
+#define REG_PLL_SCG1 REG(0x02, 0x05) /* read/write */
+#define REG_PLL_SCG2 REG(0x02, 0x06) /* read/write */
+#define REG_PLL_SCGN1 REG(0x02, 0x07) /* read/write */
+#define REG_PLL_SCGN2 REG(0x02, 0x08) /* read/write */
+#define REG_PLL_SCGR1 REG(0x02, 0x09) /* read/write */
+#define REG_PLL_SCGR2 REG(0x02, 0x0a) /* read/write */
+#define REG_AUDIO_DIV REG(0x02, 0x0e) /* read/write */
+#define REG_SEL_CLK REG(0x02, 0x11) /* read/write */
+# define SEL_CLK_SEL_CLK1 (1 << 0)
+# define SEL_CLK_SEL_VRF_CLK(x) (((x) & 3) << 1)
+# define SEL_CLK_ENA_SC_CLK (1 << 3)
+#define REG_ANA_GENERAL REG(0x02, 0x12) /* read/write */
+
+
+/* Page 09h: EDID Control */
+#define REG_EDID_DATA_0 REG(0x09, 0x00) /* read */
+/* next 127 successive registers are the EDID block */
+#define REG_EDID_CTRL REG(0x09, 0xfa) /* read/write */
+#define REG_DDC_ADDR REG(0x09, 0xfb) /* read/write */
+#define REG_DDC_OFFS REG(0x09, 0xfc) /* read/write */
+#define REG_DDC_SEGM_ADDR REG(0x09, 0xfd) /* read/write */
+#define REG_DDC_SEGM REG(0x09, 0xfe) /* read/write */
+
+
+/* Page 10h: information frames and packets */
+
+
+/* Page 11h: audio settings and content info packets */
+#define REG_AIP_CNTRL_0 REG(0x11, 0x00) /* read/write */
+# define AIP_CNTRL_0_RST_FIFO (1 << 0)
+# define AIP_CNTRL_0_SWAP (1 << 1)
+# define AIP_CNTRL_0_LAYOUT (1 << 2)
+# define AIP_CNTRL_0_ACR_MAN (1 << 5)
+# define AIP_CNTRL_0_RST_CTS (1 << 6)
+#define REG_ENC_CNTRL REG(0x11, 0x0d) /* read/write */
+# define ENC_CNTRL_RST_ENC (1 << 0)
+# define ENC_CNTRL_RST_SEL (1 << 1)
+# define ENC_CNTRL_CTL_CODE(x) (((x) & 3) << 2)
+
+
+/* Page 12h: HDCP and OTP */
+#define REG_TX3 REG(0x12, 0x9a) /* read/write */
+#define REG_TX33 REG(0x12, 0xb8) /* read/write */
+# define TX33_HDMI (1 << 1)
+
+
+/* Page 13h: Gamut related metadata packets */
+
+
+
+/* CEC registers: (not paged)
+ */
+#define REG_CEC_FRO_IM_CLK_CTRL 0xfb /* read/write */
+# define CEC_FRO_IM_CLK_CTRL_GHOST_DIS (1 << 7)
+# define CEC_FRO_IM_CLK_CTRL_ENA_OTP (1 << 6)
+# define CEC_FRO_IM_CLK_CTRL_IMCLK_SEL (1 << 1)
+# define CEC_FRO_IM_CLK_CTRL_FRO_DIV (1 << 0)
+#define REG_CEC_RXSHPDLEV 0xfe /* read */
+# define CEC_RXSHPDLEV_RXSENS (1 << 0)
+# define CEC_RXSHPDLEV_HPD (1 << 1)
+
+#define REG_CEC_ENAMODS 0xff /* read/write */
+# define CEC_ENAMODS_DIS_FRO (1 << 6)
+# define CEC_ENAMODS_DIS_CCLK (1 << 5)
+# define CEC_ENAMODS_EN_RXSENS (1 << 2)
+# define CEC_ENAMODS_EN_HDMI (1 << 1)
+# define CEC_ENAMODS_EN_CEC (1 << 0)
+
+
+/* Device versions: */
+#define TDA9989N2 0x0101
+#define TDA19989 0x0201
+#define TDA19989N2 0x0202
+#define TDA19988 0x0301
+
+static void
+cec_write(struct drm_encoder *encoder, uint16_t addr, uint8_t val)
+{
+ struct i2c_client *client = to_tda998x_priv(encoder)->cec;
+ uint8_t buf[] = {addr, val};
+ int ret;
+
+ ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+ if (ret < 0)
+ dev_err(&client->dev, "Error %d writing to cec:0x%x\n", ret, addr);
+}
+
+static uint8_t
+cec_read(struct drm_encoder *encoder, uint8_t addr)
+{
+ struct i2c_client *client = to_tda998x_priv(encoder)->cec;
+ uint8_t val;
+ int ret;
+
+ ret = i2c_master_send(client, &addr, sizeof(addr));
+ if (ret < 0)
+ goto fail;
+
+ ret = i2c_master_recv(client, &val, sizeof(val));
+ if (ret < 0)
+ goto fail;
+
+ return val;
+
+fail:
+ dev_err(&client->dev, "Error %d reading from cec:0x%x\n", ret, addr);
+ return 0;
+}
+
+static void
+set_page(struct drm_encoder *encoder, uint16_t reg)
+{
+ struct tda998x_priv *priv = to_tda998x_priv(encoder);
+
+ if (REG2PAGE(reg) != priv->current_page) {
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ uint8_t buf[] = {
+ REG_CURPAGE, REG2PAGE(reg)
+ };
+ int ret = i2c_master_send(client, buf, sizeof(buf));
+ if (ret < 0)
+ dev_err(&client->dev, "Error %d writing to REG_CURPAGE\n", ret);
+
+ priv->current_page = REG2PAGE(reg);
+ }
+}
+
+static int
+reg_read_range(struct drm_encoder *encoder, uint16_t reg, char *buf, int cnt)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ uint8_t addr = REG2ADDR(reg);
+ int ret;
+
+ set_page(encoder, reg);
+
+ ret = i2c_master_send(client, &addr, sizeof(addr));
+ if (ret < 0)
+ goto fail;
+
+ ret = i2c_master_recv(client, buf, cnt);
+ if (ret < 0)
+ goto fail;
+
+ return ret;
+
+fail:
+ dev_err(&client->dev, "Error %d reading from 0x%x\n", ret, reg);
+ return ret;
+}
+
+static uint8_t
+reg_read(struct drm_encoder *encoder, uint16_t reg)
+{
+ uint8_t val = 0;
+ reg_read_range(encoder, reg, &val, sizeof(val));
+ return val;
+}
+
+static void
+reg_write(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ uint8_t buf[] = {REG2ADDR(reg), val};
+ int ret;
+
+ set_page(encoder, reg);
+
+ ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+ if (ret < 0)
+ dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
+}
+
+static void
+reg_write16(struct drm_encoder *encoder, uint16_t reg, uint16_t val)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ uint8_t buf[] = {REG2ADDR(reg), val >> 8, val};
+ int ret;
+
+ set_page(encoder, reg);
+
+ ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+ if (ret < 0)
+ dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
+}
+
+static void
+reg_set(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
+{
+ reg_write(encoder, reg, reg_read(encoder, reg) | val);
+}
+
+static void
+reg_clear(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
+{
+ reg_write(encoder, reg, reg_read(encoder, reg) & ~val);
+}
+
+static void
+tda998x_reset(struct drm_encoder *encoder)
+{
+ /* reset audio and i2c master: */
+ reg_set(encoder, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER);
+ msleep(50);
+ reg_clear(encoder, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER);
+ msleep(50);
+
+ /* reset transmitter: */
+ reg_set(encoder, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR);
+ reg_clear(encoder, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR);
+
+ /* PLL registers common configuration */
+ reg_write(encoder, REG_PLL_SERIAL_1, 0x00);
+ reg_write(encoder, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(1));
+ reg_write(encoder, REG_PLL_SERIAL_3, 0x00);
+ reg_write(encoder, REG_SERIALIZER, 0x00);
+ reg_write(encoder, REG_BUFFER_OUT, 0x00);
+ reg_write(encoder, REG_PLL_SCG1, 0x00);
+ reg_write(encoder, REG_AUDIO_DIV, 0x03);
+ reg_write(encoder, REG_SEL_CLK, SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
+ reg_write(encoder, REG_PLL_SCGN1, 0xfa);
+ reg_write(encoder, REG_PLL_SCGN2, 0x00);
+ reg_write(encoder, REG_PLL_SCGR1, 0x5b);
+ reg_write(encoder, REG_PLL_SCGR2, 0x00);
+ reg_write(encoder, REG_PLL_SCG2, 0x10);
+}
+
+/* DRM encoder functions */
+
+static void
+tda998x_encoder_set_config(struct drm_encoder *encoder, void *params)
+{
+}
+
+static void
+tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct tda998x_priv *priv = to_tda998x_priv(encoder);
+
+ /* we only care about on or off: */
+ if (mode != DRM_MODE_DPMS_ON)
+ mode = DRM_MODE_DPMS_OFF;
+
+ if (mode == priv->dpms)
+ return;
+
+ switch (mode) {
+ case DRM_MODE_DPMS_ON:
+ /* enable audio and video ports */
+ reg_write(encoder, REG_ENA_AP, 0xff);
+ reg_write(encoder, REG_ENA_VP_0, 0xff);
+ reg_write(encoder, REG_ENA_VP_1, 0xff);
+ reg_write(encoder, REG_ENA_VP_2, 0xff);
+ /* set muxing after enabling ports: */
+ reg_write(encoder, REG_VIP_CNTRL_0,
+ VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3));
+ reg_write(encoder, REG_VIP_CNTRL_1,
+ VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1));
+ reg_write(encoder, REG_VIP_CNTRL_2,
+ VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5));
+ break;
+ case DRM_MODE_DPMS_OFF:
+ /* disable audio and video ports */
+ reg_write(encoder, REG_ENA_AP, 0x00);
+ reg_write(encoder, REG_ENA_VP_0, 0x00);
+ reg_write(encoder, REG_ENA_VP_1, 0x00);
+ reg_write(encoder, REG_ENA_VP_2, 0x00);
+ break;
+ }
+
+ priv->dpms = mode;
+}
+
+static void
+tda998x_encoder_save(struct drm_encoder *encoder)
+{
+ DBG("");
+}
+
+static void
+tda998x_encoder_restore(struct drm_encoder *encoder)
+{
+ DBG("");
+}
+
+static bool
+tda998x_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static int
+tda998x_encoder_mode_valid(struct drm_encoder *encoder,
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static void
+tda998x_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct tda998x_priv *priv = to_tda998x_priv(encoder);
+ uint16_t hs_start, hs_end, line_start, line_end;
+ uint16_t vwin_start, vwin_end, de_start, de_end;
+ uint16_t ref_pix, ref_line, pix_start2;
+ uint8_t reg, div, rep;
+
+ hs_start = mode->hsync_start - mode->hdisplay;
+ hs_end = mode->hsync_end - mode->hdisplay;
+ line_start = 1;
+ line_end = 1 + mode->vsync_end - mode->vsync_start;
+ vwin_start = mode->vtotal - mode->vsync_start;
+ vwin_end = vwin_start + mode->vdisplay;
+ de_start = mode->htotal - mode->hdisplay;
+ de_end = mode->htotal;
+
+ pix_start2 = 0;
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+ pix_start2 = (mode->htotal / 2) + hs_start;
+
+ /* TODO how is this value calculated? It is 2 for all common
+ * formats in the tables in out of tree nxp driver (assuming
+ * I've properly deciphered their byzantine table system)
+ */
+ ref_line = 2;
+
+ /* this might changes for other color formats from the CRTC: */
+ ref_pix = 3 + hs_start;
+
+ div = 148500 / mode->clock;
+
+ DBG("clock=%d, div=%u", mode->clock, div);
+ DBG("hs_start=%u, hs_end=%u, line_start=%u, line_end=%u",
+ hs_start, hs_end, line_start, line_end);
+ DBG("vwin_start=%u, vwin_end=%u, de_start=%u, de_end=%u",
+ vwin_start, vwin_end, de_start, de_end);
+ DBG("ref_line=%u, ref_pix=%u, pix_start2=%u",
+ ref_line, ref_pix, pix_start2);
+
+ /* mute the audio FIFO: */
+ reg_set(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
+
+ /* set HDMI HDCP mode off: */
+ reg_set(encoder, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS);
+ reg_clear(encoder, REG_TX33, TX33_HDMI);
+
+ reg_write(encoder, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(0));
+ /* no pre-filter or interpolator: */
+ reg_write(encoder, REG_HVF_CNTRL_0, HVF_CNTRL_0_PREFIL(0) |
+ HVF_CNTRL_0_INTPOL(0));
+ reg_write(encoder, REG_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0));
+ reg_write(encoder, REG_VIP_CNTRL_4, VIP_CNTRL_4_BLANKIT(0) |
+ VIP_CNTRL_4_BLC(0));
+ reg_clear(encoder, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR);
+
+ reg_clear(encoder, REG_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IZ);
+ reg_clear(encoder, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_DE);
+ reg_write(encoder, REG_SERIALIZER, 0);
+ reg_write(encoder, REG_HVF_CNTRL_1, HVF_CNTRL_1_VQR(0));
+
+ /* TODO enable pixel repeat for pixel rates less than 25Msamp/s */
+ rep = 0;
+ reg_write(encoder, REG_RPT_CNTRL, 0);
+ reg_write(encoder, REG_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) |
+ SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
+
+ reg_write(encoder, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) |
+ PLL_SERIAL_2_SRL_PR(rep));
+
+ reg_write16(encoder, REG_VS_PIX_STRT_2_MSB, pix_start2);
+ reg_write16(encoder, REG_VS_PIX_END_2_MSB, pix_start2);
+
+ /* set color matrix bypass flag: */
+ reg_set(encoder, REG_MAT_CONTRL, MAT_CONTRL_MAT_BP);
+
+ /* set BIAS tmds value: */
+ reg_write(encoder, REG_ANA_GENERAL, 0x09);
+
+ reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_MTHD);
+
+ reg_write(encoder, REG_VIP_CNTRL_3, 0);
+ reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_SYNC_HS);
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_V_TGL);
+
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_H_TGL);
+
+ reg_write(encoder, REG_VIDFORMAT, 0x00);
+ reg_write16(encoder, REG_NPIX_MSB, mode->hdisplay - 1);
+ reg_write16(encoder, REG_NLINE_MSB, mode->vdisplay - 1);
+ reg_write16(encoder, REG_VS_LINE_STRT_1_MSB, line_start);
+ reg_write16(encoder, REG_VS_LINE_END_1_MSB, line_end);
+ reg_write16(encoder, REG_VS_PIX_STRT_1_MSB, hs_start);
+ reg_write16(encoder, REG_VS_PIX_END_1_MSB, hs_start);
+ reg_write16(encoder, REG_HS_PIX_START_MSB, hs_start);
+ reg_write16(encoder, REG_HS_PIX_STOP_MSB, hs_end);
+ reg_write16(encoder, REG_VWIN_START_1_MSB, vwin_start);
+ reg_write16(encoder, REG_VWIN_END_1_MSB, vwin_end);
+ reg_write16(encoder, REG_DE_START_MSB, de_start);
+ reg_write16(encoder, REG_DE_STOP_MSB, de_end);
+
+ if (priv->rev == TDA19988) {
+ /* let incoming pixels fill the active space (if any) */
+ reg_write(encoder, REG_ENABLE_SPACE, 0x01);
+ }
+
+ reg_write16(encoder, REG_REFPIX_MSB, ref_pix);
+ reg_write16(encoder, REG_REFLINE_MSB, ref_line);
+
+ reg = TBG_CNTRL_1_VHX_EXT_DE |
+ TBG_CNTRL_1_VHX_EXT_HS |
+ TBG_CNTRL_1_VHX_EXT_VS |
+ TBG_CNTRL_1_DWIN_DIS | /* HDCP off */
+ TBG_CNTRL_1_VH_TGL_2;
+ if (mode->flags & (DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC))
+ reg |= TBG_CNTRL_1_VH_TGL_0;
+ reg_set(encoder, REG_TBG_CNTRL_1, reg);
+
+ /* must be last register set: */
+ reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_ONCE);
+}
+
+static enum drm_connector_status
+tda998x_encoder_detect(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ uint8_t val = cec_read(encoder, REG_CEC_RXSHPDLEV);
+ return (val & CEC_RXSHPDLEV_HPD) ? connector_status_connected :
+ connector_status_disconnected;
+}
+
+static int
+read_edid_block(struct drm_encoder *encoder, uint8_t *buf, int blk)
+{
+ uint8_t offset, segptr;
+ int ret, i;
+
+ /* enable EDID read irq: */
+ reg_set(encoder, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
+
+ offset = (blk & 1) ? 128 : 0;
+ segptr = blk / 2;
+
+ reg_write(encoder, REG_DDC_ADDR, 0xa0);
+ reg_write(encoder, REG_DDC_OFFS, offset);
+ reg_write(encoder, REG_DDC_SEGM_ADDR, 0x60);
+ reg_write(encoder, REG_DDC_SEGM, segptr);
+
+ /* enable reading EDID: */
+ reg_write(encoder, REG_EDID_CTRL, 0x1);
+
+ /* flag must be cleared by sw: */
+ reg_write(encoder, REG_EDID_CTRL, 0x0);
+
+ /* wait for block read to complete: */
+ for (i = 100; i > 0; i--) {
+ uint8_t val = reg_read(encoder, REG_INT_FLAGS_2);
+ if (val & INT_FLAGS_2_EDID_BLK_RD)
+ break;
+ msleep(1);
+ }
+
+ if (i == 0)
+ return -ETIMEDOUT;
+
+ ret = reg_read_range(encoder, REG_EDID_DATA_0, buf, EDID_LENGTH);
+ if (ret != EDID_LENGTH) {
+ dev_err(encoder->dev->dev, "failed to read edid block %d: %d",
+ blk, ret);
+ return ret;
+ }
+
+ reg_clear(encoder, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
+
+ return 0;
+}
+
+static uint8_t *
+do_get_edid(struct drm_encoder *encoder)
+{
+ int j = 0, valid_extensions = 0;
+ uint8_t *block, *new;
+ bool print_bad_edid = drm_debug & DRM_UT_KMS;
+
+ if ((block = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL)
+ return NULL;
+
+ /* base block fetch */
+ if (read_edid_block(encoder, block, 0))
+ goto fail;
+
+ if (!drm_edid_block_valid(block, 0, print_bad_edid))
+ goto fail;
+
+ /* if there's no extensions, we're done */
+ if (block[0x7e] == 0)
+ return block;
+
+ new = krealloc(block, (block[0x7e] + 1) * EDID_LENGTH, GFP_KERNEL);
+ if (!new)
+ goto fail;
+ block = new;
+
+ for (j = 1; j <= block[0x7e]; j++) {
+ uint8_t *ext_block = block + (valid_extensions + 1) * EDID_LENGTH;
+ if (read_edid_block(encoder, ext_block, j))
+ goto fail;
+
+ if (!drm_edid_block_valid(ext_block, j, print_bad_edid))
+ goto fail;
+
+ valid_extensions++;
+ }
+
+ if (valid_extensions != block[0x7e]) {
+ block[EDID_LENGTH-1] += block[0x7e] - valid_extensions;
+ block[0x7e] = valid_extensions;
+ new = krealloc(block, (valid_extensions + 1) * EDID_LENGTH, GFP_KERNEL);
+ if (!new)
+ goto fail;
+ block = new;
+ }
+
+ return block;
+
+fail:
+ dev_warn(encoder->dev->dev, "failed to read EDID\n");
+ kfree(block);
+ return NULL;
+}
+
+static int
+tda998x_encoder_get_modes(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct edid *edid = (struct edid *)do_get_edid(encoder);
+ int n = 0;
+
+ if (edid) {
+ drm_mode_connector_update_edid_property(connector, edid);
+ n = drm_add_edid_modes(connector, edid);
+ kfree(edid);
+ }
+
+ return n;
+}
+
+static int
+tda998x_encoder_create_resources(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ DBG("");
+ return 0;
+}
+
+static int
+tda998x_encoder_set_property(struct drm_encoder *encoder,
+ struct drm_connector *connector,
+ struct drm_property *property,
+ uint64_t val)
+{
+ DBG("");
+ return 0;
+}
+
+static void
+tda998x_encoder_destroy(struct drm_encoder *encoder)
+{
+ struct tda998x_priv *priv = to_tda998x_priv(encoder);
+ drm_i2c_encoder_destroy(encoder);
+ kfree(priv);
+}
+
+static struct drm_encoder_slave_funcs tda998x_encoder_funcs = {
+ .set_config = tda998x_encoder_set_config,
+ .destroy = tda998x_encoder_destroy,
+ .dpms = tda998x_encoder_dpms,
+ .save = tda998x_encoder_save,
+ .restore = tda998x_encoder_restore,
+ .mode_fixup = tda998x_encoder_mode_fixup,
+ .mode_valid = tda998x_encoder_mode_valid,
+ .mode_set = tda998x_encoder_mode_set,
+ .detect = tda998x_encoder_detect,
+ .get_modes = tda998x_encoder_get_modes,
+ .create_resources = tda998x_encoder_create_resources,
+ .set_property = tda998x_encoder_set_property,
+};
+
+/* I2C driver functions */
+
+static int
+tda998x_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ return 0;
+}
+
+static int
+tda998x_remove(struct i2c_client *client)
+{
+ return 0;
+}
+
+static int
+tda998x_encoder_init(struct i2c_client *client,
+ struct drm_device *dev,
+ struct drm_encoder_slave *encoder_slave)
+{
+ struct drm_encoder *encoder = &encoder_slave->base;
+ struct tda998x_priv *priv;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->current_page = 0;
+ priv->cec = i2c_new_dummy(client->adapter, 0x34);
+ priv->dpms = DRM_MODE_DPMS_OFF;
+
+ encoder_slave->slave_priv = priv;
+ encoder_slave->slave_funcs = &tda998x_encoder_funcs;
+
+ /* wake up the device: */
+ cec_write(encoder, REG_CEC_ENAMODS,
+ CEC_ENAMODS_EN_RXSENS | CEC_ENAMODS_EN_HDMI);
+
+ tda998x_reset(encoder);
+
+ /* read version: */
+ priv->rev = reg_read(encoder, REG_VERSION_LSB) |
+ reg_read(encoder, REG_VERSION_MSB) << 8;
+
+ /* mask off feature bits: */
+ priv->rev &= ~0x30; /* not-hdcp and not-scalar bit */
+
+ switch (priv->rev) {
+ case TDA9989N2: dev_info(dev->dev, "found TDA9989 n2"); break;
+ case TDA19989: dev_info(dev->dev, "found TDA19989"); break;
+ case TDA19989N2: dev_info(dev->dev, "found TDA19989 n2"); break;
+ case TDA19988: dev_info(dev->dev, "found TDA19988"); break;
+ default:
+ DBG("found unsupported device: %04x", priv->rev);
+ goto fail;
+ }
+
+ /* after reset, enable DDC: */
+ reg_write(encoder, REG_DDC_DISABLE, 0x00);
+
+ /* set clock on DDC channel: */
+ reg_write(encoder, REG_TX3, 39);
+
+ /* if necessary, disable multi-master: */
+ if (priv->rev == TDA19989)
+ reg_set(encoder, REG_I2C_MASTER, I2C_MASTER_DIS_MM);
+
+ cec_write(encoder, REG_CEC_FRO_IM_CLK_CTRL,
+ CEC_FRO_IM_CLK_CTRL_GHOST_DIS | CEC_FRO_IM_CLK_CTRL_IMCLK_SEL);
+
+ return 0;
+
+fail:
+ /* if encoder_init fails, the encoder slave is never registered,
+ * so cleanup here:
+ */
+ if (priv->cec)
+ i2c_unregister_device(priv->cec);
+ kfree(priv);
+ encoder_slave->slave_priv = NULL;
+ encoder_slave->slave_funcs = NULL;
+ return -ENXIO;
+}
+
+static struct i2c_device_id tda998x_ids[] = {
+ { "tda998x", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tda998x_ids);
+
+static struct drm_i2c_encoder_driver tda998x_driver = {
+ .i2c_driver = {
+ .probe = tda998x_probe,
+ .remove = tda998x_remove,
+ .driver = {
+ .name = "tda998x",
+ },
+ .id_table = tda998x_ids,
+ },
+ .encoder_init = tda998x_encoder_init,
+};
+
+/* Module initialization */
+
+static int __init
+tda998x_init(void)
+{
+ DBG("");
+ return drm_i2c_encoder_register(THIS_MODULE, &tda998x_driver);
+}
+
+static void __exit
+tda998x_exit(void)
+{
+ DBG("");
+ drm_i2c_encoder_unregister(&tda998x_driver);
+}
+
+MODULE_DESCRIPTION("NXP Semiconductors TDA998X TMDS transmitter driver");
+
+MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
+MODULE_DESCRIPTION("NXP Semiconductors TDA998X HDMI Encoder");
+MODULE_LICENSE("GPL");
+
+module_init(tda998x_init);
+module_exit(tda998x_exit);
--
1.8.1
^ permalink raw reply related
* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1358894185-21617-1-git-send-email-robdclark@gmail.com>
A simple DRM/KMS driver for the TI LCD Controller found in various
smaller TI parts (AM33xx, OMAPL138, etc). This driver uses the
CMA helpers. Currently only the TFP410 DVI encoder is supported
(tested with beaglebone + DVI cape). There are also various LCD
displays, for which support can be added (as I get hw to test on),
and an external i2c HDMI encoder found on some boards.
The display controller supports a single CRTC. And the encoder+
connector are split out into sub-devices. Depending on which LCD
or external encoder is actually present, the appropriate output
module(s) will be loaded.
v1: original
v2: fix fb refcnting and few other cleanups
v3: get +/- vsync/hsync from timings rather than panel-info, add
option DT max-bandwidth field so driver doesn't attempt to
pick a display mode with too high memory bandwidth, and other
small cleanups
Signed-off-by: Rob Clark <robdclark@gmail.com>
---
drivers/gpu/drm/Kconfig | 2 +
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/tilcdc/Kconfig | 10 +
drivers/gpu/drm/tilcdc/Makefile | 8 +
drivers/gpu/drm/tilcdc/tilcdc_crtc.c | 597 ++++++++++++++++++++++++++++++++
drivers/gpu/drm/tilcdc/tilcdc_drv.c | 605 +++++++++++++++++++++++++++++++++
drivers/gpu/drm/tilcdc/tilcdc_drv.h | 159 +++++++++
drivers/gpu/drm/tilcdc/tilcdc_regs.h | 154 +++++++++
drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++++++++++
drivers/gpu/drm/tilcdc/tilcdc_tfp410.h | 26 ++
10 files changed, 1985 insertions(+)
create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
create mode 100644 drivers/gpu/drm/tilcdc/Makefile
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 983201b..718e042 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -212,3 +212,5 @@ source "drivers/gpu/drm/cirrus/Kconfig"
source "drivers/gpu/drm/shmobile/Kconfig"
source "drivers/gpu/drm/tegra/Kconfig"
+
+source "drivers/gpu/drm/tilcdc/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 6f58c81..3af934d 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -50,4 +50,5 @@ obj-$(CONFIG_DRM_UDL) += udl/
obj-$(CONFIG_DRM_AST) += ast/
obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
obj-$(CONFIG_DRM_TEGRA) += tegra/
+obj-$(CONFIG_DRM_TILCDC) += tilcdc/
obj-y += i2c/
diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
new file mode 100644
index 0000000..ee9b592
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/Kconfig
@@ -0,0 +1,10 @@
+config DRM_TILCDC
+ tristate "DRM Support for TI LCDC Display Controller"
+ depends on DRM && OF
+ select DRM_KMS_HELPER
+ select DRM_KMS_CMA_HELPER
+ select DRM_GEM_CMA_HELPER
+ help
+ Choose this option if you have an TI SoC with LCDC display
+ controller, for example AM33xx in beagle-bone, DA8xx, or
+ OMAP-L1xx. This driver replaces the FB_DA8XX fbdev driver.
diff --git a/drivers/gpu/drm/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
new file mode 100644
index 0000000..1359cc2
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/Makefile
@@ -0,0 +1,8 @@
+ccflags-y := -Iinclude/drm -Werror
+
+tilcdc-y := \
+ tilcdc_crtc.o \
+ tilcdc_tfp410.o \
+ tilcdc_drv.o
+
+obj-$(CONFIG_DRM_TILCDC) += tilcdc.o
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
new file mode 100644
index 0000000..ad7b6b9
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
@@ -0,0 +1,597 @@
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/kfifo.h>
+
+#include "tilcdc_drv.h"
+#include "tilcdc_regs.h"
+
+struct tilcdc_crtc {
+ struct drm_crtc base;
+
+ const struct tilcdc_panel_info *info;
+ uint32_t dirty;
+ dma_addr_t start, end;
+ struct drm_pending_vblank_event *event;
+ int dpms;
+ wait_queue_head_t frame_done_wq;
+ bool frame_done;
+
+ /* fb currently set to scanout 0/1: */
+ struct drm_framebuffer *scanout[2];
+
+ /* for deferred fb unref's: */
+ DECLARE_KFIFO_PTR(unref_fifo, struct drm_framebuffer *);
+ struct work_struct work;
+};
+#define to_tilcdc_crtc(x) container_of(x, struct tilcdc_crtc, base)
+
+static void unref_worker(struct work_struct *work)
+{
+ struct tilcdc_crtc *tilcdc_crtc = container_of(work, struct tilcdc_crtc, work);
+ struct drm_device *dev = tilcdc_crtc->base.dev;
+ struct drm_framebuffer *fb;
+
+ mutex_lock(&dev->mode_config.mutex);
+ while (kfifo_get(&tilcdc_crtc->unref_fifo, &fb))
+ drm_framebuffer_unreference(fb);
+ mutex_unlock(&dev->mode_config.mutex);
+}
+
+static void set_scanout(struct drm_crtc *crtc, int n)
+{
+ static const uint32_t base_reg[] = {
+ LCDC_DMA_FB_BASE_ADDR_0_REG, LCDC_DMA_FB_BASE_ADDR_1_REG,
+ };
+ static const uint32_t ceil_reg[] = {
+ LCDC_DMA_FB_CEILING_ADDR_0_REG, LCDC_DMA_FB_CEILING_ADDR_1_REG,
+ };
+ static const uint32_t stat[] = {
+ LCDC_END_OF_FRAME0, LCDC_END_OF_FRAME1,
+ };
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+
+ pm_runtime_get_sync(dev->dev);
+ tilcdc_write(dev, base_reg[n], tilcdc_crtc->start);
+ tilcdc_write(dev, ceil_reg[n], tilcdc_crtc->end);
+ if (tilcdc_crtc->scanout[n]) {
+ if (kfifo_put(&tilcdc_crtc->unref_fifo,
+ (const struct drm_framebuffer **)&tilcdc_crtc->scanout[n])) {
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ queue_work(priv->wq, &tilcdc_crtc->work);
+ } else {
+ dev_err(dev->dev, "unref fifo full!\n");
+ drm_framebuffer_unreference(tilcdc_crtc->scanout[n]);
+ }
+ }
+ tilcdc_crtc->scanout[n] = crtc->fb;
+ drm_framebuffer_reference(tilcdc_crtc->scanout[n]);
+ tilcdc_crtc->dirty &= ~stat[n];
+ pm_runtime_put_sync(dev->dev);
+}
+
+static void update_scanout(struct drm_crtc *crtc)
+{
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+ struct drm_framebuffer *fb = crtc->fb;
+ struct drm_gem_cma_object *gem;
+ unsigned int depth, bpp;
+
+ drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp);
+ gem = drm_fb_cma_get_gem_obj(fb, 0);
+
+ tilcdc_crtc->start = gem->paddr + fb->offsets[0] +
+ (crtc->y * fb->pitches[0]) + (crtc->x * bpp/8);
+
+ tilcdc_crtc->end = tilcdc_crtc->start +
+ (crtc->mode.vdisplay * fb->pitches[0]);
+
+ if (tilcdc_crtc->dpms == DRM_MODE_DPMS_ON) {
+ /* already enabled, so just mark the frames that need
+ * updating and they will be updated on vblank:
+ */
+ tilcdc_crtc->dirty |= LCDC_END_OF_FRAME0 | LCDC_END_OF_FRAME1;
+ drm_vblank_get(dev, 0);
+ } else {
+ /* not enabled yet, so update registers immediately: */
+ set_scanout(crtc, 0);
+ set_scanout(crtc, 1);
+ }
+}
+
+static void start(struct drm_crtc *crtc)
+{
+ struct drm_device *dev = crtc->dev;
+ struct tilcdc_drm_private *priv = dev->dev_private;
+
+ if (priv->rev == 2) {
+ tilcdc_set(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
+ msleep(1);
+ tilcdc_clear(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
+ msleep(1);
+ }
+
+ tilcdc_set(dev, LCDC_DMA_CTRL_REG, LCDC_DUAL_FRAME_BUFFER_ENABLE);
+ tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_PALETTE_LOAD_MODE(DATA_ONLY));
+ tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
+}
+
+static void stop(struct drm_crtc *crtc)
+{
+ struct drm_device *dev = crtc->dev;
+
+ tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
+}
+
+static void tilcdc_crtc_destroy(struct drm_crtc *crtc)
+{
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+
+ WARN_ON(tilcdc_crtc->dpms == DRM_MODE_DPMS_ON);
+
+ drm_crtc_cleanup(crtc);
+ WARN_ON(!kfifo_is_empty(&tilcdc_crtc->unref_fifo));
+ kfifo_free(&tilcdc_crtc->unref_fifo);
+ kfree(tilcdc_crtc);
+}
+
+static int tilcdc_crtc_page_flip(struct drm_crtc *crtc,
+ struct drm_framebuffer *fb,
+ struct drm_pending_vblank_event *event)
+{
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+
+ if (tilcdc_crtc->event) {
+ dev_err(dev->dev, "already pending page flip!\n");
+ return -EBUSY;
+ }
+
+ crtc->fb = fb;
+ tilcdc_crtc->event = event;
+ update_scanout(crtc);
+
+ return 0;
+}
+
+static void tilcdc_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+ struct tilcdc_drm_private *priv = dev->dev_private;
+
+ /* we really only care about on or off: */
+ if (mode != DRM_MODE_DPMS_ON)
+ mode = DRM_MODE_DPMS_OFF;
+
+ if (tilcdc_crtc->dpms == mode)
+ return;
+
+ tilcdc_crtc->dpms = mode;
+
+ pm_runtime_get_sync(dev->dev);
+
+ if (mode == DRM_MODE_DPMS_ON) {
+ pm_runtime_forbid(dev->dev);
+ start(crtc);
+ } else {
+ tilcdc_crtc->frame_done = false;
+ stop(crtc);
+
+ /* if necessary wait for framedone irq which will still come
+ * before putting things to sleep..
+ */
+ if (priv->rev == 2) {
+ int ret = wait_event_timeout(
+ tilcdc_crtc->frame_done_wq,
+ tilcdc_crtc->frame_done,
+ msecs_to_jiffies(50));
+ if (ret == 0)
+ dev_err(dev->dev, "timeout waiting for framedone\n");
+ }
+ pm_runtime_allow(dev->dev);
+ }
+
+ pm_runtime_put_sync(dev->dev);
+}
+
+static bool tilcdc_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static void tilcdc_crtc_prepare(struct drm_crtc *crtc)
+{
+ tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
+}
+
+static void tilcdc_crtc_commit(struct drm_crtc *crtc)
+{
+ tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
+}
+
+static int tilcdc_crtc_mode_set(struct drm_crtc *crtc,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode,
+ int x, int y,
+ struct drm_framebuffer *old_fb)
+{
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ const struct tilcdc_panel_info *info = tilcdc_crtc->info;
+ uint32_t reg, hbp, hfp, hsw, vbp, vfp, vsw;
+ int ret;
+
+ ret = tilcdc_crtc_mode_valid(crtc, mode);
+ if (WARN_ON(ret))
+ return ret;
+
+ if (WARN_ON(!info))
+ return -EINVAL;
+
+ pm_runtime_get_sync(dev->dev);
+
+ /* Configure the Burst Size and fifo threshold of DMA: */
+ reg = tilcdc_read(dev, LCDC_DMA_CTRL_REG) & ~0x00000770;
+ switch (info->dma_burst_sz) {
+ case 1:
+ reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_1);
+ break;
+ case 2:
+ reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_2);
+ break;
+ case 4:
+ reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_4);
+ break;
+ case 8:
+ reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_8);
+ break;
+ case 16:
+ reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_16);
+ break;
+ default:
+ return -EINVAL;
+ }
+ reg |= (info->fifo_th << 8);
+ tilcdc_write(dev, LCDC_DMA_CTRL_REG, reg);
+
+ /* Configure the AC Bias Period and Number of Transitions per Interrupt: */
+ reg = tilcdc_read(dev, LCDC_RASTER_TIMING_2_REG) & ~0x000fff00;
+ reg |= LCDC_AC_BIAS_FREQUENCY(info->ac_bias) |
+ LCDC_AC_BIAS_TRANSITIONS_PER_INT(info->ac_bias_intrpt);
+ tilcdc_write(dev, LCDC_RASTER_TIMING_2_REG, reg);
+
+ /* Configure timings: */
+ hbp = mode->htotal - mode->hsync_end;
+ hfp = mode->hsync_start - mode->hdisplay;
+ hsw = mode->hsync_end - mode->hsync_start;
+ vbp = mode->vtotal - mode->vsync_end;
+ vfp = mode->vsync_start - mode->vdisplay;
+ vsw = mode->vsync_end - mode->vsync_start;
+
+ DBG("%dx%d, hbp=%u, hfp=%u, hsw=%u, vbp=%u, vfp=%u, vsw=%u",
+ mode->hdisplay, mode->vdisplay, hbp, hfp, hsw, vbp, vfp, vsw);
+
+ reg = (((mode->hdisplay >> 4) - 1) << 4) |
+ ((hbp & 0xff) << 24) |
+ ((hfp & 0xff) << 16) |
+ ((hsw & 0x3f) << 10);
+ if (priv->rev == 2)
+ reg |= (((mode->hdisplay >> 4) - 1) & 0x40) >> 3;
+ tilcdc_write(dev, LCDC_RASTER_TIMING_0_REG, reg);
+
+ reg = ((mode->vdisplay - 1) & 0x3ff) |
+ ((vbp & 0xff) << 24) |
+ ((vfp & 0xff) << 16) |
+ ((vsw & 0x3f) << 10);
+ tilcdc_write(dev, LCDC_RASTER_TIMING_1_REG, reg);
+
+ /* Configure display type: */
+ reg = tilcdc_read(dev, LCDC_RASTER_CTRL_REG) &
+ ~(LCDC_TFT_MODE | LCDC_MONO_8BIT_MODE | LCDC_MONOCHROME_MODE |
+ LCDC_V2_TFT_24BPP_MODE | LCDC_V2_TFT_24BPP_UNPACK | 0x000ff000);
+ reg |= LCDC_TFT_MODE; /* no monochrome/passive support */
+ if (info->tft_alt_mode)
+ reg |= LCDC_TFT_ALT_ENABLE;
+ if (priv->rev == 2) {
+ unsigned int depth, bpp;
+
+ drm_fb_get_bpp_depth(crtc->fb->pixel_format, &depth, &bpp);
+ switch (bpp) {
+ case 16:
+ break;
+ case 32:
+ reg |= LCDC_V2_TFT_24BPP_UNPACK;
+ /* fallthrough */
+ case 24:
+ reg |= LCDC_V2_TFT_24BPP_MODE;
+ break;
+ default:
+ dev_err(dev->dev, "invalid pixel format\n");
+ return -EINVAL;
+ }
+ }
+ reg |= info->fdd < 12;
+ tilcdc_write(dev, LCDC_RASTER_CTRL_REG, reg);
+
+ if (info->invert_pxl_clk)
+ tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
+ else
+ tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
+
+ if (info->sync_ctrl)
+ tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
+ else
+ tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
+
+ if (info->sync_edge)
+ tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
+ else
+ tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
+
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
+ else
+ tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
+
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
+ else
+ tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
+
+ if (info->raster_order)
+ tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
+ else
+ tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
+
+
+ update_scanout(crtc);
+ tilcdc_crtc_update_clk(crtc);
+
+ pm_runtime_put_sync(dev->dev);
+
+ return 0;
+}
+
+static int tilcdc_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+ struct drm_framebuffer *old_fb)
+{
+ update_scanout(crtc);
+ return 0;
+}
+
+static void tilcdc_crtc_load_lut(struct drm_crtc *crtc)
+{
+}
+
+static const struct drm_crtc_funcs tilcdc_crtc_funcs = {
+ .destroy = tilcdc_crtc_destroy,
+ .set_config = drm_crtc_helper_set_config,
+ .page_flip = tilcdc_crtc_page_flip,
+};
+
+static const struct drm_crtc_helper_funcs tilcdc_crtc_helper_funcs = {
+ .dpms = tilcdc_crtc_dpms,
+ .mode_fixup = tilcdc_crtc_mode_fixup,
+ .prepare = tilcdc_crtc_prepare,
+ .commit = tilcdc_crtc_commit,
+ .mode_set = tilcdc_crtc_mode_set,
+ .mode_set_base = tilcdc_crtc_mode_set_base,
+ .load_lut = tilcdc_crtc_load_lut,
+};
+
+int tilcdc_crtc_max_width(struct drm_crtc *crtc)
+{
+ struct drm_device *dev = crtc->dev;
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ int max_width = 0;
+
+ if (priv->rev == 1)
+ max_width = 1024;
+ else if (priv->rev == 2)
+ max_width = 2048;
+
+ return max_width;
+}
+
+int tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode)
+{
+ struct tilcdc_drm_private *priv = crtc->dev->dev_private;
+ unsigned int bandwidth;
+
+ if (mode->hdisplay > tilcdc_crtc_max_width(crtc))
+ return MODE_VIRTUAL_X;
+
+ /* width must be multiple of 16 */
+ if (mode->hdisplay & 0xf)
+ return MODE_VIRTUAL_X;
+
+ if (mode->vdisplay > 2048)
+ return MODE_VIRTUAL_Y;
+
+ /* filter out modes that would require too much memory bandwidth: */
+ bandwidth = mode->hdisplay * mode->vdisplay * drm_mode_vrefresh(mode);
+ if (bandwidth > priv->max_bandwidth)
+ return MODE_BAD;
+
+ return MODE_OK;
+}
+
+void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,
+ const struct tilcdc_panel_info *info)
+{
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+ tilcdc_crtc->info = info;
+}
+
+void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
+{
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ int dpms = tilcdc_crtc->dpms;
+ unsigned int lcd_clk, div;
+ int ret;
+
+ pm_runtime_get_sync(dev->dev);
+
+ if (dpms == DRM_MODE_DPMS_ON)
+ tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
+
+ /* in raster mode, minimum divisor is 2: */
+ ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);
+ if (ret) {
+ dev_err(dev->dev, "failed to set display clock rate to: %d\n",
+ crtc->mode.clock);
+ goto out;
+ }
+
+ lcd_clk = clk_get_rate(priv->clk);
+ div = lcd_clk / (crtc->mode.clock * 1000);
+
+ DBG("lcd_clk=%u, mode clock=%d, div=%u", lcd_clk, crtc->mode.clock, div);
+ DBG("fck=%lu, dpll_disp_ck=%lu", clk_get_rate(priv->clk), clk_get_rate(priv->disp_clk));
+
+ /* Configure the LCD clock divisor. */
+ tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
+ LCDC_RASTER_MODE);
+
+ if (priv->rev == 2)
+ tilcdc_set(dev, LCDC_CLK_ENABLE_REG,
+ LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
+ LCDC_V2_CORE_CLK_EN);
+
+ if (dpms == DRM_MODE_DPMS_ON)
+ tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
+
+out:
+ pm_runtime_put_sync(dev->dev);
+}
+
+irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc)
+{
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ uint32_t stat = tilcdc_read_irqstatus(dev);
+
+ if ((stat & LCDC_SYNC_LOST) && (stat & LCDC_FIFO_UNDERFLOW)) {
+ stop(crtc);
+ dev_err(dev->dev, "error: %08x\n", stat);
+ tilcdc_clear_irqstatus(dev, stat);
+ start(crtc);
+ } else if (stat & LCDC_PL_LOAD_DONE) {
+ tilcdc_clear_irqstatus(dev, stat);
+ } else {
+ struct drm_pending_vblank_event *event;
+ unsigned long flags;
+ uint32_t dirty = tilcdc_crtc->dirty & stat;
+
+ tilcdc_clear_irqstatus(dev, stat);
+
+ if (dirty & LCDC_END_OF_FRAME0)
+ set_scanout(crtc, 0);
+
+ if (dirty & LCDC_END_OF_FRAME1)
+ set_scanout(crtc, 1);
+
+ drm_handle_vblank(dev, 0);
+
+ spin_lock_irqsave(&dev->event_lock, flags);
+ event = tilcdc_crtc->event;
+ tilcdc_crtc->event = NULL;
+ if (event)
+ drm_send_vblank_event(dev, 0, event);
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+
+ if (dirty && !tilcdc_crtc->dirty)
+ drm_vblank_put(dev, 0);
+ }
+
+ if (priv->rev == 2) {
+ if (stat & LCDC_FRAME_DONE) {
+ tilcdc_crtc->frame_done = true;
+ wake_up(&tilcdc_crtc->frame_done_wq);
+ }
+ tilcdc_write(dev, LCDC_END_OF_INT_IND_REG, 0);
+ }
+
+ return IRQ_HANDLED;
+}
+
+void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file)
+{
+ struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+ struct drm_pending_vblank_event *event;
+ struct drm_device *dev = crtc->dev;
+ unsigned long flags;
+
+ /* Destroy the pending vertical blanking event associated with the
+ * pending page flip, if any, and disable vertical blanking interrupts.
+ */
+ spin_lock_irqsave(&dev->event_lock, flags);
+ event = tilcdc_crtc->event;
+ if (event && event->base.file_priv == file) {
+ tilcdc_crtc->event = NULL;
+ event->base.destroy(&event->base);
+ drm_vblank_put(dev, 0);
+ }
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev)
+{
+ struct tilcdc_crtc *tilcdc_crtc;
+ struct drm_crtc *crtc;
+ int ret;
+
+ tilcdc_crtc = kzalloc(sizeof(*tilcdc_crtc), GFP_KERNEL);
+ if (!tilcdc_crtc) {
+ dev_err(dev->dev, "allocation failed\n");
+ return NULL;
+ }
+
+ crtc = &tilcdc_crtc->base;
+
+ tilcdc_crtc->dpms = DRM_MODE_DPMS_OFF;
+ init_waitqueue_head(&tilcdc_crtc->frame_done_wq);
+
+ ret = kfifo_alloc(&tilcdc_crtc->unref_fifo, 16, GFP_KERNEL);
+ if (ret) {
+ dev_err(dev->dev, "could not allocate unref FIFO\n");
+ goto fail;
+ }
+
+ INIT_WORK(&tilcdc_crtc->work, unref_worker);
+
+ ret = drm_crtc_init(dev, crtc, &tilcdc_crtc_funcs);
+ if (ret < 0)
+ goto fail;
+
+ drm_crtc_helper_add(crtc, &tilcdc_crtc_helper_funcs);
+
+ return crtc;
+
+fail:
+ tilcdc_crtc_destroy(crtc);
+ return NULL;
+}
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
new file mode 100644
index 0000000..cf1fddc
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* LCDC DRM driver, based on da8xx-fb */
+
+#include "tilcdc_drv.h"
+#include "tilcdc_regs.h"
+#include "tilcdc_tfp410.h"
+
+#include "drm_fb_helper.h"
+
+static LIST_HEAD(module_list);
+
+void tilcdc_module_init(struct tilcdc_module *mod, const char *name,
+ const struct tilcdc_module_ops *funcs)
+{
+ mod->name = name;
+ mod->funcs = funcs;
+ INIT_LIST_HEAD(&mod->list);
+ list_add(&mod->list, &module_list);
+}
+
+void tilcdc_module_cleanup(struct tilcdc_module *mod)
+{
+ list_del(&mod->list);
+}
+
+static struct of_device_id tilcdc_of_match[];
+
+static struct drm_framebuffer *tilcdc_fb_create(struct drm_device *dev,
+ struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ return drm_fb_cma_create(dev, file_priv, mode_cmd);
+}
+
+static void tilcdc_fb_output_poll_changed(struct drm_device *dev)
+{
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ if (priv->fbdev)
+ drm_fbdev_cma_hotplug_event(priv->fbdev);
+}
+
+static const struct drm_mode_config_funcs mode_config_funcs = {
+ .fb_create = tilcdc_fb_create,
+ .output_poll_changed = tilcdc_fb_output_poll_changed,
+};
+
+static int modeset_init(struct drm_device *dev)
+{
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ struct tilcdc_module *mod;
+
+ drm_mode_config_init(dev);
+
+ priv->crtc = tilcdc_crtc_create(dev);
+
+ list_for_each_entry(mod, &module_list, list) {
+ DBG("loading module: %s", mod->name);
+ mod->funcs->modeset_init(mod, dev);
+ }
+
+ if ((priv->num_encoders = 0) || (priv->num_connectors == 0)) {
+ /* oh nos! */
+ dev_err(dev->dev, "no encoders/connectors found\n");
+ return -ENXIO;
+ }
+
+ dev->mode_config.min_width = 0;
+ dev->mode_config.min_height = 0;
+ dev->mode_config.max_width = tilcdc_crtc_max_width(priv->crtc);
+ dev->mode_config.max_height = 2048;
+ dev->mode_config.funcs = &mode_config_funcs;
+
+ return 0;
+}
+
+#ifdef CONFIG_CPU_FREQ
+static int cpufreq_transition(struct notifier_block *nb,
+ unsigned long val, void *data)
+{
+ struct tilcdc_drm_private *priv = container_of(nb,
+ struct tilcdc_drm_private, freq_transition);
+ if (val == CPUFREQ_POSTCHANGE) {
+ if (priv->lcd_fck_rate != clk_get_rate(priv->clk)) {
+ priv->lcd_fck_rate = clk_get_rate(priv->clk);
+ tilcdc_crtc_update_clk(priv->crtc);
+ }
+ }
+
+ return 0;
+}
+#endif
+
+/*
+ * DRM operations:
+ */
+
+static int tilcdc_unload(struct drm_device *dev)
+{
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ struct tilcdc_module *mod, *cur;
+
+ drm_kms_helper_poll_fini(dev);
+ drm_mode_config_cleanup(dev);
+ drm_vblank_cleanup(dev);
+
+ pm_runtime_get_sync(dev->dev);
+ drm_irq_uninstall(dev);
+ pm_runtime_put_sync(dev->dev);
+
+#ifdef CONFIG_CPU_FREQ
+ cpufreq_unregister_notifier(&priv->freq_transition,
+ CPUFREQ_TRANSITION_NOTIFIER);
+#endif
+
+ if (priv->clk)
+ clk_put(priv->clk);
+
+ if (priv->mmio)
+ iounmap(priv->mmio);
+
+ flush_workqueue(priv->wq);
+ destroy_workqueue(priv->wq);
+
+ dev->dev_private = NULL;
+
+ pm_runtime_disable(dev->dev);
+
+ list_for_each_entry_safe(mod, cur, &module_list, list) {
+ DBG("destroying module: %s", mod->name);
+ mod->funcs->destroy(mod);
+ }
+
+ kfree(priv);
+
+ return 0;
+}
+
+static int tilcdc_load(struct drm_device *dev, unsigned long flags)
+{
+ struct platform_device *pdev = dev->platformdev;
+ struct device_node *node = pdev->dev.of_node;
+ struct tilcdc_drm_private *priv;
+ struct resource *res;
+ int ret;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ dev_err(dev->dev, "failed to allocate private data\n");
+ return -ENOMEM;
+ }
+
+ dev->dev_private = priv;
+
+ priv->wq = alloc_ordered_workqueue("tilcdc", 0);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev->dev, "failed to get memory resource\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ priv->mmio = ioremap_nocache(res->start, resource_size(res));
+ if (!priv->mmio) {
+ dev_err(dev->dev, "failed to ioremap\n");
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ priv->clk = clk_get(dev->dev, "fck");
+ if (IS_ERR(priv->clk)) {
+ dev_err(dev->dev, "failed to get functional clock\n");
+ ret = -ENODEV;
+ goto fail;
+ }
+
+ priv->disp_clk = clk_get(dev->dev, "dpll_disp_ck");
+ if (IS_ERR(priv->clk)) {
+ dev_err(dev->dev, "failed to get display clock\n");
+ ret = -ENODEV;
+ goto fail;
+ }
+
+#ifdef CONFIG_CPU_FREQ
+ priv->lcd_fck_rate = clk_get_rate(priv->clk);
+ priv->freq_transition.notifier_call = cpufreq_transition;
+ ret = cpufreq_register_notifier(&priv->freq_transition,
+ CPUFREQ_TRANSITION_NOTIFIER);
+ if (ret) {
+ dev_err(dev->dev, "failed to register cpufreq notifier\n");
+ goto fail;
+ }
+#endif
+
+ if (of_property_read_u32(node, "max-bandwidth", &priv->max_bandwidth))
+ priv->max_bandwidth = 1280 * 1024 * 60;
+
+ pm_runtime_enable(dev->dev);
+
+ /* Determine LCD IP Version */
+ pm_runtime_get_sync(dev->dev);
+ switch (tilcdc_read(dev, LCDC_PID_REG)) {
+ case 0x4c100102:
+ priv->rev = 1;
+ break;
+ case 0x4f200800:
+ case 0x4f201000:
+ priv->rev = 2;
+ break;
+ default:
+ dev_warn(dev->dev, "Unknown PID Reg value 0x%08x, "
+ "defaulting to LCD revision 1\n",
+ tilcdc_read(dev, LCDC_PID_REG));
+ priv->rev = 1;
+ break;
+ }
+
+ pm_runtime_put_sync(dev->dev);
+
+ ret = modeset_init(dev);
+ if (ret < 0) {
+ dev_err(dev->dev, "failed to initialize mode setting\n");
+ goto fail;
+ }
+
+ ret = drm_vblank_init(dev, 1);
+ if (ret < 0) {
+ dev_err(dev->dev, "failed to initialize vblank\n");
+ goto fail;
+ }
+
+ pm_runtime_get_sync(dev->dev);
+ ret = drm_irq_install(dev);
+ pm_runtime_put_sync(dev->dev);
+ if (ret < 0) {
+ dev_err(dev->dev, "failed to install IRQ handler\n");
+ goto fail;
+ }
+
+ platform_set_drvdata(pdev, dev);
+
+ priv->fbdev = drm_fbdev_cma_init(dev, 16,
+ dev->mode_config.num_crtc,
+ dev->mode_config.num_connector);
+
+ drm_kms_helper_poll_init(dev);
+
+ return 0;
+
+fail:
+ tilcdc_unload(dev);
+ return ret;
+}
+
+static void tilcdc_preclose(struct drm_device *dev, struct drm_file *file)
+{
+ struct tilcdc_drm_private *priv = dev->dev_private;
+
+ tilcdc_crtc_cancel_page_flip(priv->crtc, file);
+}
+
+static void tilcdc_lastclose(struct drm_device *dev)
+{
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ drm_fbdev_cma_restore_mode(priv->fbdev);
+}
+
+static irqreturn_t tilcdc_irq(DRM_IRQ_ARGS)
+{
+ struct drm_device *dev = arg;
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ return tilcdc_crtc_irq(priv->crtc);
+}
+
+static void tilcdc_irq_preinstall(struct drm_device *dev)
+{
+ tilcdc_clear_irqstatus(dev, 0xffffffff);
+}
+
+static int tilcdc_irq_postinstall(struct drm_device *dev)
+{
+ struct tilcdc_drm_private *priv = dev->dev_private;
+
+ /* enable FIFO underflow irq: */
+ if (priv->rev == 1) {
+ tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_V1_UNDERFLOW_INT_ENA);
+ } else {
+ tilcdc_set(dev, LCDC_INT_ENABLE_SET_REG, LCDC_V2_UNDERFLOW_INT_ENA);
+ }
+
+ return 0;
+}
+
+static void tilcdc_irq_uninstall(struct drm_device *dev)
+{
+ struct tilcdc_drm_private *priv = dev->dev_private;
+
+ /* disable irqs that we might have enabled: */
+ if (priv->rev == 1) {
+ tilcdc_clear(dev, LCDC_RASTER_CTRL_REG,
+ LCDC_V1_UNDERFLOW_INT_ENA | LCDC_V1_PL_INT_ENA);
+ tilcdc_clear(dev, LCDC_DMA_CTRL_REG, LCDC_V1_END_OF_FRAME_INT_ENA);
+ } else {
+ tilcdc_clear(dev, LCDC_INT_ENABLE_SET_REG,
+ LCDC_V2_UNDERFLOW_INT_ENA | LCDC_V2_PL_INT_ENA |
+ LCDC_V2_END_OF_FRAME0_INT_ENA | LCDC_V2_END_OF_FRAME1_INT_ENA |
+ LCDC_FRAME_DONE);
+ }
+
+}
+
+static void enable_vblank(struct drm_device *dev, bool enable)
+{
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ u32 reg, mask;
+
+ if (priv->rev == 1) {
+ reg = LCDC_DMA_CTRL_REG;
+ mask = LCDC_V1_END_OF_FRAME_INT_ENA;
+ } else {
+ reg = LCDC_INT_ENABLE_SET_REG;
+ mask = LCDC_V2_END_OF_FRAME0_INT_ENA |
+ LCDC_V2_END_OF_FRAME1_INT_ENA | LCDC_FRAME_DONE;
+ }
+
+ if (enable)
+ tilcdc_set(dev, reg, mask);
+ else
+ tilcdc_clear(dev, reg, mask);
+}
+
+static int tilcdc_enable_vblank(struct drm_device *dev, int crtc)
+{
+ enable_vblank(dev, true);
+ return 0;
+}
+
+static void tilcdc_disable_vblank(struct drm_device *dev, int crtc)
+{
+ enable_vblank(dev, false);
+}
+
+#if defined(CONFIG_DEBUG_FS) || defined(CONFIG_PM_SLEEP)
+static const struct {
+ const char *name;
+ uint8_t rev;
+ uint8_t save;
+ uint32_t reg;
+} registers[] = {
+#define REG(rev, save, reg) { #reg, rev, save, reg }
+ /* exists in revision 1: */
+ REG(1, false, LCDC_PID_REG),
+ REG(1, true, LCDC_CTRL_REG),
+ REG(1, false, LCDC_STAT_REG),
+ REG(1, true, LCDC_RASTER_CTRL_REG),
+ REG(1, true, LCDC_RASTER_TIMING_0_REG),
+ REG(1, true, LCDC_RASTER_TIMING_1_REG),
+ REG(1, true, LCDC_RASTER_TIMING_2_REG),
+ REG(1, true, LCDC_DMA_CTRL_REG),
+ REG(1, true, LCDC_DMA_FB_BASE_ADDR_0_REG),
+ REG(1, true, LCDC_DMA_FB_CEILING_ADDR_0_REG),
+ REG(1, true, LCDC_DMA_FB_BASE_ADDR_1_REG),
+ REG(1, true, LCDC_DMA_FB_CEILING_ADDR_1_REG),
+ /* new in revision 2: */
+ REG(2, false, LCDC_RAW_STAT_REG),
+ REG(2, false, LCDC_MASKED_STAT_REG),
+ REG(2, false, LCDC_INT_ENABLE_SET_REG),
+ REG(2, false, LCDC_INT_ENABLE_CLR_REG),
+ REG(2, false, LCDC_END_OF_INT_IND_REG),
+ REG(2, true, LCDC_CLK_ENABLE_REG),
+ REG(2, true, LCDC_INT_ENABLE_SET_REG),
+#undef REG
+};
+#endif
+
+#ifdef CONFIG_DEBUG_FS
+static int tilcdc_regs_show(struct seq_file *m, void *arg)
+{
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct drm_device *dev = node->minor->dev;
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ unsigned i;
+
+ pm_runtime_get_sync(dev->dev);
+
+ seq_printf(m, "revision: %d\n", priv->rev);
+
+ for (i = 0; i < ARRAY_SIZE(registers); i++)
+ if (priv->rev >= registers[i].rev)
+ seq_printf(m, "%s:\t %08x\n", registers[i].name,
+ tilcdc_read(dev, registers[i].reg));
+
+ pm_runtime_put_sync(dev->dev);
+
+ return 0;
+}
+
+static int tilcdc_mm_show(struct seq_file *m, void *arg)
+{
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct drm_device *dev = node->minor->dev;
+ return drm_mm_dump_table(m, dev->mm_private);
+}
+
+static struct drm_info_list tilcdc_debugfs_list[] = {
+ { "regs", tilcdc_regs_show, 0 },
+ { "mm", tilcdc_mm_show, 0 },
+ { "fb", drm_fb_cma_debugfs_show, 0 },
+};
+
+static int tilcdc_debugfs_init(struct drm_minor *minor)
+{
+ struct drm_device *dev = minor->dev;
+ struct tilcdc_module *mod;
+ int ret;
+
+ ret = drm_debugfs_create_files(tilcdc_debugfs_list,
+ ARRAY_SIZE(tilcdc_debugfs_list),
+ minor->debugfs_root, minor);
+
+ list_for_each_entry(mod, &module_list, list)
+ if (mod->funcs->debugfs_init)
+ mod->funcs->debugfs_init(mod, minor);
+
+ if (ret) {
+ dev_err(dev->dev, "could not install tilcdc_debugfs_list\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static void tilcdc_debugfs_cleanup(struct drm_minor *minor)
+{
+ struct tilcdc_module *mod;
+ drm_debugfs_remove_files(tilcdc_debugfs_list,
+ ARRAY_SIZE(tilcdc_debugfs_list), minor);
+
+ list_for_each_entry(mod, &module_list, list)
+ if (mod->funcs->debugfs_cleanup)
+ mod->funcs->debugfs_cleanup(mod, minor);
+}
+#endif
+
+static const struct file_operations fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .unlocked_ioctl = drm_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = drm_compat_ioctl,
+#endif
+ .poll = drm_poll,
+ .read = drm_read,
+ .fasync = drm_fasync,
+ .llseek = no_llseek,
+ .mmap = drm_gem_cma_mmap,
+};
+
+static struct drm_driver tilcdc_driver = {
+ .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET,
+ .load = tilcdc_load,
+ .unload = tilcdc_unload,
+ .preclose = tilcdc_preclose,
+ .lastclose = tilcdc_lastclose,
+ .irq_handler = tilcdc_irq,
+ .irq_preinstall = tilcdc_irq_preinstall,
+ .irq_postinstall = tilcdc_irq_postinstall,
+ .irq_uninstall = tilcdc_irq_uninstall,
+ .get_vblank_counter = drm_vblank_count,
+ .enable_vblank = tilcdc_enable_vblank,
+ .disable_vblank = tilcdc_disable_vblank,
+ .gem_free_object = drm_gem_cma_free_object,
+ .gem_vm_ops = &drm_gem_cma_vm_ops,
+ .dumb_create = drm_gem_cma_dumb_create,
+ .dumb_map_offset = drm_gem_cma_dumb_map_offset,
+ .dumb_destroy = drm_gem_cma_dumb_destroy,
+#ifdef CONFIG_DEBUG_FS
+ .debugfs_init = tilcdc_debugfs_init,
+ .debugfs_cleanup = tilcdc_debugfs_cleanup,
+#endif
+ .fops = &fops,
+ .name = "tilcdc",
+ .desc = "TI LCD Controller DRM",
+ .date = "20121205",
+ .major = 1,
+ .minor = 0,
+};
+
+/*
+ * Power management:
+ */
+
+#if CONFIG_PM_SLEEP
+static int tilcdc_pm_suspend(struct device *dev)
+{
+ struct drm_device *ddev = dev_get_drvdata(dev);
+ struct tilcdc_drm_private *priv = ddev->dev_private;
+ unsigned i, n = 0;
+
+ drm_kms_helper_poll_disable(ddev);
+
+ /* Save register state: */
+ for (i = 0; i < ARRAY_SIZE(registers); i++)
+ if (registers[i].save && (priv->rev >= registers[i].rev))
+ priv->saved_register[n++] = tilcdc_read(ddev, registers[i].reg);
+
+ return 0;
+}
+
+static int tilcdc_pm_resume(struct device *dev)
+{
+ struct drm_device *ddev = dev_get_drvdata(dev);
+ struct tilcdc_drm_private *priv = ddev->dev_private;
+ unsigned i, n = 0;
+
+ /* Restore register state: */
+ for (i = 0; i < ARRAY_SIZE(registers); i++)
+ if (registers[i].save && (priv->rev >= registers[i].rev))
+ tilcdc_write(ddev, registers[i].reg, priv->saved_register[n++]);
+
+ drm_kms_helper_poll_enable(ddev);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops tilcdc_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(tilcdc_pm_suspend, tilcdc_pm_resume)
+};
+
+/*
+ * Platform driver:
+ */
+
+static int tilcdc_pdev_probe(struct platform_device *pdev)
+{
+ /* bail out early if no DT data: */
+ if (!pdev->dev.of_node) {
+ dev_err(&pdev->dev, "device-tree data is missing\n");
+ return -ENXIO;
+ }
+
+ return drm_platform_init(&tilcdc_driver, pdev);
+}
+
+static int tilcdc_pdev_remove(struct platform_device *pdev)
+{
+ drm_platform_exit(&tilcdc_driver, pdev);
+
+ return 0;
+}
+
+static struct of_device_id tilcdc_of_match[] = {
+ { .compatible = "ti,am33xx-tilcdc", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, tilcdc_of_match);
+
+static struct platform_driver tilcdc_platform_driver = {
+ .probe = tilcdc_pdev_probe,
+ .remove = tilcdc_pdev_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "tilcdc",
+ .pm = &tilcdc_pm_ops,
+ .of_match_table = tilcdc_of_match,
+ },
+};
+
+static int __init tilcdc_drm_init(void)
+{
+ DBG("init");
+ tilcdc_tfp410_init();
+ return platform_driver_register(&tilcdc_platform_driver);
+}
+
+static void __exit tilcdc_drm_fini(void)
+{
+ DBG("fini");
+ tilcdc_tfp410_fini();
+ platform_driver_unregister(&tilcdc_platform_driver);
+}
+
+module_init(tilcdc_drm_init);
+module_exit(tilcdc_drm_fini);
+
+MODULE_AUTHOR("Rob Clark <robdclark at gmail.com");
+MODULE_DESCRIPTION("TI LCD Controller DRM Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.h b/drivers/gpu/drm/tilcdc/tilcdc_drv.h
new file mode 100644
index 0000000..76321cd
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __TILCDC_DRV_H__
+#define __TILCDC_DRV_H__
+
+#include <linux/clk.h>
+#include <linux/cpufreq.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/list.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+
+struct tilcdc_drm_private {
+ void __iomem *mmio;
+
+ struct clk *disp_clk; /* display dpll */
+ struct clk *clk; /* functional clock */
+ int rev; /* IP revision */
+
+ /* don't attempt resolutions w/ higher W * H * Hz: */
+ uint32_t max_bandwidth;
+
+ /* register contents saved across suspend/resume: */
+ u32 saved_register[12];
+
+#ifdef CONFIG_CPU_FREQ
+ struct notifier_block freq_transition;
+ unsigned int lcd_fck_rate;
+#endif
+
+ struct workqueue_struct *wq;
+
+ struct drm_fbdev_cma *fbdev;
+
+ struct drm_crtc *crtc;
+
+ unsigned int num_encoders;
+ struct drm_encoder *encoders[8];
+
+ unsigned int num_connectors;
+ struct drm_connector *connectors[8];
+};
+
+/* Sub-module for display. Since we don't know at compile time what panels
+ * or display adapter(s) might be present (for ex, off chip dvi/tfp410,
+ * hdmi encoder, various lcd panels), the connector/encoder(s) are split into
+ * separate drivers. If they are probed and found to be present, they
+ * register themselves with tilcdc_register_module().
+ */
+struct tilcdc_module;
+
+struct tilcdc_module_ops {
+ /* create appropriate encoders/connectors: */
+ int (*modeset_init)(struct tilcdc_module *mod, struct drm_device *dev);
+ void (*destroy)(struct tilcdc_module *mod);
+#ifdef CONFIG_DEBUG_FS
+ /* create debugfs nodes (can be NULL): */
+ int (*debugfs_init)(struct tilcdc_module *mod, struct drm_minor *minor);
+ /* cleanup debugfs nodes (can be NULL): */
+ void (*debugfs_cleanup)(struct tilcdc_module *mod, struct drm_minor *minor);
+#endif
+};
+
+struct tilcdc_module {
+ const char *name;
+ struct list_head list;
+ const struct tilcdc_module_ops *funcs;
+};
+
+void tilcdc_module_init(struct tilcdc_module *mod, const char *name,
+ const struct tilcdc_module_ops *funcs);
+void tilcdc_module_cleanup(struct tilcdc_module *mod);
+
+
+/* Panel config that needs to be set in the crtc, but is not coming from
+ * the mode timings. The display module is expected to call
+ * tilcdc_crtc_set_panel_info() to set this during modeset.
+ */
+struct tilcdc_panel_info {
+
+ uint32_t max_bpp;
+ uint32_t min_bpp;
+
+ /* AC Bias Pin Frequency */
+ uint32_t ac_bias;
+
+ /* AC Bias Pin Transitions per Interrupt */
+ uint32_t ac_bias_intrpt;
+
+ /* DMA burst size */
+ uint32_t dma_burst_sz;
+
+ /* Bits per pixel */
+ uint32_t bpp;
+
+ /* FIFO DMA Request Delay */
+ uint32_t fdd;
+
+ /* TFT Alternative Signal Mapping (Only for active) */
+ bool tft_alt_mode;
+
+ /* 12 Bit Per Pixel (5-6-5) Mode (Only for passive) */
+ bool stn_565_mode;
+
+ /* Mono 8-bit Mode: 1=D0-D7 or 0=D0-D3 */
+ bool mono_8bit_mode;
+
+ /* Invert pixel clock */
+ bool invert_pxl_clk;
+
+ /* Horizontal and Vertical Sync Edge: 0=rising 1=falling */
+ uint32_t sync_edge;
+
+ /* Horizontal and Vertical Sync: Control: 0=ignore */
+ uint32_t sync_ctrl;
+
+ /* Raster Data Order Select: 1=Most-to-least 0=Least-to-most */
+ uint32_t raster_order;
+
+ /* DMA FIFO threshold */
+ uint32_t fifo_th;
+};
+
+#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
+
+struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev);
+void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file);
+irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc);
+void tilcdc_crtc_update_clk(struct drm_crtc *crtc);
+void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,
+ const struct tilcdc_panel_info *info);
+int tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode);
+int tilcdc_crtc_max_width(struct drm_crtc *crtc);
+
+#endif /* __TILCDC_DRV_H__ */
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_regs.h b/drivers/gpu/drm/tilcdc/tilcdc_regs.h
new file mode 100644
index 0000000..17fd1b4
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_regs.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __TILCDC_REGS_H__
+#define __TILCDC_REGS_H__
+
+/* LCDC register definitions, based on da8xx-fb */
+
+#include <linux/bitops.h>
+
+#include "tilcdc_drv.h"
+
+/* LCDC Status Register */
+#define LCDC_END_OF_FRAME1 BIT(9)
+#define LCDC_END_OF_FRAME0 BIT(8)
+#define LCDC_PL_LOAD_DONE BIT(6)
+#define LCDC_FIFO_UNDERFLOW BIT(5)
+#define LCDC_SYNC_LOST BIT(2)
+#define LCDC_FRAME_DONE BIT(0)
+
+/* LCDC DMA Control Register */
+#define LCDC_DMA_BURST_SIZE(x) ((x) << 4)
+#define LCDC_DMA_BURST_1 0x0
+#define LCDC_DMA_BURST_2 0x1
+#define LCDC_DMA_BURST_4 0x2
+#define LCDC_DMA_BURST_8 0x3
+#define LCDC_DMA_BURST_16 0x4
+#define LCDC_V1_END_OF_FRAME_INT_ENA BIT(2)
+#define LCDC_V2_END_OF_FRAME0_INT_ENA BIT(8)
+#define LCDC_V2_END_OF_FRAME1_INT_ENA BIT(9)
+#define LCDC_DUAL_FRAME_BUFFER_ENABLE BIT(0)
+
+/* LCDC Control Register */
+#define LCDC_CLK_DIVISOR(x) ((x) << 8)
+#define LCDC_RASTER_MODE 0x01
+
+/* LCDC Raster Control Register */
+#define LCDC_PALETTE_LOAD_MODE(x) ((x) << 20)
+#define PALETTE_AND_DATA 0x00
+#define PALETTE_ONLY 0x01
+#define DATA_ONLY 0x02
+
+#define LCDC_MONO_8BIT_MODE BIT(9)
+#define LCDC_RASTER_ORDER BIT(8)
+#define LCDC_TFT_MODE BIT(7)
+#define LCDC_V1_UNDERFLOW_INT_ENA BIT(6)
+#define LCDC_V2_UNDERFLOW_INT_ENA BIT(5)
+#define LCDC_V1_PL_INT_ENA BIT(4)
+#define LCDC_V2_PL_INT_ENA BIT(6)
+#define LCDC_MONOCHROME_MODE BIT(1)
+#define LCDC_RASTER_ENABLE BIT(0)
+#define LCDC_TFT_ALT_ENABLE BIT(23)
+#define LCDC_STN_565_ENABLE BIT(24)
+#define LCDC_V2_DMA_CLK_EN BIT(2)
+#define LCDC_V2_LIDD_CLK_EN BIT(1)
+#define LCDC_V2_CORE_CLK_EN BIT(0)
+#define LCDC_V2_LPP_B10 26
+#define LCDC_V2_TFT_24BPP_MODE BIT(25)
+#define LCDC_V2_TFT_24BPP_UNPACK BIT(26)
+
+/* LCDC Raster Timing 2 Register */
+#define LCDC_AC_BIAS_TRANSITIONS_PER_INT(x) ((x) << 16)
+#define LCDC_AC_BIAS_FREQUENCY(x) ((x) << 8)
+#define LCDC_SYNC_CTRL BIT(25)
+#define LCDC_SYNC_EDGE BIT(24)
+#define LCDC_INVERT_PIXEL_CLOCK BIT(22)
+#define LCDC_INVERT_HSYNC BIT(21)
+#define LCDC_INVERT_VSYNC BIT(20)
+
+/* LCDC Block */
+#define LCDC_PID_REG 0x0
+#define LCDC_CTRL_REG 0x4
+#define LCDC_STAT_REG 0x8
+#define LCDC_RASTER_CTRL_REG 0x28
+#define LCDC_RASTER_TIMING_0_REG 0x2c
+#define LCDC_RASTER_TIMING_1_REG 0x30
+#define LCDC_RASTER_TIMING_2_REG 0x34
+#define LCDC_DMA_CTRL_REG 0x40
+#define LCDC_DMA_FB_BASE_ADDR_0_REG 0x44
+#define LCDC_DMA_FB_CEILING_ADDR_0_REG 0x48
+#define LCDC_DMA_FB_BASE_ADDR_1_REG 0x4c
+#define LCDC_DMA_FB_CEILING_ADDR_1_REG 0x50
+
+/* Interrupt Registers available only in Version 2 */
+#define LCDC_RAW_STAT_REG 0x58
+#define LCDC_MASKED_STAT_REG 0x5c
+#define LCDC_INT_ENABLE_SET_REG 0x60
+#define LCDC_INT_ENABLE_CLR_REG 0x64
+#define LCDC_END_OF_INT_IND_REG 0x68
+
+/* Clock registers available only on Version 2 */
+#define LCDC_CLK_ENABLE_REG 0x6c
+#define LCDC_CLK_RESET_REG 0x70
+#define LCDC_CLK_MAIN_RESET BIT(3)
+
+
+/*
+ * Helpers:
+ */
+
+static inline void tilcdc_write(struct drm_device *dev, u32 reg, u32 data)
+{
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ iowrite32(data, priv->mmio + reg);
+}
+
+static inline u32 tilcdc_read(struct drm_device *dev, u32 reg)
+{
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ return ioread32(priv->mmio + reg);
+}
+
+static inline void tilcdc_set(struct drm_device *dev, u32 reg, u32 mask)
+{
+ tilcdc_write(dev, reg, tilcdc_read(dev, reg) | mask);
+}
+
+static inline void tilcdc_clear(struct drm_device *dev, u32 reg, u32 mask)
+{
+ tilcdc_write(dev, reg, tilcdc_read(dev, reg) & ~mask);
+}
+
+/* the register to read/clear irqstatus differs between v1 and v2 of the IP */
+static inline u32 tilcdc_irqstatus_reg(struct drm_device *dev)
+{
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ return (priv->rev == 2) ? LCDC_MASKED_STAT_REG : LCDC_STAT_REG;
+}
+
+static inline u32 tilcdc_read_irqstatus(struct drm_device *dev)
+{
+ return tilcdc_read(dev, tilcdc_irqstatus_reg(dev));
+}
+
+static inline void tilcdc_clear_irqstatus(struct drm_device *dev, u32 mask)
+{
+ tilcdc_write(dev, tilcdc_irqstatus_reg(dev), mask);
+}
+
+#endif /* __TILCDC_REGS_H__ */
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
new file mode 100644
index 0000000..eaae4dd
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/i2c.h>
+#include <linux/of_i2c.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/consumer.h>
+
+#include "tilcdc_drv.h"
+
+struct tfp410_module {
+ struct tilcdc_module base;
+ struct i2c_adapter *i2c;
+ int gpio;
+};
+#define to_tfp410_module(x) container_of(x, struct tfp410_module, base)
+
+
+static const struct tilcdc_panel_info dvi_info = {
+ .min_bpp = 16,
+ .max_bpp = 16,
+ .ac_bias = 255,
+ .ac_bias_intrpt = 0,
+ .dma_burst_sz = 16,
+ .bpp = 16,
+ .fdd = 0x80,
+ .tft_alt_mode = 0,
+ .stn_565_mode = 0,
+ .mono_8bit_mode = 0,
+ .sync_edge = 0,
+ .sync_ctrl = 1,
+ .raster_order = 0,
+};
+
+/*
+ * Encoder:
+ */
+
+struct tfp410_encoder {
+ struct drm_encoder base;
+ struct tfp410_module *mod;
+ int dpms;
+};
+#define to_tfp410_encoder(x) container_of(x, struct tfp410_encoder, base)
+
+
+static void tfp410_encoder_destroy(struct drm_encoder *encoder)
+{
+ struct tfp410_encoder *tfp410_encoder = to_tfp410_encoder(encoder);
+ drm_encoder_cleanup(encoder);
+ kfree(tfp410_encoder);
+}
+
+static void tfp410_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct tfp410_encoder *tfp410_encoder = to_tfp410_encoder(encoder);
+
+ if (tfp410_encoder->dpms == mode)
+ return;
+
+ if (mode == DRM_MODE_DPMS_ON) {
+ DBG("Power on");
+ gpio_direction_output(tfp410_encoder->mod->gpio, 1);
+ } else {
+ DBG("Power off");
+ gpio_direction_output(tfp410_encoder->mod->gpio, 0);
+ }
+
+ tfp410_encoder->dpms = mode;
+}
+
+static bool tfp410_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ /* nothing needed */
+ return true;
+}
+
+static void tfp410_encoder_prepare(struct drm_encoder *encoder)
+{
+ tfp410_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
+ tilcdc_crtc_set_panel_info(encoder->crtc, &dvi_info);
+}
+
+static void tfp410_encoder_commit(struct drm_encoder *encoder)
+{
+ tfp410_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+static void tfp410_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ /* nothing needed */
+}
+
+static const struct drm_encoder_funcs tfp410_encoder_funcs = {
+ .destroy = tfp410_encoder_destroy,
+};
+
+static const struct drm_encoder_helper_funcs tfp410_encoder_helper_funcs = {
+ .dpms = tfp410_encoder_dpms,
+ .mode_fixup = tfp410_encoder_mode_fixup,
+ .prepare = tfp410_encoder_prepare,
+ .commit = tfp410_encoder_commit,
+ .mode_set = tfp410_encoder_mode_set,
+};
+
+static struct drm_encoder *tfp410_encoder_create(struct drm_device *dev,
+ struct tfp410_module *mod)
+{
+ struct tfp410_encoder *tfp410_encoder;
+ struct drm_encoder *encoder;
+ int ret;
+
+ tfp410_encoder = kzalloc(sizeof(*tfp410_encoder), GFP_KERNEL);
+ if (!tfp410_encoder) {
+ dev_err(dev->dev, "allocation failed\n");
+ return NULL;
+ }
+
+ tfp410_encoder->dpms = DRM_MODE_DPMS_OFF;
+ tfp410_encoder->mod = mod;
+
+ encoder = &tfp410_encoder->base;
+ encoder->possible_crtcs = 1;
+
+ ret = drm_encoder_init(dev, encoder, &tfp410_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS);
+ if (ret < 0)
+ goto fail;
+
+ drm_encoder_helper_add(encoder, &tfp410_encoder_helper_funcs);
+
+ return encoder;
+
+fail:
+ tfp410_encoder_destroy(encoder);
+ return NULL;
+}
+
+/*
+ * Connector:
+ */
+
+struct tfp410_connector {
+ struct drm_connector base;
+
+ struct drm_encoder *encoder; /* our connected encoder */
+ struct tfp410_module *mod;
+};
+#define to_tfp410_connector(x) container_of(x, struct tfp410_connector, base)
+
+
+static void tfp410_connector_destroy(struct drm_connector *connector)
+{
+ struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
+ drm_connector_cleanup(connector);
+ kfree(tfp410_connector);
+}
+
+static enum drm_connector_status tfp410_connector_detect(
+ struct drm_connector *connector,
+ bool force)
+{
+ struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
+
+ if (drm_probe_ddc(tfp410_connector->mod->i2c))
+ return connector_status_connected;
+
+ return connector_status_unknown;
+}
+
+static int tfp410_connector_get_modes(struct drm_connector *connector)
+{
+ struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
+ struct edid *edid;
+ int ret = 0;
+
+ edid = drm_get_edid(connector, tfp410_connector->mod->i2c);
+
+ drm_mode_connector_update_edid_property(connector, edid);
+
+ if (edid) {
+ ret = drm_add_edid_modes(connector, edid);
+ kfree(edid);
+ }
+
+ return ret;
+}
+
+static int tfp410_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct tilcdc_drm_private *priv = connector->dev->dev_private;
+ /* our only constraints are what the crtc can generate: */
+ return tilcdc_crtc_mode_valid(priv->crtc, mode);
+}
+
+static struct drm_encoder *tfp410_connector_best_encoder(
+ struct drm_connector *connector)
+{
+ struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
+ return tfp410_connector->encoder;
+}
+
+static const struct drm_connector_funcs tfp410_connector_funcs = {
+ .destroy = tfp410_connector_destroy,
+ .dpms = drm_helper_connector_dpms,
+ .detect = tfp410_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+};
+
+static const struct drm_connector_helper_funcs tfp410_connector_helper_funcs = {
+ .get_modes = tfp410_connector_get_modes,
+ .mode_valid = tfp410_connector_mode_valid,
+ .best_encoder = tfp410_connector_best_encoder,
+};
+
+static struct drm_connector *tfp410_connector_create(struct drm_device *dev,
+ struct tfp410_module *mod, struct drm_encoder *encoder)
+{
+ struct tfp410_connector *tfp410_connector;
+ struct drm_connector *connector;
+ int ret;
+
+ tfp410_connector = kzalloc(sizeof(*tfp410_connector), GFP_KERNEL);
+ if (!tfp410_connector) {
+ dev_err(dev->dev, "allocation failed\n");
+ return NULL;
+ }
+
+ tfp410_connector->encoder = encoder;
+ tfp410_connector->mod = mod;
+
+ connector = &tfp410_connector->base;
+
+ drm_connector_init(dev, connector, &tfp410_connector_funcs,
+ DRM_MODE_CONNECTOR_DVID);
+ drm_connector_helper_add(connector, &tfp410_connector_helper_funcs);
+
+ connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
+
+ connector->interlace_allowed = 0;
+ connector->doublescan_allowed = 0;
+
+ ret = drm_mode_connector_attach_encoder(connector, encoder);
+ if (ret)
+ goto fail;
+
+ drm_sysfs_connector_add(connector);
+
+ return connector;
+
+fail:
+ tfp410_connector_destroy(connector);
+ return NULL;
+}
+
+/*
+ * Module:
+ */
+
+static int tfp410_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
+{
+ struct tfp410_module *tfp410_mod = to_tfp410_module(mod);
+ struct tilcdc_drm_private *priv = dev->dev_private;
+ struct drm_encoder *encoder;
+ struct drm_connector *connector;
+
+ encoder = tfp410_encoder_create(dev, tfp410_mod);
+ if (!encoder)
+ return -ENOMEM;
+
+ connector = tfp410_connector_create(dev, tfp410_mod, encoder);
+ if (!connector)
+ return -ENOMEM;
+
+ priv->encoders[priv->num_encoders++] = encoder;
+ priv->connectors[priv->num_connectors++] = connector;
+
+ return 0;
+}
+
+static void tfp410_destroy(struct tilcdc_module *mod)
+{
+ struct tfp410_module *tfp410_mod = to_tfp410_module(mod);
+
+ if (tfp410_mod->i2c)
+ i2c_put_adapter(tfp410_mod->i2c);
+
+ if (!IS_ERR_VALUE(tfp410_mod->gpio))
+ gpio_free(tfp410_mod->gpio);
+
+ tilcdc_module_cleanup(mod);
+ kfree(tfp410_mod);
+}
+
+static const struct tilcdc_module_ops tfp410_module_ops = {
+ .modeset_init = tfp410_modeset_init,
+ .destroy = tfp410_destroy,
+};
+
+/*
+ * Device:
+ */
+
+static struct of_device_id tfp410_of_match[];
+
+static int tfp410_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct device_node *i2c_node;
+ struct tfp410_module *tfp410_mod;
+ struct tilcdc_module *mod;
+ struct pinctrl *pinctrl;
+ uint32_t i2c_phandle;
+ int ret = -EINVAL;
+
+ /* bail out early if no DT data: */
+ if (!node) {
+ dev_err(&pdev->dev, "device-tree data is missing\n");
+ return -ENXIO;
+ }
+
+ tfp410_mod = kzalloc(sizeof(*tfp410_mod), GFP_KERNEL);
+ if (!tfp410_mod)
+ return -ENOMEM;
+
+ mod = &tfp410_mod->base;
+
+ tilcdc_module_init(mod, "tfp410", &tfp410_module_ops);
+
+ pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+ if (IS_ERR(pinctrl))
+ dev_warn(&pdev->dev, "pins are not configured\n");
+
+ if (of_property_read_u32(node, "i2c", &i2c_phandle)) {
+ dev_err(&pdev->dev, "could not get i2c bus phandle\n");
+ goto fail;
+ }
+
+ i2c_node = of_find_node_by_phandle(i2c_phandle);
+ if (!i2c_node) {
+ dev_err(&pdev->dev, "could not get i2c bus node\n");
+ goto fail;
+ }
+
+ tfp410_mod->i2c = of_find_i2c_adapter_by_node(i2c_node);
+ if (!tfp410_mod->i2c) {
+ dev_err(&pdev->dev, "could not get i2c\n");
+ goto fail;
+ }
+
+ of_node_put(i2c_node);
+
+ tfp410_mod->gpio = of_get_named_gpio_flags(node, "powerdn-gpio",
+ 0, NULL);
+ if (IS_ERR_VALUE(tfp410_mod->gpio)) {
+ dev_warn(&pdev->dev, "No power down GPIO\n");
+ } else {
+ ret = gpio_request(tfp410_mod->gpio, "DVI_PDn");
+ if (ret) {
+ dev_err(&pdev->dev, "could not get DVI_PDn gpio\n");
+ goto fail;
+ }
+ }
+
+ return 0;
+
+fail:
+ tfp410_destroy(mod);
+ return ret;
+}
+
+static int tfp410_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct of_device_id tfp410_of_match[] = {
+ { .compatible = "tilcdc,tfp410", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, tfp410_of_match);
+
+struct platform_driver tfp410_driver = {
+ .probe = tfp410_probe,
+ .remove = tfp410_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "tfp410",
+ .of_match_table = tfp410_of_match,
+ },
+};
+
+int __init tilcdc_tfp410_init(void)
+{
+ return platform_driver_register(&tfp410_driver);
+}
+
+void __exit tilcdc_tfp410_fini(void)
+{
+ platform_driver_unregister(&tfp410_driver);
+}
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_tfp410.h b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
new file mode 100644
index 0000000..5b800f1
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __TILCDC_TFP410_H__
+#define __TILCDC_TFP410_H__
+
+/* sub-module for tfp410 dvi adaptor */
+
+int tilcdc_tfp410_init(void);
+void tilcdc_tfp410_fini(void);
+
+#endif /* __TILCDC_TFP410_H__ */
--
1.8.1
^ permalink raw reply related
* [PATCH 0/4] TI LCDC DRM driver
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
To: linux-arm-kernel
I think this driver is ready to go, so I've removed the 'RFC' tag. Prove
me wrong by reviewing the patches and sending comments ;-)
The first patch adds the basic driver and TFP410 DVI output.
The second patch adds support for NXP TDA998x family of i2c connected
HDMI encoders. It is split out into an i2c encoder-slave in case someone
else has hw with the same HDMI encoder.
The final patch adds support for LCD panels, with timings and panel info
coming from device-tree.
The patch set has dependencies on the following patches that I have sent
earlier (which have not changed since then so I am not resending):
* drm/cma: add debugfs helpers
* drm: i2c encoder helper wrappers
In addition, the LCD panel patch also depends on Steffen Trumtrar's OF
display helper series. I've moved this patch to last so it can be merged
later if needed. Although I really see no reason not to merge the OF
display helper series for 3.9.
These patches and their dependencies can also be found here:
git://people.freedesktop.org/~robclark/linux tilcdc-next
http://cgit.freedesktop.org/~robclark/linux/log/?h=tilcdc-next
Rob Clark (4):
drm/tilcdc: add TI LCD Controller DRM driver (v3)
drm/i2c: nxp-tda998x (v2)
drm/tilcdc: add encoder slave
drm/tilcdc: add support for LCD panels (v4)
drivers/gpu/drm/Kconfig | 2 +
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/i2c/Makefile | 3 +
drivers/gpu/drm/i2c/tda998x_drv.c | 908 +++++++++++++++++++++++++++++++++
drivers/gpu/drm/tilcdc/Kconfig | 25 +
drivers/gpu/drm/tilcdc/Makefile | 10 +
drivers/gpu/drm/tilcdc/tilcdc_crtc.c | 597 ++++++++++++++++++++++
drivers/gpu/drm/tilcdc/tilcdc_drv.c | 611 ++++++++++++++++++++++
drivers/gpu/drm/tilcdc/tilcdc_drv.h | 159 ++++++
drivers/gpu/drm/tilcdc/tilcdc_panel.c | 443 ++++++++++++++++
drivers/gpu/drm/tilcdc/tilcdc_panel.h | 26 +
drivers/gpu/drm/tilcdc/tilcdc_regs.h | 154 ++++++
drivers/gpu/drm/tilcdc/tilcdc_slave.c | 380 ++++++++++++++
drivers/gpu/drm/tilcdc/tilcdc_slave.h | 26 +
drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++
drivers/gpu/drm/tilcdc/tilcdc_tfp410.h | 26 +
16 files changed, 3794 insertions(+)
create mode 100644 drivers/gpu/drm/i2c/tda998x_drv.c
create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
create mode 100644 drivers/gpu/drm/tilcdc/Makefile
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.c
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.h
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.c
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.h
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
--
1.8.1
^ permalink raw reply
* [PATCH 0/6] at91: some fixes for 3.8-rc5
From: Jean-Christophe PLAGNIOL-VILLARD @ 2013-01-22 22:22 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <cover.1358876553.git.nicolas.ferre@atmel.com>
On 18:50 Tue 22 Jan , Nicolas Ferre wrote:
> Hi all,
>
> I try to collect fixes for AT91 that can be applied during this development
> cycle. Please tell me now if you have more to queue or if some of the proposed
> fixes are not targetted to v3.8-final in you opinion...
Acked-by: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com>
Best Regards,
J.
^ permalink raw reply
* [PATCH V5 1/3] clk: tegra: add Tegra specific clocks
From: Stephen Warren @ 2013-01-22 22:16 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20130122212448.24671.89455@quantum>
On 01/22/2013 02:24 PM, Mike Turquette wrote:
> Quoting Stephen Warren (2013-01-16 12:52:53)
>> From: Prashant Gaikwad <pgaikwad@nvidia.com>
>>
>> Add Tegra specific clocks, pll, pll_out, peripheral, frac_divider, super.
>>
>> Signed-off-by: Prashant Gaikwad <pgaikwad@nvidia.com>
>> [swarren: alloc sizeof(*foo) not sizeof(struct foo), add comments re:
>> storing pointers to stack variables, make a timeout loop more idiomatic,
>> use _clk_pll_disable() not clk_disable_pll() from _program_pll() to
>> avoid redundant lock operations, unified tegra_clk_periph() and
>> tegra_clk_periph_nodiv(), unified tegra_clk_pll{,e}, rename all clock
>> registration functions so they don't have the same name as the clock
>> structs.]
>> Signed-off-by: Stephen Warren <swarren@nvidia.com>
>> ---
>> V5: Implemented all changes marked [swarren] in the commit descriptions.
>>
>> Mike, These patches depend on a couple patches in the Tegra tree, and
>> various other Tegra patches depend on them. I'd like to take these through
>> the Tegra tree. Can you please ack or otherwise OK this? Thanks.
>>
>
> For the series, including the updated v6 patch 2/3:
>
> Acked-by: Mike Turquette <mturquette@linaro.org>
Thanks very much for the review.
I'm working on applying these patches and the other related ARM-side
patches to the Tegra for-3.9/soc branch as we speak.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox