* Re: [PATCH v2] mtd/spi-nor: Add SPI memory controllers for Aspeed SoCs
From: Marek Vasut @ 2016-11-20 21:43 UTC (permalink / raw)
To: Cédric Le Goater, linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
Cc: David Woodhouse, Brian Norris, Boris Brezillon,
Richard Weinberger, Cyrille Pitchen,
devicetree-u79uwXL29TY76Z2rM5mHXA, Rob Herring, Mark Rutland,
Joel Stanley
In-Reply-To: <1478688149-4554-1-git-send-email-clg-Bxea+6Xhats@public.gmane.org>
On 11/09/2016 11:42 AM, Cédric Le Goater wrote:
> This driver adds mtd support for spi-nor attached to either or both of
> the Firmware Memory Controller or the SPI Flash Controller (AST2400
> only).
>
> The SMC controllers on the Aspeed AST2500 SoC are very similar to the
> ones found on the AST2400. The differences are on the number of
> supported flash modules and their default mappings in the SoC address
> space.
>
> The Aspeed AST2500 has one SPI controller for the BMC firmware and two
> for the host firmware. All controllers have now the same set of
> registers compatible with the AST2400 FMC controller and the legacy
> 'SMC' controller is fully gone.
>
> Each controller has a memory range on which it maps its flash module
> slaves. Each slave is assigned a memory window for its mapping that
> can be changed at bootime with the Segment Address Register.
>
> Each SPI flash slave can then be accessed in two modes: Command and
> User. When in User mode, accesses to the memory segment of the slaves
> are translated in SPI transfers. When in Command mode, the HW
> generates the SPI commands automatically and the memory segment is
> accessed as if doing a MMIO.
>
> Currently, only the User mode is supported. Command mode needs a
> little more work to check that the memory window on the AHB bus fits
> the module size.
>
> Based on previous work from Milton D. Miller II <miltonm-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
>
> Signed-off-by: Cédric Le Goater <clg-Bxea+6Xhats@public.gmane.org>
> ---
> Tested on:
>
> * OpenPOWER Palmetto (AST2400) with
> FMC controller : n25q256a
> SPI controller : mx25l25635e and n25q512ax3
>
> * Evaluation board (AST2500) with
> FMC controller : w25q256
> SPI controller : w25q256
>
> * OpenPOWER Witherspoon (AST2500) with
> FMC controller : mx25l25635e * 2
> SPI controller : mx66l1g45g
>
> Changes since v2:
>
> - added a set4b ops to handle difference in the controllers
> - simplified the IO routines
> - prepared for fast read using dummy cycles
>
> Work in progress:
>
> - read optimization using higher SPI clock frequencies
> - command mode to direct reads from AHB
> - DMA support
>
> .../devicetree/bindings/mtd/aspeed-smc.txt | 72 ++
> drivers/mtd/spi-nor/Kconfig | 12 +
> drivers/mtd/spi-nor/Makefile | 1 +
> drivers/mtd/spi-nor/aspeed-smc.c | 783 +++++++++++++++++++++
> 4 files changed, 868 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mtd/aspeed-smc.txt
> create mode 100644 drivers/mtd/spi-nor/aspeed-smc.c
>
> diff --git a/Documentation/devicetree/bindings/mtd/aspeed-smc.txt b/Documentation/devicetree/bindings/mtd/aspeed-smc.txt
> new file mode 100644
> index 000000000000..7516b0c01fcf
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mtd/aspeed-smc.txt
> @@ -0,0 +1,72 @@
> +* Aspeed Static Memory controller
> +* Aspeed SPI Flash Controller
> +
> +The Static memory controller in the ast2400 supports 5 chip selects
> +each can be attached to NAND, parallel NOR, or SPI NOR attached flash.
So this controller is supported by this driver, which behaves like a SPI
controller driver, yet the block can also do NAND and parallel NOR ?
> +The Firmware Memory Controller in the ast2500 supports 3 chip selects,
> +two of which are always in SPI-NOR mode and the third can be SPI-NOR
> +or parallel flash. The SPI flash controller in the ast2400 supports
> +one of 2 chip selects selected by pinmux. The two SPI flash
> +controllers in the ast2500 each support two chip selects.
This paragraph is confusing, it's hard to grok down how many different
controllers does this driver support and what are their properties from
it. It is all there, it's just hard to read.
Also, please split the DT bindings into separate patch and send them to
DT list for review.
> +Required properties:
> + - compatible : Should be one of
> + "aspeed,ast2400-fmc" for the AST2400 Static Memory Controller
> + "aspeed,ast2400-smc" for the AST2400 SPI Flash Controller
> + "aspeed,ast2500-fmc" for the AST2500 Firmware SPI Memory Controller
> + "aspeed,ast2500-smc" for the AST2500 SPI Flash Controllers
> + - reg : the first contains the control register location and length,
> + the second contains the memory window mapping address and length
> + - #address-cells : must be 1 corresponding to chip select child binding
> + - #size-cells : must be 0 corresponding to chip select child binding
> +
> +Optional properties:
> + - interrupts : Should contain the interrupt for the dma device if an fmc
> +
> +The child nodes are the SPI Flash modules which must have a compatible
> +property as specified in bindings/mtd/jedec,spi-nor.txt
> +
> +Optionally, the child node can contain properties for SPI mode (may be
> +ignored):
> + - spi-max-frequency - (optional) max frequency of spi bus
You don't need to add the (optional) here again.
> +Example:
> +fmc: fmc@1e620000 {
I'd suggest to keep the example minimal -- drop the partitions etc.
> + compatible = "aspeed,ast2400-fmc";
> + reg = < 0x1e620000 0x94
> + 0x20000000 0x02000000
> + 0x22000000 0x02000000 >;
> + #address-cells = <1>;
> + #size-cells = <0>;
> + interrupts = <19>;
> + flash@0 {
> + reg = < 0 >;
> + compatible = "jedec,spi-nor" ;
> + /* spi-max-frequency = <>; */
> + /* m25p,fast-read; */
> + #address-cells = <1>;
> + #size-cells = <1>;
> + partitions {
> + compatible = "fixed-partitions";
> + #address-cells = <1>;
> + #size-cells = <1>;
> + boot@0 {
> + label = "boot-loader";
> + reg = < 0 0x8000 >;
> + };
> + image@8000 {
> + label = "kernel-image";
> + reg = < 0x8000 0x1f8000 >;
> + };
> + };
> + };
> + flash@1 {
> + reg = < 1 >;
> + compatible = "jedec,spi-nor" ;
> + label = "alt";
> + /* spi-max-frequency = <>; */
> + status = "fail";
> + /* m25p,fast-read; */
> + };
> +};
> diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
> index 4a682ee0f632..96148600fdab 100644
> --- a/drivers/mtd/spi-nor/Kconfig
> +++ b/drivers/mtd/spi-nor/Kconfig
> @@ -76,4 +76,16 @@ config SPI_NXP_SPIFI
> Flash. Enable this option if you have a device with a SPIFI
> controller and want to access the Flash as a mtd device.
>
> +config ASPEED_FLASH_SPI
Should be SPI_ASPEED , see the other controllers and keep the list sorted.
> + tristate "Aspeed flash controllers in SPI mode"
> + depends on HAS_IOMEM && OF
> + depends on ARCH_ASPEED || COMPILE_TEST
> + # IO_SPACE_LIMIT must be equivalent to (~0UL)
> + depends on !NEED_MACH_IO_H
Why?
> + help
> + This enables support for the New Static Memory Controller
> + (FMC) in the Aspeed SoCs (AST2400 and AST2500) when attached
> + to SPI nor chips, and support for the SPI Memory controller
> + (SPI) for the BIOS.
I think there is a naming chaos between FMC, SMC (as in Static MC) and
SMC (as in SPI MC).
> endif # MTD_SPI_NOR
> diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile
> index 121695e83542..c3174ebc45c2 100644
> --- a/drivers/mtd/spi-nor/Makefile
> +++ b/drivers/mtd/spi-nor/Makefile
> @@ -4,4 +4,5 @@ obj-$(CONFIG_SPI_CADENCE_QUADSPI) += cadence-quadspi.o
> obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o
> obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o
> obj-$(CONFIG_MTD_MT81xx_NOR) += mtk-quadspi.o
> +obj-$(CONFIG_ASPEED_FLASH_SPI) += aspeed-smc.o
> obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o
> diff --git a/drivers/mtd/spi-nor/aspeed-smc.c b/drivers/mtd/spi-nor/aspeed-smc.c
> new file mode 100644
> index 000000000000..30662daf89ca
> --- /dev/null
> +++ b/drivers/mtd/spi-nor/aspeed-smc.c
> @@ -0,0 +1,783 @@
> +/*
> + * ASPEED Static Memory Controller driver
> + *
> + * Copyright (c) 2015-2016, IBM Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; either version
> + * 2 of the License, or (at your option) any later version.
> + */
> +
> +#include <linux/bug.h>
> +#include <linux/device.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/mtd/mtd.h>
> +#include <linux/mtd/partitions.h>
> +#include <linux/mtd/spi-nor.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/sysfs.h>
> +
> +#define DEVICE_NAME "aspeed-smc"
> +
> +/*
> + * In user mode all data bytes read or written to the chip decode address
> + * range are transferred to or from the SPI bus. The range is treated as a
> + * fifo of arbitratry 1, 2, or 4 byte width but each write has to be aligned
> + * to its size. The address within the multiple 8kB range is ignored when
> + * sending bytes to the SPI bus.
> + *
> + * On the arm architecture, as of Linux version 4.3, memcpy_fromio and
> + * memcpy_toio on little endian targets use the optimized memcpy routines
> + * that were designed for well behavied memory storage. These routines
> + * have a stutter if the source and destination are not both word aligned,
> + * once with a duplicate access to the source after aligning to the
> + * destination to a word boundary, and again with a duplicate access to
> + * the source when the final byte count is not word aligned.
> + *
> + * When writing or reading the fifo this stutter discards data or sends
> + * too much data to the fifo and can not be used by this driver.
> + *
> + * While the low level io string routines that implement the insl family do
> + * the desired accesses and memory increments, the cross architecture io
> + * macros make them essentially impossible to use on a memory mapped address
> + * instead of a a token from the call to iomap of an io port.
> + *
> + * These fifo routines use readl and friends to a constant io port and update
> + * the memory buffer pointer and count via explicit code. The final updates
> + * to len are optimistically suppressed.
> + */
> +static int aspeed_smc_read_from_ahb(void *buf, const void __iomem *src,
> + size_t len)
> +{
What if start of buf is unaligned ?
> + if ((((unsigned long)src | (unsigned long)buf | len) & 3) == 0) {
Uh, should use boolean OR, not bitwise or. Also, if you're testing
pointer for NULL, do if (!ptr) .
if (!src || !buf || !len)
return;
while (...)
> + while (len > 3) {
> + *(u32 *)buf = readl(src);
> + buf += 4;
> + src += 4;
> + len -= 4;
> + }
> + }
> +
> + while (len--) {
> + *(u8 *)buf = readb(src);
> + buf += 1;
> + src += 1;
> + }
> + return 0;
> +}
> +
> +static int aspeed_smc_write_to_ahb(void __iomem *dst, const void *buf,
> + size_t len)
> +{
> + if ((((unsigned long)dst | (unsigned long)buf | len) & 3) == 0) {
DTTO
> + while (len > 3) {
> + u32 val = *(u32 *)buf;
> +
> + writel(val, dst);
> + buf += 4;
> + dst += 4;
> + len -= 4;
> + }
> + }
> +
> + while (len--) {
> + u8 val = *(u8 *)buf;
> +
> + writeb(val, dst);
> + buf += 1;
> + dst += 1;
> + }
> + return 0;
> +}
> +
> +enum smc_flash_type {
> + smc_type_nor = 0, /* controller connected to nor flash */
> + smc_type_nand = 1, /* controller connected to nand flash */
> + smc_type_spi = 2, /* controller connected to spi flash */
> +};
> +
> +struct aspeed_smc_chip;
> +
> +struct aspeed_smc_info {
> + u32 maxsize; /* maximum size of 1 chip window */
> + u8 nce; /* number of chip enables */
> + u8 maxwidth; /* max width of spi bus */
> + bool hastype; /* flash type field exists in cfg reg */
> + u8 we0; /* shift for write enable bit for ce 0 */
> + u8 ctl0; /* offset in regs of ctl for ce 0 */
> + u8 time; /* offset in regs of timing */
> + u8 misc; /* offset in regs of misc settings */
> +
> + void (*set_4b)(struct aspeed_smc_chip *chip);
> +};
> +
> +static void aspeed_smc_chip_set_4b_smc_2400(struct aspeed_smc_chip *chip);
> +static void aspeed_smc_chip_set_4b(struct aspeed_smc_chip *chip);
> +
> +static const struct aspeed_smc_info fmc_2400_info = {
> + .maxsize = 64 * 1024 * 1024,
> + .nce = 5,
> + .maxwidth = 4,
> + .hastype = true,
Shouldn't all this be specified in DT ?
> + .we0 = 16,
> + .ctl0 = 0x10,
> + .time = 0x94,
> + .misc = 0x54,
> + .set_4b = aspeed_smc_chip_set_4b,
> +};
> +
> +static const struct aspeed_smc_info smc_2400_info = {
> + .maxsize = 64 * 1024 * 1024,
> + .nce = 1,
> + .maxwidth = 2,
> + .hastype = false,
> + .we0 = 0,
> + .ctl0 = 0x04,
> + .time = 0x14,
> + .misc = 0x10,
> + .set_4b = aspeed_smc_chip_set_4b_smc_2400,
> +};
> +
> +static const struct aspeed_smc_info fmc_2500_info = {
> + .maxsize = 256 * 1024 * 1024,
> + .nce = 3,
> + .maxwidth = 2,
> + .hastype = true,
> + .we0 = 16,
> + .ctl0 = 0x10,
> + .time = 0x94,
> + .misc = 0x54,
> + .set_4b = aspeed_smc_chip_set_4b,
> +};
> +
> +static const struct aspeed_smc_info smc_2500_info = {
> + .maxsize = 128 * 1024 * 1024,
> + .nce = 2,
> + .maxwidth = 2,
> + .hastype = false,
> + .we0 = 16,
> + .ctl0 = 0x10,
> + .time = 0x94,
> + .misc = 0x54,
> + .set_4b = aspeed_smc_chip_set_4b,
> +};
> +
> +enum smc_ctl_reg_value {
> + smc_base, /* base value without mode for other commands */
> + smc_read, /* command reg for (maybe fast) reads */
> + smc_write, /* command reg for writes with timings */
> + smc_num_ctl_reg_values /* last value to get count of commands */
> +};
> +
> +struct aspeed_smc_controller;
> +
> +struct aspeed_smc_chip {
> + int cs;
> + struct aspeed_smc_controller *controller;
> + __le32 __iomem *ctl; /* control register */
Why do you use __le32* here and void* below ?
> + void __iomem *base; /* base of chip window */
> + __le32 ctl_val[smc_num_ctl_reg_values]; /* controls with timing */
> + enum smc_flash_type type; /* what type of flash */
> + struct spi_nor nor;
> +};
> +
> +struct aspeed_smc_controller {
> + struct device *dev;
> +
> + struct mutex mutex; /* controller access mutex */
> + const struct aspeed_smc_info *info; /* type info of controller */
> + void __iomem *regs; /* controller registers */
> + void __iomem *windows; /* per-chip windows resource */
> +
> + struct aspeed_smc_chip *chips[0]; /* pointers to attached chips */
> +};
> +
> +/*
> + * SPI Flash Configuration Register (AST2400 SPI)
> + */
> +#define CONFIG_REG 0x0
> +#define CONFIG_ENABLE_CE_INACTIVE BIT(1)
> +#define CONFIG_WRITE BIT(0)
#define[space]FOO[tab]BIT(bar)
> +/*
> + * SPI Flash Configuration Register (AST2500 SPI)
> + * Type setting Register (AST2500 FMC and AST2400 FMC)
> + */
> +#define TYPE_SETTING_REG 0x0
> +#define CONFIG_DISABLE_LEGACY BIT(31) /* 1 on AST2500 FMC */
> +
> +#define CONFIG_CE2_WRITE BIT(18)
> +#define CONFIG_CE1_WRITE BIT(17)
> +#define CONFIG_CE0_WRITE BIT(16)
> +
> +#define CONFIG_CE2_TYPE BIT(4) /* FMC only */
> +#define CONFIG_CE1_TYPE BIT(2) /* FMC only */
> +#define CONFIG_CE0_TYPE BIT(0) /* FMC only */
> +
> +/*
> + * CE Control Register (AST2500 SPI,FMC and AST2400 FMC)
> + */
> +#define CE_CONTROL_REG 0x4
> +#define CE2_ENABLE_CE_INACTIVE BIT(10)
> +#define CE1_ENABLE_CE_INACTIVE BIT(9)
> +#define CE0_ENABLE_CE_INACTIVE BIT(8)
> +#define CE2_CONTROL_EXTENDED BIT(2)
> +#define CE1_CONTROL_EXTENDED BIT(1)
> +#define CE0_CONTROL_EXTENDED BIT(0)
> +
> +/* CE0 Control Register (depends on the controller type) */
> +#define CONTROL_SPI_AAF_MODE BIT(31)
> +#define CONTROL_SPI_IO_MODE_MASK GENMASK(30, 28)
> +#define CONTROL_SPI_IO_DUAL_DATA BIT(29)
> +#define CONTROL_SPI_IO_DUAL_ADDR_DATA (BIT(29) | BIT(28))
> +#define CONTROL_SPI_IO_QUAD_DATA BIT(30)
> +#define CONTROL_SPI_IO_QUAD_ADDR_DATA (BIT(30) | BIT(28))
> +#define CONTROL_SPI_CE_INACTIVE_SHIFT 24
> +#define CONTROL_SPI_CE_INACTIVE_MASK GENMASK(27, CONTROL_SPI_CE_INACTIVE_SHIFT)
> +/* 0 = 16T ... 15 = 1T T=HCLK */
> +#define CONTROL_SPI_COMMAND_SHIFT 16
> +#define CONTROL_SPI_DUMMY_CYCLE_COMMAND_OUTPUT BIT(15)
> +#define CONTROL_SPI_IO_DUMMY_CYCLES_HI BIT(14)
> +#define CONTROL_SPI_IO_DUMMY_CYCLES_HI_SHIFT 14
> +#define CONTROL_SPI_IO_ADDRESS_4B BIT(13) /* AST2400 SPI */
> +#define CONTROL_SPI_CLK_DIV4 BIT(13) /* others */
> +#define CONTROL_SPI_RW_MERGE BIT(12)
> +#define CONTROL_SPI_IO_DUMMY_CYCLES_LO_SHIFT 6
> +#define CONTROL_SPI_IO_DUMMY_CYCLES_LO GENMASK(7, \
> + CONTROL_SPI_IO_DUMMY_CYCLES_LO_SHIFT)
> +#define CONTROL_SPI_IO_DUMMY_CYCLES_MASK (CONTROL_SPI_IO_DUMMY_CYCLES_HI | \
> + CONTROL_SPI_IO_DUMMY_CYCLES_LO)
> +#define CONTROL_SPI_IO_DUMMY_CYCLES_SET(dummy) \
> + (((((dummy) >> 2) & 0x1) << CONTROL_SPI_IO_DUMMY_CYCLES_HI_SHIFT) | \
> + (((dummy) & 0x3) << CONTROL_SPI_IO_DUMMY_CYCLES_LO_SHIFT))
> +
> +#define CONTROL_SPI_CLOCK_FREQ_SEL_SHIFT 8
> +#define CONTROL_SPI_CLOCK_FREQ_SEL_MASK GENMASK(11, \
> + CONTROL_SPI_CLOCK_FREQ_SEL_SHIFT)
> +#define CONTROL_SPI_LSB_FIRST BIT(5)
> +#define CONTROL_SPI_CLOCK_MODE_3 BIT(4)
> +#define CONTROL_SPI_IN_DUAL_DATA BIT(3)
> +#define CONTROL_SPI_CE_STOP_ACTIVE_CONTROL BIT(2)
> +#define CONTROL_SPI_COMMAND_MODE_MASK GENMASK(1, 0)
> +#define CONTROL_SPI_COMMAND_MODE_NORMAL (0)
> +#define CONTROL_SPI_COMMAND_MODE_FREAD (1)
> +#define CONTROL_SPI_COMMAND_MODE_WRITE (2)
> +#define CONTROL_SPI_COMMAND_MODE_USER (3)
> +
> +#define CONTROL_SPI_KEEP_MASK (CONTROL_SPI_AAF_MODE | \
> + CONTROL_SPI_CE_INACTIVE_MASK | CONTROL_SPI_CLK_DIV4 | \
> + CONTROL_SPI_IO_DUMMY_CYCLES_MASK | CONTROL_SPI_CLOCK_FREQ_SEL_MASK | \
> + CONTROL_SPI_LSB_FIRST | CONTROL_SPI_CLOCK_MODE_3)
> +
> +/* Segment Address Registers */
> +#define SEGMENT_ADDR_REG0 0x30
> +#define SEGMENT_ADDR_START(_r) ((((_r) >> 16) & 0xFF) << 23)
> +#define SEGMENT_ADDR_END(_r) ((((_r) >> 24) & 0xFF) << 23)
> +
> +static u32 spi_control_fill_opcode(u8 opcode)
> +{
> + return ((u32)(opcode)) << CONTROL_SPI_COMMAND_SHIFT;
return opcode << CONTROL... , fix these horrible casts and parenthesis
globally.
> +}
> +
> +static inline u32 aspeed_smc_chip_write_bit(struct aspeed_smc_chip *chip)
> +{
> + return ((u32)1 << (chip->controller->info->we0 + chip->cs));
return BIT(...)
I'm not sure these microfunctions are even needed.
> +}
> +
> +static void aspeed_smc_chip_check_config(struct aspeed_smc_chip *chip)
> +{
> + struct aspeed_smc_controller *controller = chip->controller;
> + u32 reg;
> +
> + reg = readl(controller->regs + CONFIG_REG);
> +
> + if (!(reg & aspeed_smc_chip_write_bit(chip))) {
Invert the logic and return here, ie if (reg & BIT()) return , to trim
the indent.
> + dev_dbg(controller->dev,
> + "config write is not set ! @%p: 0x%08x\n",
> + controller->regs + CONFIG_REG, reg);
> + reg |= aspeed_smc_chip_write_bit(chip);
> + writel(reg, controller->regs + CONFIG_REG);
> + }
> +}
> +
> +static void aspeed_smc_start_user(struct spi_nor *nor)
> +{
> + struct aspeed_smc_chip *chip = nor->priv;
> + u32 ctl = chip->ctl_val[smc_base];
> +
> + /*
> + * When the chip is controlled in user mode, we need write
> + * access to send the opcodes to it. So check the config.
> + */
> + aspeed_smc_chip_check_config(chip);
> +
> + ctl |= CONTROL_SPI_COMMAND_MODE_USER |
> + CONTROL_SPI_CE_STOP_ACTIVE_CONTROL;
> + writel(ctl, chip->ctl);
> +
> + ctl &= ~CONTROL_SPI_CE_STOP_ACTIVE_CONTROL;
> + writel(ctl, chip->ctl);
> +}
> +
> +static void aspeed_smc_stop_user(struct spi_nor *nor)
> +{
> + struct aspeed_smc_chip *chip = nor->priv;
> +
> + u32 ctl = chip->ctl_val[smc_read];
> + u32 ctl2 = ctl | CONTROL_SPI_COMMAND_MODE_USER |
> + CONTROL_SPI_CE_STOP_ACTIVE_CONTROL;
> +
> + writel(ctl2, chip->ctl); /* stop user CE control */
> + writel(ctl, chip->ctl); /* default to fread or read */
> +}
> +
> +static int aspeed_smc_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
> +{
> + struct aspeed_smc_chip *chip = nor->priv;
> +
> + mutex_lock(&chip->controller->mutex);
Won't this have a horrid overhead ?
> + aspeed_smc_start_user(nor);
> + aspeed_smc_write_to_ahb(chip->base, &opcode, 1);
> + aspeed_smc_read_from_ahb(buf, chip->base, len);
> + aspeed_smc_stop_user(nor);
> +
> + mutex_unlock(&chip->controller->mutex);
> +
> + return 0;
> +}
> +
> +static int aspeed_smc_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
> + int len)
> +{
> + struct aspeed_smc_chip *chip = nor->priv;
> +
> + mutex_lock(&chip->controller->mutex);
> +
> + aspeed_smc_start_user(nor);
> + aspeed_smc_write_to_ahb(chip->base, &opcode, 1);
> + aspeed_smc_write_to_ahb(chip->base, buf, len);
> + aspeed_smc_stop_user(nor);
> +
> + mutex_unlock(&chip->controller->mutex);
> +
> + return 0;
> +}
> +
> +static void aspeed_smc_send_cmd_addr(struct spi_nor *nor, u8 cmd, u32 addr)
> +{
> + struct aspeed_smc_chip *chip = nor->priv;
> + __be32 temp;
> + u32 cmdaddr;
> +
> + switch (nor->addr_width) {
> + default:
> + WARN_ONCE(1, "Unexpected address width %u, defaulting to 3\n",
> + nor->addr_width);
> + /* FALLTHROUGH */
> + case 3:
> + cmdaddr = addr & 0xFFFFFF;
> +
> + cmdaddr |= (u32)cmd << 24;
Drop the cast.
> + temp = cpu_to_be32(cmdaddr);
> + aspeed_smc_write_to_ahb(chip->base, &temp, 4);
> + break;
> + case 4:
> + temp = cpu_to_be32(addr);
> + aspeed_smc_write_to_ahb(chip->base, &cmd, 1);
> + aspeed_smc_write_to_ahb(chip->base, &temp, 4);
> + break;
> + }
> +}
> +
> +static ssize_t aspeed_smc_read_user(struct spi_nor *nor, loff_t from,
> + size_t len, u_char *read_buf)
> +{
> + struct aspeed_smc_chip *chip = nor->priv;
> +
> + mutex_lock(&chip->controller->mutex);
> +
> + aspeed_smc_start_user(nor);
> + aspeed_smc_send_cmd_addr(nor, nor->read_opcode, from);
> + aspeed_smc_read_from_ahb(read_buf, chip->base, len);
> + aspeed_smc_stop_user(nor);
> +
> + mutex_unlock(&chip->controller->mutex);
> +
> + return len;
> +}
> +
> +static ssize_t aspeed_smc_write_user(struct spi_nor *nor, loff_t to, size_t len,
> + const u_char *write_buf)
> +{
> + struct aspeed_smc_chip *chip = nor->priv;
> +
> + mutex_lock(&chip->controller->mutex);
> +
> + aspeed_smc_start_user(nor);
> + aspeed_smc_send_cmd_addr(nor, nor->program_opcode, to);
> + aspeed_smc_write_to_ahb(chip->base, write_buf, len);
> + aspeed_smc_stop_user(nor);
> +
> + mutex_unlock(&chip->controller->mutex);
> +
> + return len;
> +}
> +
> +static int aspeed_smc_remove(struct platform_device *dev)
> +{
> + struct aspeed_smc_chip *chip;
> + struct aspeed_smc_controller *controller = platform_get_drvdata(dev);
> + int n;
> +
> + for (n = 0; n < controller->info->nce; n++) {
> + chip = controller->chips[n];
> + if (chip)
> + mtd_device_unregister(&chip->nor.mtd);
> + }
> +
> + return 0;
> +}
> +
> +static const struct of_device_id aspeed_smc_matches[] = {
> + { .compatible = "aspeed,ast2400-fmc", .data = &fmc_2400_info },
> + { .compatible = "aspeed,ast2400-smc", .data = &smc_2400_info },
> + { .compatible = "aspeed,ast2500-fmc", .data = &fmc_2500_info },
> + { .compatible = "aspeed,ast2500-smc", .data = &smc_2500_info },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, aspeed_smc_matches);
> +
> +static struct platform_device *
> +of_platform_device_create_or_find(struct device_node *child,
> + struct device *parent)
> +{
> + struct platform_device *cdev;
> +
> + cdev = of_platform_device_create(child, NULL, parent);
> + if (!cdev)
> + cdev = of_find_device_by_node(child);
> + return cdev;
> +}
> +
> +static void __iomem *window_start(struct aspeed_smc_controller *controller,
> + struct resource *r, unsigned int n)
> +{
> + u32 offset = 0;
> + u32 reg;
> +
> + if (controller->info->nce > 1) {
> + reg = readl(controller->regs + SEGMENT_ADDR_REG0 + n * 4);
> +
> + if (SEGMENT_ADDR_START(reg) >= SEGMENT_ADDR_END(reg))
> + return NULL;
> +
> + offset = SEGMENT_ADDR_START(reg) - r->start;
> + }
> +
> + return controller->windows + offset;
> +}
> +
> +static void aspeed_smc_chip_enable_write(struct aspeed_smc_chip *chip)
> +{
> + struct aspeed_smc_controller *controller = chip->controller;
> + u32 reg;
> +
> + reg = readl(controller->regs + CONFIG_REG);
> +
> + reg |= aspeed_smc_chip_write_bit(chip);
> + writel(reg, controller->regs + CONFIG_REG);
> +}
> +
> +static void aspeed_smc_chip_set_type(struct aspeed_smc_chip *chip, int type)
> +{
> + struct aspeed_smc_controller *controller = chip->controller;
> + u32 reg;
> +
> + reg = readl(controller->regs + CONFIG_REG);
> +
> + chip->type = type;
You can move this above the readl() to make the RMW block consistent.
> + reg &= ~(3 << (chip->cs * 2));
> + reg |= chip->type << (chip->cs * 2);
> + writel(reg, controller->regs + CONFIG_REG);
> +}
> +
> +/*
> + * The AST2500 FMC and AST2400 FMC flash controllers should be
> + * strapped by hardware, or autodetected, but the AST2500 SPI flash
> + * needs to be set.
> + */
> +static void aspeed_smc_chip_set_4b(struct aspeed_smc_chip *chip)
> +{
> + struct aspeed_smc_controller *controller = chip->controller;
> + u32 reg;
> +
> + if (chip->controller->info == &smc_2500_info) {
> + reg = readl(controller->regs + CE_CONTROL_REG);
> + reg |= 1 << chip->cs;
> + writel(reg, controller->regs + CE_CONTROL_REG);
> + }
> +}
> +
> +/*
> + * The AST2400 SPI flash controller does not have a CE Control
> + * register. It uses the CE0 control register to set 4Byte mode at the
> + * controller level.
> + */
> +static void aspeed_smc_chip_set_4b_smc_2400(struct aspeed_smc_chip *chip)
> +{
> + chip->ctl_val[smc_base] |= CONTROL_SPI_IO_ADDRESS_4B;
> + chip->ctl_val[smc_read] |= CONTROL_SPI_IO_ADDRESS_4B;
> +}
> +
> +static int aspeed_smc_chip_setup_init(struct aspeed_smc_chip *chip,
> + struct resource *r)
> +{
> + struct aspeed_smc_controller *controller = chip->controller;
> + const struct aspeed_smc_info *info = controller->info;
> + u32 reg, base_reg;
> +
> + /*
> + * Always turn on the write enable bit to allow opcodes to be
> + * sent in user mode.
> + */
> + aspeed_smc_chip_enable_write(chip);
> +
> + /* The driver only supports SPI type flash for the moment */
> + if (info->hastype)
> + aspeed_smc_chip_set_type(chip, smc_type_spi);
> +
> + /*
> + * Configure chip base address in memory
> + */
> + chip->base = window_start(controller, r, chip->cs);
> + if (!chip->base) {
> + dev_warn(chip->nor.dev, "CE segment window closed.\n");
> + return -1;
> + }
> +
> + /*
> + * Read the existing control register to get basic values.
> + *
> + * XXX This register probably needs more sanitation.
What's this comment about ?
> + * Do we need support for mode 3 vs mode 0 clock phasing?
> + */
> + reg = readl(chip->ctl);
> + dev_dbg(controller->dev, "control register: %08x\n", reg);
> +
> + base_reg = reg & CONTROL_SPI_KEEP_MASK;
> + if (base_reg != reg) {
> + dev_info(controller->dev,
> + "control register changed to: %08x\n",
> + base_reg);
> + }
> + chip->ctl_val[smc_base] = base_reg;
> +
> + /*
> + * Retain the prior value of the control register as the
> + * default if it was normal access mode. Otherwise start with
> + * the sanitized base value set to read mode.
> + */
> + if ((reg & CONTROL_SPI_COMMAND_MODE_MASK) ==
> + CONTROL_SPI_COMMAND_MODE_NORMAL)
> + chip->ctl_val[smc_read] = reg;
> + else
> + chip->ctl_val[smc_read] = chip->ctl_val[smc_base] |
> + CONTROL_SPI_COMMAND_MODE_NORMAL;
> +
> + dev_dbg(controller->dev, "default control register: %08x\n",
> + chip->ctl_val[smc_read]);
> + return 0;
> +}
> +
> +static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip)
> +{
> + struct aspeed_smc_controller *controller = chip->controller;
> + const struct aspeed_smc_info *info = controller->info;
> + u32 cmd;
> +
> + if (chip->nor.addr_width == 4 && info->set_4b)
> + info->set_4b(chip);
> +
> + /*
> + * base mode has not been optimized yet. use it for writes.
> + */
> + chip->ctl_val[smc_write] = chip->ctl_val[smc_base] |
> + spi_control_fill_opcode(chip->nor.program_opcode) |
> + CONTROL_SPI_COMMAND_MODE_WRITE;
> +
> + dev_dbg(controller->dev, "write control register: %08x\n",
> + chip->ctl_val[smc_write]);
> +
> + /*
> + * XXX TODO
> + * Adjust clocks if fast read and write are supported.
> + * Interpret spi-nor flags to adjust controller settings.
> + * Check if resource size big enough for detected chip and
> + * add support assisted (normal or fast-) read and dma.
> + */
> + switch (chip->nor.flash_read) {
> + case SPI_NOR_NORMAL:
> + cmd = CONTROL_SPI_COMMAND_MODE_NORMAL;
> + break;
> + case SPI_NOR_FAST:
> + cmd = CONTROL_SPI_COMMAND_MODE_FREAD;
> + break;
> + default:
> + dev_err(chip->nor.dev, "unsupported SPI read mode\n");
> + return -EINVAL;
> + }
> +
> + chip->ctl_val[smc_read] |= cmd |
> + CONTROL_SPI_IO_DUMMY_CYCLES_SET(chip->nor.read_dummy / 8);
> +
> + dev_dbg(controller->dev, "base control register: %08x\n",
> + chip->ctl_val[smc_read]);
> + return 0;
> +}
> +
> +static int aspeed_smc_probe(struct platform_device *pdev)
> +{
> + struct aspeed_smc_controller *controller;
> + const struct of_device_id *match;
> + const struct aspeed_smc_info *info;
> + struct resource *r;
> + struct device_node *child;
> + int err = 0;
> + unsigned int n;
> +
> + match = of_match_device(aspeed_smc_matches, &pdev->dev);
> + if (!match || !match->data)
> + return -ENODEV;
> + info = match->data;
> +
> + controller = devm_kzalloc(&pdev->dev, sizeof(*controller) +
> + info->nce * sizeof(controller->chips[0]), GFP_KERNEL);
> + if (!controller)
> + return -ENOMEM;
> + controller->info = info;
> +
> + mutex_init(&controller->mutex);
> + platform_set_drvdata(pdev, controller);
> +
> + r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + controller->regs = devm_ioremap_resource(&pdev->dev, r);
> + if (IS_ERR(controller->regs))
> + return PTR_ERR(controller->regs);
> +
> + r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> + controller->windows = devm_ioremap_resource(&pdev->dev, r);
> + if (IS_ERR(controller->windows))
> + return PTR_ERR(controller->windows);
> +
> + controller->dev = &pdev->dev;
> +
> + /* The pinmux or bootloader will disable the legacy mode controller */
> +
> + /*
> + * XXX Need to add arbitration to the SMC (BIOS) controller if access
> + * is shared by the host.
> + */
> + for_each_available_child_of_node(controller->dev->of_node, child) {
> + struct platform_device *cdev;
> + struct aspeed_smc_chip *chip;
Pull this into separate function, ie. like cadence_qspi.c , so we can
identify the developing boilerplate easily.
> + /* This version does not support nand or nor flash devices. */
> + if (!of_device_is_compatible(child, "jedec,spi-nor"))
> + continue;
> +
> + /*
> + * create a platform device from the of node. If the device
> + * already was created (eg from a prior bind/unbind cycle)
> + * reuse it.
> + *
> + * The creating the device node for the child here allows its
> + * use for error reporting via dev_err below.
> + */
> + cdev = of_platform_device_create_or_find(child,
> + controller->dev);
> + if (!cdev)
> + continue;
> +
> + err = of_property_read_u32(child, "reg", &n);
> + if (err == -EINVAL && info->nce == 1)
> + n = 0;
> + else if (err || n >= info->nce)
> + continue;
> + if (controller->chips[n]) {
> + dev_err(&cdev->dev,
> + "chip-id %u already in use in use by %s\n",
> + n, dev_name(controller->chips[n]->nor.dev));
> + continue;
> + }
> +
> + chip = devm_kzalloc(controller->dev, sizeof(*chip), GFP_KERNEL);
> + if (!chip)
> + continue;
> + chip->controller = controller;
> + chip->ctl = controller->regs + info->ctl0 + n * 4;
> + chip->cs = n;
> +
> + chip->nor.dev = &cdev->dev;
> + chip->nor.priv = chip;
> + spi_nor_set_flash_node(&chip->nor, child);
> + chip->nor.mtd.name = of_get_property(child, "label", NULL);
> + chip->nor.read = aspeed_smc_read_user;
> + chip->nor.write = aspeed_smc_write_user;
> + chip->nor.read_reg = aspeed_smc_read_reg;
> + chip->nor.write_reg = aspeed_smc_write_reg;
> +
> + err = aspeed_smc_chip_setup_init(chip, r);
> + if (err)
> + continue;
> +
> + /*
> + * XXX Add support for SPI_NOR_QUAD and SPI_NOR_DUAL attach
> + * when board support is present as determined by of property.
> + */
> + err = spi_nor_scan(&chip->nor, NULL, SPI_NOR_NORMAL);
> + if (err)
> + continue;
> +
> + err = aspeed_smc_chip_setup_finish(chip);
> + if (err)
> + continue;
> +
> + err = mtd_device_register(&chip->nor.mtd, NULL, 0);
> + if (err)
> + continue;
What happens if some chip fails to register ?
> + controller->chips[n] = chip;
> + }
> +
> + /* Were any children registered? */
> + for (n = 0; n < info->nce; n++)
> + if (controller->chips[n])
> + break;
> +
> + if (n == info->nce)
> + return -ENODEV;
> +
> + return 0;
> +}
> +
> +static struct platform_driver aspeed_smc_driver = {
> + .probe = aspeed_smc_probe,
> + .remove = aspeed_smc_remove,
> + .driver = {
> + .name = DEVICE_NAME,
> + .of_match_table = aspeed_smc_matches,
> + }
> +};
> +
> +module_platform_driver(aspeed_smc_driver);
> +
> +MODULE_DESCRIPTION("ASPEED Static Memory Controller Driver");
> +MODULE_AUTHOR("Milton Miller");
> +MODULE_LICENSE("GPL v2");
>
--
Best regards,
Marek Vasut
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH 2/2] mtd: spi-nor: add rockchip serial flash controller driver
From: Marek Vasut @ 2016-11-20 21:11 UTC (permalink / raw)
To: Shawn Lin, Rob Herring, David Woodhouse, Brian Norris
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Heiko Stuebner,
linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <775a8918-bc93-218a-808d-2a160137ad56-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
On 11/16/2016 02:59 AM, Shawn Lin wrote:
> Hi Marek,
Hi,
[...]
>>> +static int rockchip_sfc_read_reg(struct spi_nor *nor, u8 opcode,
>>> + u8 *buf, int len)
>>> +{
>>> + struct rockchip_sfc_priv *priv = nor->priv;
>>> + struct rockchip_sfc *sfc = priv->sfc;
>>> + int ret;
>>> + u32 tmp;
>>> + u32 i;
>>> +
>>> + ret = rockchip_sfc_op_reg(nor, opcode, len, SFC_CMD_DIR_RD);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + while (len > 0) {
>>> + tmp = readl_relaxed(sfc->regbase + SFC_DATA);
>>> + for (i = 0; i < len; i++)
>>> + *buf++ = (u8)((tmp >> (i * 8)) & 0xff);
>>
>> Won't this fail for len > 4 ?
>
> nope, this loop will reduce 4 for each successful readl. And
> reading the remained bytes which isn't aligned to DWORD, isn't it?
Try for len = 8 ... it will write 8 bytes to the buf, but half of them
would be zero. I believe it should look like:
for (i = 0; i < 4 /* was len */; i++)
*buf++ = (u8)((tmp >> (i * 8)) & 0xff);
>>
>> Also, you can use ioread32_rep() here, but (!) that won't work for
>> unaligned reads, which I dunno if they can happen here, but please do
>> double-check.
>
> yes, I have checked this API as well as others like memcpy_{to,from}io
> , etc. They will generate a external abort for arm core as the unaligned
> (DWORD) read/write via AHB aren't supported by Rockchip Socs. So I have
> to open code these stuff. This could be easily found for other
> upstreamed rockchip drivers. :)
This is normal, but you can still use the _rep variant if you handle the
corner cases.
>>
>>> + len = len - 4;
>>> + }
>>> +
>>> + return 0;
>>> +}
[...]
>>> +static ssize_t rockchip_sfc_write(struct spi_nor *nor, loff_t to,
>>> + size_t len, const u_char *write_buf)
>>> +{
>>> + struct rockchip_sfc_priv *priv = nor->priv;
>>> + struct rockchip_sfc *sfc = priv->sfc;
>>> + size_t offset;
>>> + int ret;
>>> + dma_addr_t dma_addr = 0;
>>> +
>>> + if (!sfc->use_dma)
>>> + goto no_dma;
>>
>> Seems like there's a lot of similarity between read/write .
>
> I was thinking to combine read/write with a extra argument to
> indicate WR/RD. But as we could see still some differece between
> WR and RD and there are already some condiction checks. So it
> will make the code hard to read with stuffing lots of condition
> checks. So I splited out read and write strightforward. :)
Hrm, is it that bad ?
>>> + for (offset = 0; offset < len; offset += SFC_DMA_MAX_LEN) {
>>> + size_t trans = min_t(size_t, SFC_DMA_MAX_LEN, len - offset);
>>> +
>>> + dma_addr = dma_map_single(NULL, (void *)write_buf,
>>> + trans, DMA_TO_DEVICE);
>>> + if (dma_mapping_error(sfc->dev, dma_addr)) {
>>> + dma_addr = 0;
>>> + memcpy(sfc->buffer, write_buf + offset, trans);
>>> + }
>>> +
>>> + /* Fail to map dma, use pre-allocated area instead */
>>> + ret = rockchip_sfc_dma_transfer(nor, to + offset,
>>> + dma_addr ? dma_addr :
>>> + sfc->dma_buffer,
>>> + trans, SFC_CMD_DIR_WR);
>>> + if (dma_addr)
>>> + dma_unmap_single(NULL, dma_addr,
>>> + trans, DMA_TO_DEVICE);
>>> + if (ret) {
>>> + dev_warn(nor->dev, "DMA write timeout\n");
>>> + return ret;
>>> + }
>>> + }
>>> +
>>> + return len;
>>> +no_dma:
>>> + ret = rockchip_sfc_pio_transfer(nor, to, len,
>>> + (u_char *)write_buf, SFC_CMD_DIR_WR);
>>> + if (ret) {
>>> + dev_warn(nor->dev, "PIO write timeout\n");
>>> + return ret;
>>> + }
>>> + return len;
>>> +}
[...]
>>> +static void rockchip_sfc_unregister_all(struct rockchip_sfc *sfc)
>>> +{
>>> + int i;
>>> +
>>> + for (i = 0; i < sfc->num_chip; i++)
>>> + mtd_device_unregister(&sfc->nor[i]->mtd);
>>> +}
>>> +
>>> +static int rockchip_sfc_register_all(struct rockchip_sfc *sfc)
>>> +{
>>> + struct device *dev = sfc->dev;
>>> + struct device_node *np;
>>> + int ret;
>>> +
>>> + for_each_available_child_of_node(dev->of_node, np) {
>>> + ret = rockchip_sfc_register(np, sfc);
>>> + if (ret)
>>> + goto fail;
>>> +
>>> + if (sfc->num_chip == SFC_MAX_CHIP_NUM) {
>>> + dev_warn(dev, "Exceeds the max cs limitation\n");
>>> + break;
>>> + }
>>> + }
>>> +
>>> + return 0;
>>> +
>>> +fail:
>>> + dev_err(dev, "Failed to register all chip\n");
>>> + rockchip_sfc_unregister_all(sfc);
>>
>> See cadence qspi where we only unregister the registered flashes.
>> Implement it the same way here.
>>
>
> yup, but I'm afraid that rockchip_sfc_unregister_all confused you
> as it actually unregisters the registered ones, not for all.
>
> static void rockchip_sfc_unregister_all(struct rockchip_sfc *sfc)
> {
> int i;
>
> for (i = 0; i < sfc->num_chip; i++)
> mtd_device_unregister(&sfc->nor[i]->mtd);
> }
>
> sfc->num_chip stands for how many flashes registered successfully.
Does it work if you have a hole in there ? Like if you have a flash on
chipselect 0 and chipselect 2 ?
>>> + return ret;
>>> +}
[...]
--
Best regards,
Marek Vasut
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH v2 2/2] ARM: dts: add support for Turris Omnia
From: Uwe Kleine-König @ 2016-11-20 20:31 UTC (permalink / raw)
To: Gregory CLEMENT
Cc: Andrew Lunn, Jason Cooper, Sebastian Hesselbarth,
Martin Strbačka, Tomas Hlavacek, Rob Herring, Mark Rutland,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <87h974apmd.fsf-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
[-- Attachment #1: Type: text/plain, Size: 256 bytes --]
Hello Gregory,
On Fri, Nov 18, 2016 at 02:23:22PM +0100, Gregory CLEMENT wrote:
> What is the status for this patch?
>
> I see that there is still email about the RFC version.
right, I'm preparing a v3, so don't pick up v2.
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply
* Re: [PATCH RFC] ARM: dts: add support for Turris Omnia
From: Uwe Kleine-König @ 2016-11-20 20:30 UTC (permalink / raw)
To: tomas.hlavacek-x+rMaJPWets
Cc: Andrew Lunn, Mark Rutland, marex-ynQEQJNshbs, Jason Cooper,
Martin Strba??ka, devicetree-u79uwXL29TY76Z2rM5mHXA, Rob Herring,
Gregory Clement,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
Sebastian Hesselbarth
In-Reply-To: <1479586147.10840.0-TAvD023jEQEN+BqQ9rBEUg@public.gmane.org>
[-- Attachment #1: Type: text/plain, Size: 1220 bytes --]
Hello Tomas,
On Sat, Nov 19, 2016 at 09:09:07PM +0100, tomas.hlavacek-x+rMaJPWets@public.gmane.org wrote:
> On Mon, Nov 14, 2016 at 9:28 PM, Andrew Lunn <andrew-g2DYL2Zd6BY@public.gmane.org> wrote:
> > Interrupts don't seem to work very well with the nxp,pca9538. Which
> > is probably why it is disabled by default.
>
> I was thinking about this issue and I can remember that there was an earlier
> prototype that had a shared interrupt line from PHY (88E1514) and from the
> PCA9538. In this case we needed to specifically disable the interrupt of the
> PHY to release the interrupt line (which needed a hack into PHY driver
> code). The IRQ from PHY is connected as an ordinary input to PCA9538 in
> later board prototype. And the same holds for the production version.
That would explain why I see an "irq but nobody cared" message when
booting the original system.
This isn't the problem I meant though. When adding interrupt-parent =
<&pcawan>; interrupts = <7 IRQ_TYPE_LEVEL_LOW>; to the phy node I get an
error saying that there is no irq domain associated with this device.
> Do you have CZ11NIC13 or older board revision?
CZ11NIC12 is indicated on my board.
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply
* Re: [PATCH v5 0/2] platform: Add DT support for DA8xx
From: Sekhar Nori @ 2016-11-20 13:08 UTC (permalink / raw)
To: Alexandre Bailon, khilman, robh+dt, b-liu
Cc: linux-kernel, linux-usb, devicetree, linux-arm-kernel
In-Reply-To: <1479294456-7942-1-git-send-email-abailon@baylibre.com>
On Wednesday 16 November 2016 04:37 PM, Alexandre Bailon wrote:
> This add and enable the usb otg for da850 and da850-lcdk.
> This series depends on "driver: dd DT support for DA8xx" patch set.
I see that Bin has already applied this.
> If this series is applied before the "usb: musb: da8xx: Fix few issues" patch
> set then the usb driver will always retrun -ENODEV.
And this seems to be applied too. So I am going ahead and applying this
series.
For future, please add some information on which device you are
referring to in the subject line. Reading the subject line for the
cover-letter, there is no way to know what exactly is being worked on here.
Thanks,
Sekhar
^ permalink raw reply
* Re: [RFC 2/6] drm/etnaviv: allow building etnaviv on omap devices
From: Russell King - ARM Linux @ 2016-11-20 11:43 UTC (permalink / raw)
To: Joshua Clayton
Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Rob Herring,
Robert Nelson, devicetree-u79uwXL29TY76Z2rM5mHXA,
tony-4v6yS6AI5VpBDgjK7y7TUQ, Christian Gmeiner,
linux-omap-u79uwXL29TY76Z2rM5mHXA, Lucas Stach
In-Reply-To: <2481825.ONQ5ft592J@diplodocus>
On Sat, Nov 19, 2016 at 03:58:15AM -0800, Joshua Clayton wrote:
> On Friday, November 18, 2016 10:33:42 AM Rob Herring wrote:
> > On Thu, Nov 17, 2016 at 08:44:32PM -0600, Robert Nelson wrote:
> > > Signed-off-by: Robert Nelson <robertcnelson-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> > > CC: Christian Gmeiner <christian.gmeiner-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> > > CC: Russell King <rmk+kernel-lFZ/pmaqli7XmaaqVzeoHQ@public.gmane.org>
> > > CC: Lucas Stach <l.stach-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> > > ---
> > > drivers/gpu/drm/etnaviv/Kconfig | 2 +-
> > > 1 file changed, 1 insertion(+), 1 deletion(-)
> > >
> > > diff --git a/drivers/gpu/drm/etnaviv/Kconfig b/drivers/gpu/drm/etnaviv/Kconfig
> > > index 2cde7a5..b776f41 100644
> > > --- a/drivers/gpu/drm/etnaviv/Kconfig
> > > +++ b/drivers/gpu/drm/etnaviv/Kconfig
> > > @@ -2,7 +2,7 @@
> > > config DRM_ETNAVIV
> > > tristate "ETNAVIV (DRM support for Vivante GPU IP cores)"
> > > depends on DRM
> > > - depends on ARCH_MXC || ARCH_DOVE
> > > + depends on ARCH_MXC || ARCH_DOVE || ARCH_OMAP2PLUS
> >
> > Why not just drop this line. Then it will get better build testing, too.
> >
> > > select SHMEM
> > > select TMPFS
> > > select IOMMU_API
> >
> > _______________________________________________
> > linux-arm-kernel mailing list
> > linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
> > http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
> ...building with ARCH=x86_64
> ^
> drivers/gpu/drm/etnaviv/etnaviv_gpu.c: In function ‘etnaviv_gpu_init’:
> drivers/gpu/drm/etnaviv/etnaviv_gpu.c:633:18: error: ‘PHYS_OFFSET’ undeclared (first use in this function)
> if (dma_mask < PHYS_OFFSET + SZ_2G)
> ^
> drivers/gpu/drm/etnaviv/etnaviv_gpu.c:633:18: note: each undeclared identifier is reported only once for each function it appears in
... and that's why we don't drop the dependency, although changing it
to "depends on ARM" will open it up to wider build testing on other
ARM platforms, I don't think that gains very much.
The issue is that the command buffers must be located within the first
2GB of GPU virtual address space, and that space can only be translated
(offset). What the code here is trying to do is to locate the 2GB of
virtual address space such that it overlaps the CMA region if RAM is
bigger than 2GB, or covers all of the RAM if not.
For that, we need to know where physical RAM starts.
I guess we could replace PHYS_OFFSET with virt_to_phys(PAGE_OFFSET)
here instead, which would be more arch-portable - although that would
be assuming there's no (system?) IOMMU between memory and the GPU.
--
RMK's Patch system: http://www.armlinux.org.uk/developer/patches/
FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
according to speedtest.net.
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH v6 0/5] drm: sun8i: Add DE2 HDMI video support
From: Jean-Francois Moine @ 2016-11-20 11:32 UTC (permalink / raw)
To: Dave Airlie, Maxime Ripard, Rob Herring
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
This patchset series adds HDMI video support to the Allwinner
sun8i SoCs which include the display engine 2 (DE2).
The driver contains the code for the A83T and H3, but it could be
used/extended for other SoCs as the A64, H2 and H5.
v6:
- remove audio support (other patchset to come)
- use DRM modeset data for HDMI configuration
(thanks to Jernej Škrabec)
- more meaningfull register names
- use a mutex for DE I/O protection
- merge DE and plane into one file
- don't activate the video hardware when video not started
(Maxime Ripard)
- remove 'type = "video" in DT graph ports
(Rob Herring)
- change the I/O accesses by #define instead of struct
(Maxime Ripard, André Przywara)
- remove pm functions (Maxime Ripard)
- set the pll-de/de clocks in the DT (Maxime Ripard)
- use platform_get_irq instead of irq_of_parse_and_map
(Maxime Ripard)
- rename sunxi to sun8i (Maxime Ripard)
- fix coding style errors (Maxime Ripard)
- subclass the drm structure in private data (Daniel Vetter)
- move drm_dev_register at end of init (Daniel Vetter)
v5:
- add overlay plane
- add audio support
- add support for the A83T
- add back the HDMI driver
- many bug fixes
v4:
- drivers/clk/sunxi/Makefile was missing (Emil Velikov)
v3:
- add the hardware cursor
- simplify and fix the DE2 init sequences
- generation for all SUNXI SoCs (Andre Przywara)
v2:
- remove the HDMI driver
- remarks from Chen-Yu Tsai and Russell King
- DT documentation added
Jean-Francois Moine (5):
drm: sun8i: Add a basic DRM driver for Allwinner DE2
drm: sunxi: add HDMI video support to A83T and H3
ARM: dts: sun8i-h3: add HDMI video nodes
ARM: dts: sun8i-h3: Add HDMI video to the Banana Pi M2+
ARM: dts: sun8i-h3: Add HDMI video to the Orange PI 2
.../devicetree/bindings/display/sunxi/hdmi.txt | 53 ++
.../bindings/display/sunxi/sun8i-de2.txt | 83 ++
arch/arm/boot/dts/sun8i-h3-bananapi-m2-plus.dts | 13 +
arch/arm/boot/dts/sun8i-h3-orangepi-2.dts | 13 +
arch/arm/boot/dts/sun8i-h3.dtsi | 51 ++
drivers/gpu/drm/Kconfig | 2 +
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/sun8i/Kconfig | 26 +
drivers/gpu/drm/sun8i/Makefile | 9 +
drivers/gpu/drm/sun8i/de2_crtc.c | 440 +++++++++++
drivers/gpu/drm/sun8i/de2_crtc.h | 50 ++
drivers/gpu/drm/sun8i/de2_drm.h | 48 ++
drivers/gpu/drm/sun8i/de2_drv.c | 379 ++++++++++
drivers/gpu/drm/sun8i/de2_hdmi.c | 394 ++++++++++
drivers/gpu/drm/sun8i/de2_hdmi.h | 51 ++
drivers/gpu/drm/sun8i/de2_hdmi_io.c | 839 +++++++++++++++++++++
drivers/gpu/drm/sun8i/de2_plane.c | 712 +++++++++++++++++
17 files changed, 3164 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/sunxi/hdmi.txt
create mode 100644 Documentation/devicetree/bindings/display/sunxi/sun8i-de2.txt
create mode 100644 drivers/gpu/drm/sun8i/Kconfig
create mode 100644 drivers/gpu/drm/sun8i/Makefile
create mode 100644 drivers/gpu/drm/sun8i/de2_crtc.c
create mode 100644 drivers/gpu/drm/sun8i/de2_crtc.h
create mode 100644 drivers/gpu/drm/sun8i/de2_drm.h
create mode 100644 drivers/gpu/drm/sun8i/de2_drv.c
create mode 100644 drivers/gpu/drm/sun8i/de2_hdmi.c
create mode 100644 drivers/gpu/drm/sun8i/de2_hdmi.h
create mode 100644 drivers/gpu/drm/sun8i/de2_hdmi_io.c
create mode 100644 drivers/gpu/drm/sun8i/de2_plane.c
--
2.10.2
--
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply
* [PATCH v6 5/5] ARM: dts: sun8i-h3: Add HDMI video to the Orange PI 2
From: Jean-Francois Moine @ 2016-11-20 11:26 UTC (permalink / raw)
To: Dave Airlie, Maxime Ripard, Rob Herring
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
In-Reply-To: <cover.1479641523.git.moinejf-GANU6spQydw@public.gmane.org>
Signed-off-by: Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
---
arch/arm/boot/dts/sun8i-h3-orangepi-2.dts | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/arch/arm/boot/dts/sun8i-h3-orangepi-2.dts b/arch/arm/boot/dts/sun8i-h3-orangepi-2.dts
index 047e9e1..9ecc6f1 100644
--- a/arch/arm/boot/dts/sun8i-h3-orangepi-2.dts
+++ b/arch/arm/boot/dts/sun8i-h3-orangepi-2.dts
@@ -56,6 +56,7 @@
serial0 = &uart0;
/* ethernet0 is the H3 emac, defined in sun8i-h3.dtsi */
ethernet1 = &rtl8189;
+ lcd0 = &lcd0;
};
chosen {
@@ -105,16 +106,28 @@
};
};
+&de {
+ status = "okay";
+};
+
&ehci1 {
status = "okay";
};
+&hdmi {
+ status = "okay";
+};
+
&ir {
pinctrl-names = "default";
pinctrl-0 = <&ir_pins_a>;
status = "okay";
};
+&lcd0 {
+ status = "okay";
+};
+
&mmc0 {
pinctrl-names = "default";
pinctrl-0 = <&mmc0_pins_a>, <&mmc0_cd_pin>;
--
2.10.2
^ permalink raw reply related
* [PATCH v6 4/5] ARM: dts: sun8i-h3: Add HDMI video to the Banana Pi M2+
From: Jean-Francois Moine @ 2016-11-20 11:23 UTC (permalink / raw)
To: Dave Airlie, Maxime Ripard, Rob Herring
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
In-Reply-To: <cover.1479641523.git.moinejf-GANU6spQydw@public.gmane.org>
Signed-off-by: Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
---
arch/arm/boot/dts/sun8i-h3-bananapi-m2-plus.dts | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/arch/arm/boot/dts/sun8i-h3-bananapi-m2-plus.dts b/arch/arm/boot/dts/sun8i-h3-bananapi-m2-plus.dts
index c0c49dd..4b5baae 100644
--- a/arch/arm/boot/dts/sun8i-h3-bananapi-m2-plus.dts
+++ b/arch/arm/boot/dts/sun8i-h3-bananapi-m2-plus.dts
@@ -55,6 +55,7 @@
aliases {
serial0 = &uart0;
serial1 = &uart1;
+ lcd0 = &lcd0;
};
chosen {
@@ -93,6 +94,10 @@
};
};
+&de {
+ status = "okay";
+};
+
&ehci1 {
status = "okay";
};
@@ -101,12 +106,20 @@
status = "okay";
};
+&hdmi {
+ status = "okay";
+};
+
&ir {
pinctrl-names = "default";
pinctrl-0 = <&ir_pins_a>;
status = "okay";
};
+&lcd0 {
+ status = "okay";
+};
+
&mmc0 {
pinctrl-names = "default";
pinctrl-0 = <&mmc0_pins_a>, <&mmc0_cd_pin>;
--
2.10.2
^ permalink raw reply related
* [PATCH v6 3/5] ARM: dts: sun8i-h3: add HDMI video nodes
From: Jean-Francois Moine @ 2016-11-20 11:20 UTC (permalink / raw)
To: Dave Airlie, Maxime Ripard, Rob Herring
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
In-Reply-To: <cover.1479641523.git.moinejf-GANU6spQydw@public.gmane.org>
Signed-off-by: Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
---
arch/arm/boot/dts/sun8i-h3.dtsi | 51 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/arch/arm/boot/dts/sun8i-h3.dtsi b/arch/arm/boot/dts/sun8i-h3.dtsi
index 416b825..7c6b1d5 100644
--- a/arch/arm/boot/dts/sun8i-h3.dtsi
+++ b/arch/arm/boot/dts/sun8i-h3.dtsi
@@ -140,6 +140,16 @@
#size-cells = <1>;
ranges;
+ de: de-controller@01000000 {
+ compatible = "allwinner,sun8i-h3-display-engine";
+ reg = <0x01000000 0x400000>;
+ clocks = <&ccu CLK_BUS_DE>, <&ccu CLK_DE>;
+ clock-names = "bus", "clock";
+ resets = <&ccu RST_BUS_DE>;
+ ports = <&lcd0_p>;
+ status = "disabled";
+ };
+
dma: dma-controller@01c02000 {
compatible = "allwinner,sun8i-h3-dma";
reg = <0x01c02000 0x1000>;
@@ -149,6 +159,23 @@
#dma-cells = <1>;
};
+ lcd0: lcd-controller@01c0c000 {
+ compatible = "allwinner,sun8i-a83t-tcon";
+ reg = <0x01c0c000 0x400>;
+ clocks = <&ccu CLK_BUS_TCON0>, <&ccu CLK_TCON0>;
+ clock-names = "bus", "clock";
+ resets = <&ccu RST_BUS_TCON0>;
+ interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ lcd0_p: port {
+ lcd0_hdmi: endpoint {
+ remote-endpoint = <&hdmi_lcd0>;
+ };
+ };
+ };
+
mmc0: mmc@01c0f000 {
compatible = "allwinner,sun7i-a20-mmc";
reg = <0x01c0f000 0x1000>;
@@ -314,6 +341,11 @@
clock-names = "hosc", "losc";
#clock-cells = <1>;
#reset-cells = <1>;
+
+ assigned-clocks = <&ccu CLK_PLL_DE>,
+ <&ccu CLK_DE>;
+ assigned-clock-rates = <864000000>,
+ <432000000>;
};
pio: pinctrl@01c20800 {
@@ -564,6 +596,25 @@
interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;
};
+ hdmi: hdmi@01ee0000 {
+ compatible = "allwinner,sun8i-h3-hdmi";
+ reg = <0x01ee0000 0x20000>;
+ clocks = <&ccu CLK_BUS_HDMI>, <&ccu CLK_HDMI>,
+ <&ccu CLK_HDMI_DDC>;
+ clock-names = "bus", "clock", "ddc-clock";
+ resets = <&ccu RST_BUS_HDMI0>, <&ccu RST_BUS_HDMI1>;
+ reset-names = "hdmi0", "hdmi1";
+ status = "disabled";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 { /* video */
+ reg = <0>;
+ hdmi_lcd0: endpoint {
+ remote-endpoint = <&lcd0_hdmi>;
+ };
+ };
+ };
+
rtc: rtc@01f00000 {
compatible = "allwinner,sun6i-a31-rtc";
reg = <0x01f00000 0x54>;
--
2.10.2
^ permalink raw reply related
* [PATCH v4 3/3] dmaengine: sun6i: share the dma driver with sun50i
From: Hao Zhang @ 2016-11-20 10:45 UTC (permalink / raw)
To: maxime.ripard, wens, dan.j.williams, vinod.koul, mark.rutland,
robh+dt, catalin.marinas, will.deacon
Cc: dmaengine, linux-kernel, devicetree, linux-arm-kernel, hao5781286
In-Reply-To: <1479638740-20520-1-git-send-email-hao5781286@gmail.com>
Changes the limited buswith to 8 bytes,and add
the test in sun6i_dma_config function
Accroding to sun6i dma driver, i think ,if the client
doesn't configure the address width with dmaengine_slave_config
function, it would use the default width. So we can add the test
in sun6i_dma_config function called by dmaengine_slave_config,
and test the configuration whether is support for the device.
Signed-off-by: Hao Zhang <hao5781286@gmail.com>
---
drivers/dma/sun6i-dma.c | 33 ++++++++++++++++++++++++++++++++-
1 file changed, 32 insertions(+), 1 deletion(-)
diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index a235878..f7c90b6 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -250,7 +250,7 @@ static inline s8 convert_burst(u32 maxburst)
static inline s8 convert_buswidth(enum dma_slave_buswidth addr_width)
{
if ((addr_width < DMA_SLAVE_BUSWIDTH_1_BYTE) ||
- (addr_width > DMA_SLAVE_BUSWIDTH_4_BYTES))
+ (addr_width > DMA_SLAVE_BUSWIDTH_8_BYTES))
return -EINVAL;
return addr_width >> 1;
@@ -758,6 +758,18 @@ static int sun6i_dma_config(struct dma_chan *chan,
{
struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
+ if ((BIT(config->src_addr_width) | chan->device->src_addr_widths) !=
+ chan->device->src_addr_widths) {
+ dev_err(chan2dev(chan), "Invalid DMA configuration\n");
+ return -EINVAL;
+ }
+
+ if ((BIT(config->dst_addr_width) | chan->device->dst_addr_widths) !=
+ chan->device->dst_addr_widths) {
+ dev_err(chan2dev(chan), "Invalid DMA configuration\n");
+ return -EINVAL;
+ }
+
memcpy(&vchan->cfg, config, sizeof(*config));
return 0;
@@ -1028,11 +1040,23 @@ static struct sun6i_dma_config sun8i_h3_dma_cfg = {
.nr_max_vchans = 34,
};
+/*
+ * The A64 has 8 physical channels, a maximum DRQ port id of 27,
+ * and a total of 38 usable source and destination endpoints.
+ */
+
+static struct sun6i_dma_config sun50i_a64_dma_cfg = {
+ .nr_max_channels = 8,
+ .nr_max_requests = 27,
+ .nr_max_vchans = 38,
+};
+
static const struct of_device_id sun6i_dma_match[] = {
{ .compatible = "allwinner,sun6i-a31-dma", .data = &sun6i_a31_dma_cfg },
{ .compatible = "allwinner,sun8i-a23-dma", .data = &sun8i_a23_dma_cfg },
{ .compatible = "allwinner,sun8i-a83t-dma", .data = &sun8i_a83t_dma_cfg },
{ .compatible = "allwinner,sun8i-h3-dma", .data = &sun8i_h3_dma_cfg },
+ { .compatible = "allwinner,sun50i-a64-dma", .data = &sun50i_a64_dma_cfg },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sun6i_dma_match);
@@ -1112,6 +1136,13 @@ static int sun6i_dma_probe(struct platform_device *pdev)
BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
sdc->slave.directions = BIT(DMA_DEV_TO_MEM) |
BIT(DMA_MEM_TO_DEV);
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "allwinner,sun50i-a64-dma")) {
+ sdc->slave.src_addr_widths |= BIT(DMA_SLAVE_BUSWIDTH_8_BYTES);
+ sdc->slave.dst_addr_widths |= BIT(DMA_SLAVE_BUSWIDTH_8_BYTES);
+ }
+
sdc->slave.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
sdc->slave.dev = &pdev->dev;
--
2.7.4
^ permalink raw reply related
* [PATCH v4 2/3] ARM64: dts: sun6i: add dma node for a64.
From: Hao Zhang @ 2016-11-20 10:45 UTC (permalink / raw)
To: maxime.ripard, wens, dan.j.williams, vinod.koul, mark.rutland,
robh+dt, catalin.marinas, will.deacon
Cc: dmaengine, linux-kernel, devicetree, linux-arm-kernel, hao5781286
In-Reply-To: <1479638740-20520-1-git-send-email-hao5781286@gmail.com>
Accroding to the Allwinner_A64_User_Manual_V1.0 P198
the DMA base address is 0x01c02000.
Signed-off-by: Hao Zhang <hao5781286@gmail.com>
---
arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
index e3c3d7d8..403bbfa 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi
@@ -122,6 +122,15 @@
#size-cells = <1>;
ranges;
+ dma: dma-controller@1c02000 {
+ compatible = "allwinner,sun50i-a64-dma";
+ reg = <0x01c02000 0x1000>;
+ interrupts = <GIC_SPI 50 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_DMA>;
+ resets = <&ccu RST_BUS_DMA>;
+ #dma-cells = <1>;
+ };
+
ccu: clock@01c20000 {
compatible = "allwinner,sun50i-a64-ccu";
reg = <0x01c20000 0x400>;
--
2.7.4
^ permalink raw reply related
* [PATCH v4 1/3] Documentation: DT: add dma compatible for sun50i A64 SOC
From: Hao Zhang @ 2016-11-20 10:45 UTC (permalink / raw)
To: maxime.ripard, wens, dan.j.williams, vinod.koul, mark.rutland,
robh+dt, catalin.marinas, will.deacon
Cc: dmaengine, linux-kernel, devicetree, linux-arm-kernel, hao5781286
In-Reply-To: <1479638740-20520-1-git-send-email-hao5781286@gmail.com>
This add the property of Allwinner sun50i A64 dma.
Signed-off-by: Hao Zhang <hao5781286@gmail.com>
---
Documentation/devicetree/bindings/dma/sun6i-dma.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/dma/sun6i-dma.txt b/Documentation/devicetree/bindings/dma/sun6i-dma.txt
index 6b26704..4398b99 100644
--- a/Documentation/devicetree/bindings/dma/sun6i-dma.txt
+++ b/Documentation/devicetree/bindings/dma/sun6i-dma.txt
@@ -9,6 +9,7 @@ Required properties:
"allwinner,sun8i-a23-dma"
"allwinner,sun8i-a83t-dma"
"allwinner,sun8i-h3-dma"
+ "allwinner,sun50i-a64-dma"
- reg: Should contain the registers base address and length
- interrupts: Should contain a reference to the interrupt used by this device
- clocks: Should contain a reference to the parent AHB clock
--
2.7.4
^ permalink raw reply related
* [PATCH v4 0/3] dmaengine: sun6i: add the support for the Allwinner A64 SOC.
From: Hao Zhang @ 2016-11-20 10:45 UTC (permalink / raw)
To: maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8, wens-jdAy2FN1RRM,
dan.j.williams-ral2JQCrhuEAvxtiuMwx3w,
vinod.koul-ral2JQCrhuEAvxtiuMwx3w, mark.rutland-5wv7dgnIgG8,
robh+dt-DgEjT+Ai2ygdnm+yROfE0A, catalin.marinas-5wv7dgnIgG8,
will.deacon-5wv7dgnIgG8
Cc: dmaengine-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
hao5781286-Re5JQEeQqe8AvxtiuMwx3w
Abort Allwinner A64 SOC :
Allwinner A64 (sun50i) 64bit SoC features a Quad-Core Cortex-A53 ARM CPU,
and a Mali400 MP2 GPU from ARM.
It has 8 channel DMA which flexible
data width of 8/16/32/64-bit. Detailed info about it is on
Allwinner_A64_User_Manual_V1.0 page 196 and A64_Datasheet_V1.1 page 8.
Document:
A64 Datasheet v1.1: http://files.pine64.org/doc/datasheet/pine64/A64_Datasheet_V1.1.pdf
A64 User Manual v1.0: http://files.pine64.org/doc/datasheet/pine64/Allwinner_A64_User_Manual_V1.0.pdf
Abort Pine64:
The Pine64 is a cost-optimized board with SOC A64 sporting ARMv8 (64-bit ARM) capable cores.
Detail info :
https://linux-sunxi.org/Pine64
http://wiki.pine64.org/index.php/Main_Page
PATCH LOG:
PATCH[v3,1/3]: https://patchwork.kernel.org/patch/9415765/
PATCH[v3,2/3]: https://patchwork.kernel.org/patch/9415761/
PATCH[V3,3/3]: https://patchwork.kernel.org/patch/9415761/
Changes in v4 :
PATCH[v4,1/3]:
Since PATCH[v3,1/3] has Acked-by Maxime Ripard and Rob Herring
this is resend it.
PATCH[v4,2/3]:
Accept the comments from Maxime Ripard and add the dma nodes by
base adress, thinks!
PATCH[V4,3/3]:
Accept the hint from Maxime Ripard, add the 8 bytes bus width for
A64 dma and add the buswidth test, thinks!
It has test on Pine64 using dmatest.
Regards :)
Hao Zhang (3):
Documentation: DT: add dma compatible for sun50i A64 SOC
ARM64: dts: sun6i: add dma node for a64.
dmaengine: sun6i: share the dma driver with sun50i
.../devicetree/bindings/dma/sun6i-dma.txt | 1 +
arch/arm64/boot/dts/allwinner/sun50i-a64.dtsi | 9 ++++++
drivers/dma/sun6i-dma.c | 33 +++++++++++++++++++++-
3 files changed, 42 insertions(+), 1 deletion(-)
--
2.7.4
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH v6 2/5] drm: sun8i: add HDMI video support to A83T and H3
From: Jean-Francois Moine @ 2016-11-20 9:56 UTC (permalink / raw)
To: Dave Airlie, Maxime Ripard, Rob Herring
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
In-Reply-To: <cover.1479641523.git.moinejf-GANU6spQydw@public.gmane.org>
This patch adds a HDMI video driver to the Allwinner's SoCs A83T and H3.
Signed-off-by: Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
---
.../devicetree/bindings/display/sunxi/hdmi.txt | 53 ++
drivers/gpu/drm/sun8i/Kconfig | 7 +
drivers/gpu/drm/sun8i/Makefile | 2 +
drivers/gpu/drm/sun8i/de2_hdmi.c | 394 ++++++++++
drivers/gpu/drm/sun8i/de2_hdmi.h | 51 ++
drivers/gpu/drm/sun8i/de2_hdmi_io.c | 839 +++++++++++++++++++++
6 files changed, 1346 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/sunxi/hdmi.txt
create mode 100644 drivers/gpu/drm/sun8i/de2_hdmi.c
create mode 100644 drivers/gpu/drm/sun8i/de2_hdmi.h
create mode 100644 drivers/gpu/drm/sun8i/de2_hdmi_io.c
diff --git a/Documentation/devicetree/bindings/display/sunxi/hdmi.txt b/Documentation/devicetree/bindings/display/sunxi/hdmi.txt
new file mode 100644
index 0000000..85709ab
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/sunxi/hdmi.txt
@@ -0,0 +1,53 @@
+Allwinner HDMI Transmitter
+==========================
+
+The Allwinner HDMI transmitters are included in the SoCs.
+They support audio and video.
+
+Required properties:
+ - #address-cells : should be <1>
+ - #size-cells : should be <0>
+ - compatible : should be one of
+ "allwinner,sun8i-a83t-hdmi"
+ "allwinner,sun8i-h3-hdmi"
+ - clocks : phandles to the HDMI clocks as described in
+ Documentation/devicetree/bindings/clock/clock-bindings.txt
+ - clock-names : must be
+ "gate" : bus gate
+ "clock" : streaming clock
+ "ddc-clock" : DDC clock
+ - resets : One or two phandles to the HDMI resets
+ - reset-names : when 2 phandles, must be
+ "hdmi0" and "hdmi1"
+
+Required nodes:
+ - port: Audio and video input port nodes with endpoint definitions
+ as defined in Documentation/devicetree/bindings/graph.txt.
+ port@0 is video and port@1 is audio.
+
+Example:
+
+ hdmi: hdmi@01ee0000 {
+ compatible = "allwinner,sun8i-a83t-hdmi";
+ reg = <0x01ee0000 0x20000>;
+ clocks = <&ccu CLK_BUS_HDMI>, <&ccu CLK_HDMI>,
+ <&ccu CLK_HDMI_DDC>;
+ clock-names = "gate", "clock", "ddc-clock";
+ resets = <&ccu RST_HDMI0>, <&ccu RST_HDMI1>;
+ reset-names = "hdmi0", "hdmi1";
+ ...
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 { /* video */
+ reg = <0>;
+ hdmi_lcd1: endpoint {
+ remote-endpoint = <&lcd1_hdmi>;
+ };
+ };
+ port@1 { /* audio */
+ reg = <1>;
+ hdmi_i2s2: endpoint {
+ remote-endpoint = <&i2s2_hdmi>;
+ };
+ };
+ };
diff --git a/drivers/gpu/drm/sun8i/Kconfig b/drivers/gpu/drm/sun8i/Kconfig
index 6940895..5c4607b 100644
--- a/drivers/gpu/drm/sun8i/Kconfig
+++ b/drivers/gpu/drm/sun8i/Kconfig
@@ -17,3 +17,10 @@ config DRM_SUN8I_DE2
Choose this option if your Allwinner chipset has the DE2 interface
as the A64, A83T and H3. If M is selected the module will be called
sun8i-de2-drm.
+
+config DRM_SUN8I_DE2_HDMI
+ tristate "Support for DE2 HDMI"
+ depends on DRM_SUN8I_DE2
+ help
+ Choose this option if you use want HDMI on DE2.
+ If M is selected the module will be called sun8i-de2-hdmi.
diff --git a/drivers/gpu/drm/sun8i/Makefile b/drivers/gpu/drm/sun8i/Makefile
index f107919..6ba97c2 100644
--- a/drivers/gpu/drm/sun8i/Makefile
+++ b/drivers/gpu/drm/sun8i/Makefile
@@ -3,5 +3,7 @@
#
sun8i-de2-drm-objs := de2_drv.o de2_crtc.o de2_plane.o
+sun8i-de2-hdmi-objs := de2_hdmi.o de2_hdmi_io.o
obj-$(CONFIG_DRM_SUN8I_DE2) += sun8i-de2-drm.o
+obj-$(CONFIG_DRM_SUN8I_DE2_HDMI) += sun8i-de2-hdmi.o
diff --git a/drivers/gpu/drm/sun8i/de2_hdmi.c b/drivers/gpu/drm/sun8i/de2_hdmi.c
new file mode 100644
index 0000000..9898a12
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_hdmi.c
@@ -0,0 +1,394 @@
+/*
+ * Allwinner DRM driver - HDMI
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/component.h>
+#include <linux/clk.h>
+#include <linux/hdmi.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+
+#include "de2_hdmi.h"
+
+static const struct of_device_id de2_hdmi_dt_ids[] = {
+ { .compatible = "allwinner,sun8i-a83t-hdmi",
+ .data = (void *) SOC_A83T },
+ { .compatible = "allwinner,sun8i-h3-hdmi",
+ .data = (void *) SOC_H3 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, de2_hdmi_dt_ids);
+
+#define conn_to_priv(x) \
+ container_of(x, struct de2_hdmi_priv, connector)
+
+#define enc_to_priv(x) \
+ container_of(x, struct de2_hdmi_priv, encoder)
+
+/* --- encoder functions --- */
+
+static void de2_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct de2_hdmi_priv *priv = enc_to_priv(encoder);
+ struct clk *parent_clk;
+ u32 parent_rate;
+ int ret;
+
+ priv->cea_mode = drm_match_cea_mode(mode);
+
+ DRM_DEBUG_DRIVER("cea_mode %d\n", priv->cea_mode);
+
+ /* determine and set the best rate for the parent clock (pll-video) */
+ if ((270000 * 2) % mode->clock == 0)
+ parent_rate = 270000000;
+ else if (297000 % mode->clock == 0)
+ parent_rate = 297000000;
+ else
+ return; /* "640x480" rejected */
+ parent_clk = clk_get_parent(priv->clk);
+
+ ret = clk_set_rate(parent_clk, parent_rate);
+ if (ret) {
+ dev_err(priv->dev, "set parent rate failed %d\n", ret);
+ return;
+ }
+ ret = clk_set_rate(priv->clk, mode->clock * 1000);
+ if (ret)
+ dev_err(priv->dev, "set rate failed %d\n", ret);
+
+ mutex_lock(&priv->mutex);
+ hdmi_io_video_mode(priv, mode);
+ mutex_unlock(&priv->mutex);
+}
+
+static void de2_hdmi_encoder_enable(struct drm_encoder *encoder)
+{
+ struct de2_hdmi_priv *priv = enc_to_priv(encoder);
+
+ mutex_lock(&priv->mutex);
+ hdmi_io_video_on(priv);
+ mutex_unlock(&priv->mutex);
+}
+
+static void de2_hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+ struct de2_hdmi_priv *priv = enc_to_priv(encoder);
+
+ mutex_lock(&priv->mutex);
+ hdmi_io_video_off(priv);
+ mutex_unlock(&priv->mutex);
+}
+
+static const struct drm_encoder_helper_funcs de2_hdmi_encoder_helper_funcs = {
+ .mode_set = de2_hdmi_encoder_mode_set,
+ .enable = de2_hdmi_encoder_enable,
+ .disable = de2_hdmi_encoder_disable,
+};
+
+static const struct drm_encoder_funcs de2_hdmi_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+/* --- connector functions --- */
+
+static int de2_hdmi_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ int cea_mode = drm_match_cea_mode(mode);
+
+ if (hdmi_io_mode_valid(cea_mode) < 0)
+ return MODE_NOMODE;
+
+ return MODE_OK;
+}
+
+static enum drm_connector_status de2_hdmi_connector_detect(
+ struct drm_connector *connector, bool force)
+{
+ struct de2_hdmi_priv *priv = conn_to_priv(connector);
+ int ret;
+
+ mutex_lock(&priv->mutex);
+ ret = hdmi_io_get_hpd(priv);
+ mutex_unlock(&priv->mutex);
+
+ return ret ? connector_status_connected :
+ connector_status_disconnected;
+}
+
+static int read_edid_block(void *data, u8 *buf,
+ unsigned int blk, size_t length)
+{
+ struct de2_hdmi_priv *priv = data;
+ int ret;
+
+ mutex_lock(&priv->mutex);
+ ret = hdmi_io_ddc_read(priv,
+ blk / 2, (blk & 1) ? 128 : 0,
+ length, buf);
+ mutex_unlock(&priv->mutex);
+
+ return ret;
+}
+
+static int de2_hdmi_connector_get_modes(struct drm_connector *connector)
+{
+ struct de2_hdmi_priv *priv = conn_to_priv(connector);
+ struct edid *edid;
+ int n;
+
+ edid = drm_do_get_edid(connector, read_edid_block, priv);
+
+ if (!edid) {
+ dev_warn(priv->dev, "failed to read EDID\n");
+ if (!connector->cmdline_mode.specified)
+ return 0;
+
+ return drm_add_modes_noedid(connector,
+ connector->cmdline_mode.xres,
+ connector->cmdline_mode.yres);
+ }
+
+ drm_mode_connector_update_edid_property(connector, edid);
+ n = drm_add_edid_modes(connector, edid);
+
+ drm_edid_to_eld(connector, edid);
+
+ kfree(edid);
+
+ DRM_DEBUG_DRIVER("%s EDID ok %d modes\n",
+ connector->eld[0] ? "HDMI" : "DVI", n);
+
+ return n;
+}
+
+static const
+struct drm_connector_helper_funcs de2_hdmi_connector_helper_funcs = {
+ .get_modes = de2_hdmi_connector_get_modes,
+ .mode_valid = de2_hdmi_connector_mode_valid,
+};
+
+static const struct drm_connector_funcs de2_hdmi_connector_funcs = {
+ .dpms = drm_atomic_helper_connector_dpms,
+ .reset = drm_atomic_helper_connector_reset,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = de2_hdmi_connector_detect,
+ .destroy = drm_connector_cleanup,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static void de2_hdmi_cleanup(struct de2_hdmi_priv *priv)
+{
+ clk_disable_unprepare(priv->clk_ddc);
+ clk_disable_unprepare(priv->clk);
+ clk_disable_unprepare(priv->gate);
+ reset_control_assert(priv->reset1);
+ reset_control_assert(priv->reset0);
+}
+
+static int de2_hdmi_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm = data;
+ struct de2_hdmi_priv *priv = dev_get_drvdata(dev);
+ struct drm_encoder *encoder = &priv->encoder;
+ struct drm_connector *connector = &priv->connector;
+ int ret;
+
+ encoder->possible_crtcs =
+ drm_of_find_possible_crtcs(drm, dev->of_node);
+
+ /* if no CRTC, delay */
+ if (encoder->possible_crtcs == 0)
+ return -EPROBE_DEFER;
+
+ /* HDMI init */
+ ret = reset_control_deassert(priv->reset0);
+ if (ret)
+ goto err;
+ ret = reset_control_deassert(priv->reset1);
+ if (ret)
+ goto err;
+ ret = clk_prepare_enable(priv->gate);
+ if (ret)
+ goto err;
+ ret = clk_prepare_enable(priv->clk);
+ if (ret)
+ goto err;
+ ret = clk_prepare_enable(priv->clk_ddc);
+ if (ret)
+ goto err;
+
+ mutex_lock(&priv->mutex);
+ hdmi_io_init(priv);
+ mutex_unlock(&priv->mutex);
+
+ /* encoder init */
+ ret = drm_encoder_init(drm, encoder, &de2_hdmi_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+ if (ret)
+ goto err;
+
+ drm_encoder_helper_add(encoder, &de2_hdmi_encoder_helper_funcs);
+
+ /* connector init */
+ ret = drm_connector_init(drm, connector,
+ &de2_hdmi_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA);
+ if (ret)
+ goto err_connector;
+
+ connector->interlace_allowed = 1;
+ connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
+ drm_connector_helper_add(connector,
+ &de2_hdmi_connector_helper_funcs);
+
+ drm_mode_connector_attach_encoder(connector, encoder);
+
+ return 0;
+
+err_connector:
+ drm_encoder_cleanup(encoder);
+err:
+ dev_err(dev, "err %d\n", ret);
+ return ret;
+}
+
+static void de2_hdmi_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct de2_hdmi_priv *priv = dev_get_drvdata(dev);
+
+ if (priv->connector.dev)
+ drm_connector_cleanup(&priv->connector);
+ drm_encoder_cleanup(&priv->encoder);
+ de2_hdmi_cleanup(priv);
+}
+
+static const struct component_ops de2_hdmi_ops = {
+ .bind = de2_hdmi_bind,
+ .unbind = de2_hdmi_unbind,
+};
+
+static int de2_hdmi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct de2_hdmi_priv *priv;
+ struct resource *res;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, priv);
+ priv->dev = dev;
+
+ mutex_init(&priv->mutex);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "failed to get memory resource\n");
+ return -ENXIO;
+ }
+ priv->mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->mmio)) {
+ ret = PTR_ERR(priv->mmio);
+ dev_err(dev, "failed to map registers err %d\n", ret);
+ return ret;
+ }
+
+ priv->gate = devm_clk_get(dev, "bus");
+ if (IS_ERR(priv->gate)) {
+ ret = PTR_ERR(priv->gate);
+ dev_err(dev, "gate clock err %d\n", ret);
+ return ret;
+ }
+
+ priv->clk = devm_clk_get(dev, "clock");
+ if (IS_ERR(priv->clk)) {
+ ret = PTR_ERR(priv->clk);
+ dev_err(dev, "hdmi clock err %d\n", ret);
+ return ret;
+ }
+
+ priv->clk_ddc = devm_clk_get(dev, "ddc-clock");
+ if (IS_ERR(priv->clk_ddc)) {
+ ret = PTR_ERR(priv->clk_ddc);
+ dev_err(dev, "hdmi-ddc clock err %d\n", ret);
+ return ret;
+ }
+
+ priv->reset0 = devm_reset_control_get(dev, "hdmi0");
+ if (IS_ERR(priv->reset0)) {
+ ret = PTR_ERR(priv->reset0);
+ dev_err(dev, "reset controller err %d\n", ret);
+ return ret;
+ }
+
+ priv->reset1 = devm_reset_control_get(dev, "hdmi1");
+ if (IS_ERR(priv->reset1)) {
+ ret = PTR_ERR(priv->reset1);
+ dev_err(dev, "reset controller err %d\n", ret);
+ return ret;
+ }
+
+ priv->soc_type = (int) of_match_device(de2_hdmi_dt_ids,
+ &pdev->dev)->data;
+
+ return component_add(dev, &de2_hdmi_ops);
+}
+
+static int de2_hdmi_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &de2_hdmi_ops);
+
+ return 0;
+}
+
+static struct platform_driver de2_hdmi_driver = {
+ .probe = de2_hdmi_probe,
+ .remove = de2_hdmi_remove,
+ .driver = {
+ .name = "sun8i-de2-hdmi",
+ .of_match_table = of_match_ptr(de2_hdmi_dt_ids),
+ },
+};
+
+/* create the video HDMI driver */
+static int __init de2_hdmi_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&de2_hdmi_driver);
+
+ return ret;
+}
+
+static void __exit de2_hdmi_fini(void)
+{
+ platform_driver_unregister(&de2_hdmi_driver);
+}
+
+module_init(de2_hdmi_init);
+module_exit(de2_hdmi_fini);
+
+MODULE_AUTHOR("Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>");
+MODULE_DESCRIPTION("Allwinner DE2 HDMI encoder/connector");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/sun8i/de2_hdmi.h b/drivers/gpu/drm/sun8i/de2_hdmi.h
new file mode 100644
index 0000000..1f0b1d9
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_hdmi.h
@@ -0,0 +1,51 @@
+#ifndef __DE2_HDMI_H__
+#define __DE2_HDMI_H__
+/*
+ * Copyright (C) 2016 Jean-François Moine
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <drm/drmP.h>
+
+/* SoC types */
+#define SOC_A83T 0
+#define SOC_H3 1
+
+struct de2_hdmi_priv {
+ struct device *dev;
+ void __iomem *mmio;
+
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+
+ struct clk *clk;
+ struct clk *clk_ddc;
+ struct clk *gate;
+ struct reset_control *reset0;
+ struct reset_control *reset1;
+
+ struct mutex mutex;
+ u8 soc_type;
+ u8 cea_mode;
+};
+
+/* in de2_hdmi_io.c */
+void hdmi_io_init(struct de2_hdmi_priv *priv);
+void hdmi_io_video_on(struct de2_hdmi_priv *priv);
+void hdmi_io_video_off(struct de2_hdmi_priv *priv);
+int hdmi_io_video_mode(struct de2_hdmi_priv *priv,
+ struct drm_display_mode *mode);
+int hdmi_io_ddc_read(struct de2_hdmi_priv *priv,
+ char pointer, char offset,
+ int nbyte, char *pbuf);
+int hdmi_io_get_hpd(struct de2_hdmi_priv *priv);
+int hdmi_io_mode_valid(int cea_mode);
+
+#endif /* __DE2_HDMI_H__ */
diff --git a/drivers/gpu/drm/sun8i/de2_hdmi_io.c b/drivers/gpu/drm/sun8i/de2_hdmi_io.c
new file mode 100644
index 0000000..8c690bf
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_hdmi_io.c
@@ -0,0 +1,839 @@
+/*
+ * Allwinner A83T and H3 HDMI lowlevel functions
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
+ * Adapted from the sun8iw6 and sun8iw7 disp2 drivers
+ * Copyright (c) 2016 Allwinnertech Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+/*
+ * The HDMI controller in the A83T and H3 seems to be a
+ * Synopsys DesignWare HDMI controller.
+ * The PHYs are unknown.
+ * Documentation:
+ * https://linux-sunxi.org/DWC_HDMI_Controller
+ * https://www.synopsys.com/dw/doc.php/ds/c/dwc_hdmi_tx_csds.pdf
+ */
+
+#include <linux/hdmi.h>
+
+#include "de2_hdmi.h"
+
+/* guessed PHY registers */
+#define HDMI_PHY_LOCK_READ_REG 0x10010
+#define HDMI_PHY_CTRL_REG 0x10020
+#define HDMI_PHY_24_REG 0x10024
+#define HDMI_PHY_28_REG 0x10028
+#define HDMI_PHY_PLL_REG 0x1002c
+#define HDMI_PHY_CLK_REG 0x10030
+#define HDMI_PHY_34_REG 0x10034
+#define HDMI_PHY_STATUS_REG 0x10038
+
+/* DW registers (obfuscated addresses) */
+
+/* Interrupt Registers */
+#define R_0100_HDMI_IH_FC_STAT0 0x0010
+#define R_0101_HDMI_IH_FC_STAT1 0x0011
+#define R_0102_HDMI_IH_FC_STAT2 0x8010
+#define R_0103_HDMI_IH_AS_STAT0 0x8011
+#define R_0104_HDMI_IH_PHY_STAT0 0x0012
+#define R_0105_HDMI_IH_I2CM_STAT0 0x0013
+#define R_0106_HDMI_IH_CEC_STAT0 0x8012
+#define R_0107_HDMI_IH_VP_STAT0 0x8013
+#define R_0108_HDMI_IH_I2CMPHY_STAT0 0x4010
+#define R_01ff_HDMI_IH_MUTE 0xf01f
+
+/* Video Sample Registers */
+#define R_0200_HDMI_TX_INVID0 0x0800
+#define R_0201_HDMI_TX_INSTUFFING 0x0801
+#define R_0202_HDMI_TX_GYDATA0 0x8800
+#define R_0203_HDMI_TX_GYDATA1 0x8801
+#define R_0204_HDMI_TX_RCRDATA0 0x0802
+#define R_0205_HDMI_TX_RCRDATA1 0x0803
+#define R_0206_HDMI_TX_BCBDATA0 0x8802
+#define R_0207_HDMI_TX_BCBDATA1 0x8803
+
+/* Video Packetizer Registers */
+#define R_0801_HDMI_VP_PR_CD 0x0401
+#define R_0802_HDMI_VP_STUFF 0x8400
+#define R_0803_HDMI_VP_REMAP 0x8401
+#define R_0804_HDMI_VP_CONF 0x0402
+#define R_0807_HDMI_VP_MASK 0x8403
+
+/* Frame Composer Registers */
+#define R_1000_HDMI_FC_INVIDCONF 0x0040
+#define HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH 0x10
+#define R_1001_HDMI_FC_INHACTV0 0x0041
+#define R_1002_HDMI_FC_INHACTV1 0x8040
+#define R_1003_HDMI_FC_INHBLANK0 0x8041
+#define R_1004_HDMI_FC_INHBLANK1 0x0042
+#define R_1005_HDMI_FC_INVACTV0 0x0043
+#define R_1006_HDMI_FC_INVACTV1 0x8042
+#define R_1007_HDMI_FC_INVBLANK 0x8043
+#define R_1008_HDMI_FC_HSYNCINDELAY0 0x4040
+#define R_1009_HDMI_FC_HSYNCINDELAY1 0x4041
+#define R_100a_HDMI_FC_HSYNCINWIDTH0 0xc040
+#define R_100b_HDMI_FC_HSYNCINWIDTH1 0xc041
+#define R_100c_HDMI_FC_VSYNCINDELAY 0x4042
+#define R_100d_HDMI_FC_VSYNCINWIDTH 0x4043
+#define R_1011_HDMI_FC_CTRLDUR 0x0045
+#define R_1012_HDMI_FC_EXCTRLDUR 0x8044
+#define R_1013_HDMI_FC_EXCTRLSPAC 0x8045
+#define R_1014_HDMI_FC_CH0PREAM 0x0046
+#define R_1015_HDMI_FC_CH1PREAM 0x0047
+#define R_1016_HDMI_FC_CH2PREAM 0x8046
+#define R_1018_HDMI_FC_GCP 0x4044
+#define R_1019_HDMI_FC_AVICONF0 0x4045
+#define HDMI_FC_AVICONF0_SCAN_INFO_UNDERSCAN 0x20
+#define R_101a_HDMI_FC_AVICONF1 0xc044
+#define R_101b_HDMI_FC_AVICONF2 0xc045
+#define R_101c_HDMI_FC_AVIVID 0x4046
+#define R_1025_HDMI_FC_AUDICONF0 0x2043
+#define R_1026_HDMI_FC_AUDICONF1 0xa042
+#define R_1027_HDMI_FC_AUDICONF2 0xa043
+#define R_1028_HDMI_FC_AUDICONF3 0x6040
+#define R_1029_HDMI_FC_VSDIEEEID0 0x6041
+#define R_1030_HDMI_FC_VSDIEEEID1 0x2044
+#define R_1031_HDMI_FC_VSDIEEEID2 0x2045
+#define R_1032_HDMI_FC_VSDPAYLOAD0 0xa044
+#define R_1033_HDMI_FC_VSDPAYLOAD1 0xa045
+#define R_1034_HDMI_FC_VSDPAYLOAD2 0x2046
+#define R_1063_HDMI_FC_AUDSCONF 0xa049
+#define R_1065_HDMI_FC_AUDSV 0x204b
+#define R_1066_HDMI_FC_AUDSU 0xa04a
+#define R_1067_HDMI_FC_AUDSCHNLS0 0xa04b
+#define HDMI_FC_AUDSCHNLS0_CGMSA 0x30
+#define R_1068_HDMI_FC_AUDSCHNLS1 0x6048
+#define R_1069_HDMI_FC_AUDSCHNLS2 0x6049
+#define R_106a_HDMI_FC_AUDSCHNLS3 0xe048
+#define HDMI_FC_AUDSCHNLS3_OIEC_CH0(v) (v)
+#define HDMI_FC_AUDSCHNLS3_OIEC_CH1(v) (v << 4)
+#define R_106b_HDMI_FC_AUDSCHNLS4 0xe049
+#define HDMI_FC_AUDSCHNLS4_OIEC_CH2(v) (v)
+#define HDMI_FC_AUDSCHNLS4_OIEC_CH3(v) (v << 4)
+#define R_106c_HDMI_FC_AUDSCHNLS5 0x604a
+#define HDMI_FC_AUDSCHNLS5_OIEC_CH0(v) (v)
+#define HDMI_FC_AUDSCHNLS5_OIEC_CH1(v) (v << 4)
+#define R_106d_HDMI_FC_AUDSCHNLS6 0x604b
+#define HDMI_FC_AUDSCHNLS6_OIEC_CH2(v) (v)
+#define HDMI_FC_AUDSCHNLS6_OIEC_CH3(v) (v << 4)
+#define R_106e_HDMI_FC_AUDSCHNLS7 0xe04a
+#define R_106f_HDMI_FC_AUDSCHNLS8 0xe04b
+#define HDMI_FC_AUDSCHNLS8_WORDLENGTH(v) (v)
+#define R_10b3_HDMI_FC_DATAUTO0 0xb045
+#define R_10b4_HDMI_FC_DATAUTO1 0x3046
+#define R_10b5_HDMI_FC_DATAUTO2 0x3047
+#define R_10d2_HDMI_FC_MASK0 0x904c
+#define R_10d6_HDMI_FC_MASK1 0x904e
+#define R_10da_HDMI_FC_MASK2 0xd04c
+#define R_10e0_HDMI_FC_PRCONF 0x3048
+#define R_1103_HDMI_FC_GMD_CONF 0x8051
+#define R_1104_HDMI_FC_GMD_HB 0x0052
+#define R_1200_HDMI_FC_DBGFORCE 0x0840
+#define HDMI_FC_DBGFORCE_FORCEAUDIO BIT(4)
+#define HDMI_FC_DBGFORCE_FORCEVIDEO BIT(0)
+#define R_1219_HDMI_FC_DBGTMDS0 0x4845
+
+/* HDMI Source PHY Registers */
+#define R_3000_HDMI_PHY_CONF0 0x0240
+#define HDMI_PHY_CONF0_PDZ BIT(7)
+#define HDMI_PHY_CONF0_ENTMDS BIT(6)
+#define HDMI_PHY_CONF0_SPARECTRL BIT(5)
+#define HDMI_PHY_CONF0_GEN2_PDDQ BIT(4)
+#define HDMI_PHY_CONF0_GEN2_TXPWRON BIT(3)
+#define HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE BIT(2)
+#define HDMI_PHY_CONF0_SELDATAENPOL BIT(1)
+#define HDMI_PHY_CONF0_SELDIPIF BIT(0)
+#define R_3001_HDMI_PHY_TST0 0x0241
+#define HDMI_PHY_TST0_TSTCLR BIT(5)
+#define R_3005_HDMI_PHY_INT0 0x0243
+#define R_3006_HDMI_PHY_MASK0 0x8242
+
+/* HDMI Master PHY Registers */
+#define R_3020_HDMI_PHY_I2CM_SLAVE_ADDR 0x2240
+#define HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2 0x69
+#define R_3021_HDMI_PHY_I2CM_ADDRESS_ADDR 0x2241
+#define R_3022_HDMI_PHY_I2CM_DATAO_1_ADDR 0xa240
+#define R_3023_HDMI_PHY_I2CM_DATAO_0_ADDR 0xa241
+#define R_3026_HDMI_PHY_I2CM_OPERATION_ADDR 0xa242
+#define HDMI_PHY_I2CM_OPERATION_ADDR_WRITE 0x10
+#define R_3027_HDMI_PHY_I2CM_INT_ADDR 0xa243
+#define R_3028_HDMI_PHY_I2CM_CTLINT_ADDR 0x6240
+
+/* Audio Sampler Registers */
+#define R_3100_HDMI_AUD_CONF0 0x0250
+#define HDMI_AUD_CONF0_SW_RESET 0x80
+#define HDMI_AUD_CONF0_I2S_ALL_ENABLE 0x2f
+#define R_3101_HDMI_AUD_CONF1 0x0251
+#define R_3102_HDMI_AUD_INT 0x8250
+#define R_3103_HDMI_AUD_CONF2 0x8251
+#define R_3200_HDMI_AUD_N1 0x0a40
+#define R_3201_HDMI_AUD_N2 0x0a41
+#define R_3202_HDMI_AUD_N3 0x8a40
+#define R_3205_HDMI_AUD_CTS3 0x0a43
+#define R_3206_HDMI_AUD_INPUTCLKFS 0x8a42
+#define HDMI_AUD_INPUTCLKFS_64FS 0x04
+#define R_3302_HDMI_AUD_SPDIFINT 0x8a50
+
+/* Generic Parallel Audio Interface Registers */
+#define R_3506_HDMI_GP_POL 0x8272
+
+/* Main Controller Registers */
+#define R_4001_HDMI_MC_CLKDIS 0x0081
+#define HDMI_MC_CLKDIS_HDCPCLK_DISABLE BIT(6)
+#define HDMI_MC_CLKDIS_AUDCLK_DISABLE BIT(3)
+#define HDMI_MC_CLKDIS_TMDSCLK_DISABLE BIT(1)
+#define R_4002_HDMI_MC_SWRSTZ 0x8080
+#define R_4004_HDMI_MC_FLOWCTRL 0x0082
+#define R_4005_HDMI_MC_PHYRSTZ 0x0083
+#define HDMI_MC_PHYRSTZ_DEASSERT BIT(0)
+
+/* HDCP Encryption Engine Registers */
+#define R_5000_HDMI_A_HDCPCFG0 0x00c0
+#define R_5001_HDMI_A_HDCPCFG1 0x00c1
+#define HDMI_A_HDCPCFG1_PH2UPSHFTENC BIT(2)
+#define HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE BIT(1)
+#define HDMI_A_HDCPCFG1_SWRESET BIT(0)
+#define R_5008_HDMI_A_APIINTMSK 0x40c0
+#define R_5009_HDMI_A_VIDPOLCFG 0x40c1
+#define HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH BIT(4)
+
+/* CEC Engine Registers */
+#define R_7d02_HDMI_CEC_MASK 0x86f0
+
+/* I2C Master Registers (E-DDC) */
+#define R_7e00_HDMI_I2CM_SLAVE 0x0ee0
+#define R_7e01_HDMI_I2CM_ADDRESS 0x0ee1
+#define R_7e03_HDMI_I2CM_DATAI 0x8ee1
+#define R_7e04_HDMI_I2CM_OPERATION 0x0ee2
+#define HDMI_I2CM_OPERATION_DDC_READ 0x02
+#define R_7e05_HDMI_I2CM_INT 0x0ee3
+#define R_7e06_HDMI_I2CM_CTLINT 0x8ee2
+#define R_7e07_HDMI_I2CM_DIV 0x8ee3
+#define R_7e08_HDMI_I2CM_SEGADDR 0x4ee0
+#define R_7e09_HDMI_I2CM_SOFTRSTZ 0x4ee1
+#define R_7e0a_HDMI_I2CM_SEGPTR 0xcee0
+#define R_7e0b_HDMI_I2CM_SS_SCL_HCNT_1_ADDR 0xcee1
+#define R_7e0c_HDMI_I2CM_SS_SCL_HCNT_0_ADDR 0x4ee2
+#define R_7e0d_HDMI_I2CM_SS_SCL_LCNT_1_ADDR 0x4ee3
+#define R_7e0e_HDMI_I2CM_SS_SCL_LCNT_0_ADDR 0xcee2
+#define R_7e0f_HDMI_I2CM_FS_SCL_HCNT_1_ADDR 0xcee3
+#define R_7e10_HDMI_I2CM_FS_SCL_HCNT_0_ADDR 0x0ee4
+#define R_7e11_HDMI_I2CM_FS_SCL_LCNT_1_ADDR 0x0ee5
+
+#define VIC_720x480_60 2
+#define VIC_1280x720_60 4
+#define VIC_1920x1080i_60 5
+#define VIC_720x480i_60 6
+#define VIC_1920x1080_60 16
+#define VIC_720x576_50 17
+#define VIC_1280x720_50 19
+#define VIC_1920x1080i_50 20
+#define VIC_720x576i_50 21
+#define VIC_1920x1080_50 31
+#define VIC_1920x1080_24 32
+#define VIC_1920x1080_25 33
+#define VIC_1920x1080_30 34
+
+static inline u8 hdmi_readb(struct de2_hdmi_priv *priv, u32 addr)
+{
+ return readb_relaxed(priv->mmio + addr);
+}
+
+static inline u32 hdmi_readl(struct de2_hdmi_priv *priv, u32 addr)
+{
+ return readl_relaxed(priv->mmio + addr);
+}
+
+static inline void hdmi_writeb(struct de2_hdmi_priv *priv, u32 addr, u8 data)
+{
+ writeb_relaxed(data, priv->mmio + addr);
+}
+
+static inline void hdmi_writel(struct de2_hdmi_priv *priv, u32 addr, u32 data)
+{
+ writel_relaxed(data, priv->mmio + addr);
+}
+
+static inline void hdmi_orb(struct de2_hdmi_priv *priv, u32 addr, u8 data)
+{
+ writeb_relaxed(readb_relaxed(priv->mmio + addr) | data,
+ priv->mmio + addr);
+}
+
+static inline void hdmi_orl(struct de2_hdmi_priv *priv, u32 addr, u32 data)
+{
+ writel_relaxed(readl_relaxed(priv->mmio + addr) | data,
+ priv->mmio + addr);
+}
+
+static inline void hdmi_andl(struct de2_hdmi_priv *priv, u32 addr, u32 data)
+{
+ writel_relaxed(readl_relaxed(priv->mmio + addr) & data,
+ priv->mmio + addr);
+}
+
+/* read lock/unlock functions */
+static inline void hdmi_lock_read(struct de2_hdmi_priv *priv)
+{
+ hdmi_writel(priv, HDMI_PHY_LOCK_READ_REG, 0x54524545);
+}
+static inline void hdmi_unlock_read(struct de2_hdmi_priv *priv)
+{
+ hdmi_writel(priv, HDMI_PHY_LOCK_READ_REG, 0x57415452);
+}
+
+static void hdmi_inner_init(struct de2_hdmi_priv *priv)
+{
+ u8 clkdis = priv->soc_type == SOC_H3 ?
+ ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE : 0xff;
+
+ hdmi_lock_read(priv);
+
+ /* software reset */
+ hdmi_writeb(priv, R_4002_HDMI_MC_SWRSTZ, 0x00);
+ udelay(2);
+
+ /* mask all interrupts */
+ hdmi_writeb(priv, R_01ff_HDMI_IH_MUTE, 0x00);
+ hdmi_writeb(priv, R_0807_HDMI_VP_MASK, 0xff);
+ hdmi_writeb(priv, R_10d2_HDMI_FC_MASK0, 0xff);
+ hdmi_writeb(priv, R_10d6_HDMI_FC_MASK1, 0xff);
+ hdmi_writeb(priv, R_10da_HDMI_FC_MASK2, 0xff);
+ hdmi_writeb(priv, R_3102_HDMI_AUD_INT, 0xff);
+ hdmi_writeb(priv, R_3302_HDMI_AUD_SPDIFINT, 0xff);
+ hdmi_writeb(priv, R_3506_HDMI_GP_POL, 0xff);
+ hdmi_writeb(priv, R_5008_HDMI_A_APIINTMSK, 0xff);
+ hdmi_writeb(priv, R_7d02_HDMI_CEC_MASK, 0xff);
+ hdmi_writeb(priv, R_7e05_HDMI_I2CM_INT, 0xff);
+ hdmi_writeb(priv, R_7e06_HDMI_I2CM_CTLINT, 0xff);
+
+ hdmi_writeb(priv, R_1063_HDMI_FC_AUDSCONF, 0xf0);
+ hdmi_writeb(priv, R_10b3_HDMI_FC_DATAUTO0, 0x1e);
+ hdmi_writeb(priv, R_5001_HDMI_A_HDCPCFG1, 0x00);
+ hdmi_writeb(priv, R_5001_HDMI_A_HDCPCFG1,
+ HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE |
+ HDMI_A_HDCPCFG1_SWRESET);
+ hdmi_writeb(priv, R_5000_HDMI_A_HDCPCFG0, 0x00);
+ hdmi_writeb(priv, R_5009_HDMI_A_VIDPOLCFG,
+ HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH);
+ hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, clkdis);
+ hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, 0x00);
+ hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, clkdis);
+ hdmi_writeb(priv, R_0100_HDMI_IH_FC_STAT0, 0xff);
+ hdmi_writeb(priv, R_0101_HDMI_IH_FC_STAT1, 0xff);
+ hdmi_writeb(priv, R_0102_HDMI_IH_FC_STAT2, 0xff);
+ hdmi_writeb(priv, R_0103_HDMI_IH_AS_STAT0, 0xff);
+ hdmi_writeb(priv, R_0105_HDMI_IH_I2CM_STAT0, 0xff);
+ hdmi_writeb(priv, R_0106_HDMI_IH_CEC_STAT0, 0xff);
+ hdmi_writeb(priv, R_0107_HDMI_IH_VP_STAT0, 0xff);
+}
+
+static void hdmi_phy_init_a83t(struct de2_hdmi_priv *priv)
+{
+ hdmi_inner_init(priv);
+
+ hdmi_writeb(priv, 0x10000, 0x01);
+ hdmi_writeb(priv, 0x10001, 0x00);
+ hdmi_writeb(priv, 0x10002, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2);
+ hdmi_writeb(priv, 0x10003, 0x00);
+ hdmi_writeb(priv, 0x10007, 0xa0);
+ hdmi_writeb(priv, R_4005_HDMI_MC_PHYRSTZ, HDMI_MC_PHYRSTZ_DEASSERT);
+ udelay(1);
+ hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE |
+ HDMI_PHY_CONF0_SELDATAENPOL);
+ hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_PDDQ |
+ HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE |
+ HDMI_PHY_CONF0_SELDATAENPOL);
+ hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_PDDQ |
+ HDMI_PHY_CONF0_SELDATAENPOL);
+ hdmi_writeb(priv, R_3006_HDMI_PHY_MASK0, 0xf0);
+ hdmi_writeb(priv, R_3027_HDMI_PHY_I2CM_INT_ADDR, 0xff);
+ hdmi_writeb(priv, R_3028_HDMI_PHY_I2CM_CTLINT_ADDR, 0xff);
+ hdmi_writeb(priv, R_0104_HDMI_IH_PHY_STAT0, 0xff);
+ hdmi_writeb(priv, R_0108_HDMI_IH_I2CMPHY_STAT0, 0xff);
+ hdmi_writeb(priv, R_4005_HDMI_MC_PHYRSTZ, 0x00);
+ hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_PDDQ |
+ HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE |
+ HDMI_PHY_CONF0_SELDATAENPOL);
+ hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE |
+ HDMI_PHY_CONF0_SELDATAENPOL);
+ hdmi_writeb(priv, R_3001_HDMI_PHY_TST0, HDMI_PHY_TST0_TSTCLR);
+ hdmi_writeb(priv, R_3020_HDMI_PHY_I2CM_SLAVE_ADDR,
+ HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2);
+ hdmi_writeb(priv, R_3001_HDMI_PHY_TST0, 0x00);
+}
+
+static void hdmi_phy_init_h3(struct de2_hdmi_priv *priv)
+{
+ int to_cnt;
+ u32 tmp;
+
+ hdmi_writel(priv, HDMI_PHY_CTRL_REG, 0);
+ hdmi_writel(priv, HDMI_PHY_CTRL_REG, 1 << 0);
+ udelay(5);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 1 << 16);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 1 << 1);
+ udelay(10);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 1 << 2);
+ udelay(5);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 1 << 3);
+ usleep_range(40, 50);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 1 << 19);
+ usleep_range(100, 120);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 1 << 18);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 7 << 4);
+
+ to_cnt = 10;
+ while (1) {
+ if (hdmi_readl(priv, HDMI_PHY_STATUS_REG) & 0x80)
+ break;
+ usleep_range(200, 250);
+ if (--to_cnt == 0) {
+ pr_warn("hdmi phy init timeout\n");
+ break;
+ }
+ }
+
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 0xf << 8);
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 1 << 7);
+
+ hdmi_writel(priv, HDMI_PHY_PLL_REG, 0x39dc5040);
+ hdmi_writel(priv, HDMI_PHY_CLK_REG, 0x80084343);
+ msleep(20);
+ hdmi_writel(priv, HDMI_PHY_34_REG, 0x00000001);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0x02000000);
+ msleep(100);
+ tmp = hdmi_readl(priv, HDMI_PHY_STATUS_REG);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0xc0000000);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, (tmp >> 11) & 0x3f);
+ hdmi_writel(priv, HDMI_PHY_CTRL_REG, 0x01ff0f7f);
+ hdmi_writel(priv, HDMI_PHY_24_REG, 0x80639000);
+ hdmi_writel(priv, HDMI_PHY_28_REG, 0x0f81c405);
+
+ hdmi_inner_init(priv);
+}
+
+static void hdmi_i2cm_write(struct de2_hdmi_priv *priv,
+ int addr, u8 valh, u8 vall)
+{
+ hdmi_writeb(priv, R_3021_HDMI_PHY_I2CM_ADDRESS_ADDR, addr);
+ hdmi_writeb(priv, R_3022_HDMI_PHY_I2CM_DATAO_1_ADDR, valh);
+ hdmi_writeb(priv, R_3023_HDMI_PHY_I2CM_DATAO_0_ADDR, vall);
+ hdmi_writeb(priv, R_3026_HDMI_PHY_I2CM_OPERATION_ADDR,
+ HDMI_PHY_I2CM_OPERATION_ADDR_WRITE);
+ usleep_range(2000, 2500);
+}
+
+static int get_divider(int rate)
+{
+ if (rate <= 27000)
+ return 11;
+ if (rate <= 74250)
+ return 4;
+ if (rate <= 148500)
+ return 2;
+ return 1;
+}
+
+static void hdmi_phy_set_a83t(struct de2_hdmi_priv *priv,
+ struct drm_display_mode *mode)
+{
+ switch (get_divider(mode->clock)) {
+ case 1:
+ hdmi_i2cm_write(priv, 0x06, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x15, 0x00, 0x0f);
+ hdmi_i2cm_write(priv, 0x10, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x19, 0x00, 0x02);
+ hdmi_i2cm_write(priv, 0x0e, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x09, 0x80, 0x2b);
+ break;
+ case 2: /* 1080P @ 60 & 50 */
+ hdmi_i2cm_write(priv, 0x06, 0x04, 0xa0);
+ hdmi_i2cm_write(priv, 0x15, 0x00, 0x0a);
+ hdmi_i2cm_write(priv, 0x10, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x19, 0x00, 0x02);
+ hdmi_i2cm_write(priv, 0x0e, 0x00, 0x21);
+ hdmi_i2cm_write(priv, 0x09, 0x80, 0x29);
+ break;
+ case 4: /* 720P @ 50 & 60, 1080I, 1080P */
+ hdmi_i2cm_write(priv, 0x06, 0x05, 0x40);
+ hdmi_i2cm_write(priv, 0x15, 0x00, 0x05);
+ hdmi_i2cm_write(priv, 0x10, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x19, 0x00, 0x07);
+ hdmi_i2cm_write(priv, 0x0e, 0x02, 0xb5);
+ hdmi_i2cm_write(priv, 0x09, 0x80, 0x09);
+ break;
+/* case 11: * 480P/576P */
+ default:
+ hdmi_i2cm_write(priv, 0x06, 0x01,
+ mode->flags & DRM_MODE_FLAG_DBLCLK ? 0xe3 : 0xe0);
+ hdmi_i2cm_write(priv, 0x15, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x10, 0x08, 0xda);
+ hdmi_i2cm_write(priv, 0x19, 0x00, 0x07);
+ hdmi_i2cm_write(priv, 0x0e, 0x03, 0x18);
+ hdmi_i2cm_write(priv, 0x09, 0x80, 0x09);
+ break;
+ }
+ hdmi_i2cm_write(priv, 0x1e, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x13, 0x00, 0x00);
+ hdmi_i2cm_write(priv, 0x17, 0x00, 0x00);
+ hdmi_writeb(priv, R_3000_HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_TXPWRON |
+ HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE |
+ HDMI_PHY_CONF0_SELDATAENPOL);
+}
+
+static void hdmi_phy_set_h3(struct de2_hdmi_priv *priv,
+ struct drm_display_mode *mode)
+{
+ u32 tmp;
+
+ hdmi_andl(priv, HDMI_PHY_CTRL_REG, ~0xf000);
+
+ switch (get_divider(mode->clock)) {
+ case 1:
+ hdmi_writel(priv, HDMI_PHY_PLL_REG, 0x31dc5fc0);
+ hdmi_writel(priv, HDMI_PHY_CLK_REG, 0x800863c0);
+ msleep(20);
+ hdmi_writel(priv, HDMI_PHY_34_REG, 0x00000001);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0x02000000);
+ msleep(200);
+ tmp = (hdmi_readl(priv, HDMI_PHY_STATUS_REG) >> 11) & 0x3f;
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0xc0000000);
+ if (tmp < 0x3d)
+ tmp += 2;
+ else
+ tmp = 0x3f;
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, tmp);
+ msleep(100);
+ hdmi_writel(priv, HDMI_PHY_CTRL_REG, 0x01ffff7f);
+ hdmi_writel(priv, HDMI_PHY_24_REG, 0x8063b000);
+ hdmi_writel(priv, HDMI_PHY_28_REG, 0x0f8246b5);
+ break;
+ case 2: /* 1080P @ 60 & 50 */
+ hdmi_writel(priv, HDMI_PHY_PLL_REG, 0x39dc5040);
+ hdmi_writel(priv, HDMI_PHY_CLK_REG, 0x80084381);
+ msleep(20);
+ hdmi_writel(priv, HDMI_PHY_34_REG, 0x00000001);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0x02000000);
+ msleep(100);
+ tmp = (hdmi_readl(priv, HDMI_PHY_STATUS_REG) >> 11) & 0x3f;
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0xc0000000);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, tmp);
+ hdmi_writel(priv, HDMI_PHY_CTRL_REG, 0x01ffff7f);
+ hdmi_writel(priv, HDMI_PHY_24_REG, 0x8063a800);
+ hdmi_writel(priv, HDMI_PHY_28_REG, 0x0f81c485);
+ break;
+ case 4: /* 720P @ 50 & 60, 1080I, 1080P */
+ hdmi_writel(priv, HDMI_PHY_PLL_REG, 0x39dc5040);
+ hdmi_writel(priv, HDMI_PHY_CLK_REG, 0x80084343);
+ msleep(20);
+ hdmi_writel(priv, HDMI_PHY_34_REG, 0x00000001);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0x02000000);
+ msleep(100);
+ tmp = (hdmi_readl(priv, HDMI_PHY_STATUS_REG) >> 11) & 0x3f;
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0xc0000000);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, tmp);
+ hdmi_writel(priv, HDMI_PHY_CTRL_REG, 0x01ffff7f);
+ hdmi_writel(priv, HDMI_PHY_24_REG, 0x8063b000);
+ hdmi_writel(priv, HDMI_PHY_28_REG, 0x0f81c405);
+ break;
+ default:
+/* case 11: * 480P/576P */
+ hdmi_writel(priv, HDMI_PHY_PLL_REG, 0x39dc5040);
+ hdmi_writel(priv, HDMI_PHY_CLK_REG, 0x8008430a);
+ msleep(20);
+ hdmi_writel(priv, HDMI_PHY_34_REG, 0x00000001);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0x02000000);
+ msleep(100);
+ tmp = (hdmi_readl(priv, HDMI_PHY_STATUS_REG) >> 11) & 0x3f;
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, 0xc0000000);
+ hdmi_orl(priv, HDMI_PHY_PLL_REG, tmp);
+ hdmi_writel(priv, HDMI_PHY_CTRL_REG, 0x01ffff7f);
+ hdmi_writel(priv, HDMI_PHY_24_REG, 0x8063b000);
+ hdmi_writel(priv, HDMI_PHY_28_REG, 0x0f81c405);
+ break;
+ }
+}
+
+/* HDMI functions */
+
+void hdmi_io_init(struct de2_hdmi_priv *priv)
+{
+ if (priv->soc_type == SOC_H3)
+ hdmi_phy_init_h3(priv);
+ else
+ hdmi_phy_init_a83t(priv);
+
+ /* hpd reset */
+ hdmi_writeb(priv, R_5001_HDMI_A_HDCPCFG1,
+ HDMI_A_HDCPCFG1_PH2UPSHFTENC);
+ hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS,
+ HDMI_MC_CLKDIS_HDCPCLK_DISABLE);
+}
+
+void hdmi_io_video_on(struct de2_hdmi_priv *priv)
+{
+ if (priv->soc_type == SOC_H3)
+ hdmi_orl(priv, HDMI_PHY_CTRL_REG, 0x0f << 12);
+}
+
+void hdmi_io_video_off(struct de2_hdmi_priv *priv)
+{
+ if (priv->soc_type == SOC_H3)
+ hdmi_andl(priv, HDMI_PHY_CTRL_REG, ~(0x0f << 12));
+}
+
+/* video init */
+int hdmi_io_video_mode(struct de2_hdmi_priv *priv,
+ struct drm_display_mode *mode)
+{
+ int avi_d2; /* AVI InfoFrame Data Byte 2 */
+ int h_blank, v_blank, h_sync_w, h_front_p;
+ int invidconf;
+
+ /* colorimetry and aspect ratio */
+ switch (priv->cea_mode) {
+ case VIC_720x480_60:
+ case VIC_720x480i_60:
+ case VIC_720x576_50:
+ case VIC_720x576i_50:
+ avi_d2 = (HDMI_COLORIMETRY_ITU_601 << 6) |
+ (HDMI_PICTURE_ASPECT_4_3 << 4) | 0x08;
+ break;
+ default:
+ avi_d2 = (HDMI_COLORIMETRY_ITU_709 << 6) |
+ (HDMI_PICTURE_ASPECT_16_9 << 4) | 0x08;
+ break;
+ }
+
+ h_blank = mode->htotal - mode->hdisplay;
+ v_blank = mode->vtotal - mode->vdisplay;
+ h_sync_w = mode->hsync_end - mode->hsync_start;
+ h_front_p = mode->hsync_start - mode->hdisplay;
+
+ invidconf = 0;
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+ invidconf |= 0x01;
+ if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+ invidconf |= 0x20;
+ if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+ invidconf |= 0x40;
+
+ if (priv->soc_type == SOC_H3) {
+ hdmi_phy_set_h3(priv, mode);
+ hdmi_inner_init(priv);
+ } else {
+ hdmi_io_init(priv);
+ }
+
+ hdmi_writeb(priv, R_1200_HDMI_FC_DBGFORCE,
+ HDMI_FC_DBGFORCE_FORCEVIDEO);
+ hdmi_writeb(priv, R_1219_HDMI_FC_DBGTMDS0, 0x00);
+ hdmi_writeb(priv, R_1000_HDMI_FC_INVIDCONF,
+ invidconf |
+ HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH);
+ hdmi_writeb(priv, 0x10001,
+ invidconf < 0x60 ? 0x03 : 0x00);
+ hdmi_writeb(priv, R_1002_HDMI_FC_INHACTV1,
+ mode->hdisplay >> 8);
+ hdmi_writeb(priv, R_100d_HDMI_FC_VSYNCINWIDTH,
+ mode->vsync_end - mode->vsync_start);
+ hdmi_writeb(priv, R_1006_HDMI_FC_INVACTV1,
+ mode->vdisplay >> 8);
+ hdmi_writeb(priv, R_1004_HDMI_FC_INHBLANK1,
+ h_blank >> 8);
+ hdmi_writeb(priv, R_100c_HDMI_FC_VSYNCINDELAY,
+ mode->vsync_start - mode->vdisplay);
+ hdmi_writeb(priv, R_1009_HDMI_FC_HSYNCINDELAY1,
+ h_front_p >> 8);
+ hdmi_writeb(priv, R_100b_HDMI_FC_HSYNCINWIDTH1,
+ h_sync_w >> 8);
+ hdmi_writeb(priv, R_1001_HDMI_FC_INHACTV0,
+ mode->hdisplay);
+ hdmi_writeb(priv, R_1003_HDMI_FC_INHBLANK0,
+ h_blank);
+ hdmi_writeb(priv, R_1008_HDMI_FC_HSYNCINDELAY0,
+ h_front_p);
+ hdmi_writeb(priv, R_100a_HDMI_FC_HSYNCINWIDTH0,
+ h_sync_w);
+ hdmi_writeb(priv, R_1005_HDMI_FC_INVACTV0,
+ mode->vdisplay);
+ hdmi_writeb(priv, R_1007_HDMI_FC_INVBLANK,
+ v_blank);
+ hdmi_writeb(priv, R_1011_HDMI_FC_CTRLDUR, 12);
+ hdmi_writeb(priv, R_1012_HDMI_FC_EXCTRLDUR, 32);
+ hdmi_writeb(priv, R_1013_HDMI_FC_EXCTRLSPAC, 1);
+ hdmi_writeb(priv, R_1014_HDMI_FC_CH0PREAM, 0x0b);
+ hdmi_writeb(priv, R_1015_HDMI_FC_CH1PREAM, 0x16);
+ hdmi_writeb(priv, R_1016_HDMI_FC_CH2PREAM, 0x21);
+ hdmi_writeb(priv, R_10e0_HDMI_FC_PRCONF,
+ mode->flags & DRM_MODE_FLAG_DBLCLK ? 0x21 : 0x10);
+ hdmi_writeb(priv, R_0801_HDMI_VP_PR_CD,
+ mode->flags & DRM_MODE_FLAG_DBLCLK ? 0x41 : 0x40);
+ hdmi_writeb(priv, R_0802_HDMI_VP_STUFF, 0x07);
+ hdmi_writeb(priv, R_0803_HDMI_VP_REMAP, 0x00);
+ hdmi_writeb(priv, R_0804_HDMI_VP_CONF, 0x47);
+ hdmi_writeb(priv, R_0200_HDMI_TX_INVID0, 0x01);
+ hdmi_writeb(priv, R_0201_HDMI_TX_INSTUFFING, 0x07);
+ hdmi_writeb(priv, R_0202_HDMI_TX_GYDATA0, 0x00);
+ hdmi_writeb(priv, R_0203_HDMI_TX_GYDATA1, 0x00);
+ hdmi_writeb(priv, R_0204_HDMI_TX_RCRDATA0, 0x00);
+ hdmi_writeb(priv, R_0205_HDMI_TX_RCRDATA1, 0x00);
+ hdmi_writeb(priv, R_0206_HDMI_TX_BCBDATA0, 0x00);
+ hdmi_writeb(priv, R_0207_HDMI_TX_BCBDATA1, 0x00);
+
+ if (priv->connector.eld[0]) { /* if audio/HDMI */
+ hdmi_writeb(priv, R_10b3_HDMI_FC_DATAUTO0, 0x08);
+ hdmi_writeb(priv, R_1031_HDMI_FC_VSDIEEEID2, 0x00);
+ hdmi_writeb(priv, R_1030_HDMI_FC_VSDIEEEID1,
+ HDMI_IEEE_OUI >> 8);
+ hdmi_writeb(priv, R_1029_HDMI_FC_VSDIEEEID0,
+ HDMI_IEEE_OUI & 0xff);
+ hdmi_writeb(priv, R_1032_HDMI_FC_VSDPAYLOAD0, 0x00);
+ hdmi_writeb(priv, R_1033_HDMI_FC_VSDPAYLOAD1, 0x00);
+ hdmi_writeb(priv, R_1034_HDMI_FC_VSDPAYLOAD2, 0x00);
+ hdmi_writeb(priv, R_10b4_HDMI_FC_DATAUTO1, 0x01);
+ hdmi_writeb(priv, R_10b5_HDMI_FC_DATAUTO2, 0x11);
+ hdmi_writeb(priv, R_1018_HDMI_FC_GCP, 0x00);
+ hdmi_writeb(priv, R_1104_HDMI_FC_GMD_HB, 0x00);
+ hdmi_writeb(priv, R_1103_HDMI_FC_GMD_CONF, 0x11);
+
+ hdmi_lock_read(priv);
+ hdmi_orb(priv, R_1000_HDMI_FC_INVIDCONF, 0x08);
+ hdmi_unlock_read(priv);
+
+ /* AVI */
+ hdmi_writeb(priv, R_1019_HDMI_FC_AVICONF0,
+ HDMI_FC_AVICONF0_SCAN_INFO_UNDERSCAN);
+ hdmi_writeb(priv, R_101a_HDMI_FC_AVICONF1, avi_d2);
+
+ hdmi_writeb(priv, R_101b_HDMI_FC_AVICONF2, 0x08);
+ hdmi_writeb(priv, R_101c_HDMI_FC_AVIVID, priv->cea_mode);
+ }
+
+ hdmi_writeb(priv, R_4004_HDMI_MC_FLOWCTRL, 0x00);
+ hdmi_writeb(priv, R_4001_HDMI_MC_CLKDIS, 0x00); /* enable all clocks */
+
+ if (priv->soc_type != SOC_H3)
+ hdmi_phy_set_a83t(priv, mode);
+
+ hdmi_writeb(priv, R_1200_HDMI_FC_DBGFORCE, 0x00);
+
+ return 0;
+}
+
+/* get a block of EDID */
+int hdmi_io_ddc_read(struct de2_hdmi_priv *priv,
+ char pointer, char off,
+ int nbyte, char *pbuf)
+{
+ unsigned int to_cnt;
+ u8 reg;
+ int ret = 0;
+
+ hdmi_lock_read(priv);
+ hdmi_writeb(priv, R_7e09_HDMI_I2CM_SOFTRSTZ, 0x00);
+ to_cnt = 50;
+ while (!(hdmi_readb(priv, R_7e09_HDMI_I2CM_SOFTRSTZ) & 0x01)) {
+ udelay(10);
+ if (--to_cnt == 0) { /* wait for 500us for timeout */
+ pr_warn("hdmi ddc reset timeout\n");
+ break;
+ }
+ }
+
+ hdmi_writeb(priv, R_7e07_HDMI_I2CM_DIV, 0x05);
+ hdmi_writeb(priv, R_7e05_HDMI_I2CM_INT, 0x08);
+ hdmi_writeb(priv, R_7e0c_HDMI_I2CM_SS_SCL_HCNT_0_ADDR, 0xd8);
+ hdmi_writeb(priv, R_7e0e_HDMI_I2CM_SS_SCL_LCNT_0_ADDR, 0xfe);
+
+ while (nbyte > 0) {
+ hdmi_writeb(priv, R_7e00_HDMI_I2CM_SLAVE, 0xa0 >> 1);
+ hdmi_writeb(priv, R_7e01_HDMI_I2CM_ADDRESS, off);
+ hdmi_writeb(priv, R_7e08_HDMI_I2CM_SEGADDR, 0x60 >> 1);
+ hdmi_writeb(priv, R_7e0a_HDMI_I2CM_SEGPTR, pointer);
+ hdmi_writeb(priv, R_7e04_HDMI_I2CM_OPERATION,
+ HDMI_I2CM_OPERATION_DDC_READ);
+
+ to_cnt = 200; /* timeout 100ms */
+ while (1) {
+ reg = hdmi_readb(priv, R_0105_HDMI_IH_I2CM_STAT0);
+ hdmi_writeb(priv, R_0105_HDMI_IH_I2CM_STAT0, reg);
+ if (reg & 0x02) {
+ *pbuf++ = hdmi_readb(priv,
+ R_7e03_HDMI_I2CM_DATAI);
+ break;
+ }
+ if (reg & 0x01) {
+ pr_warn("hdmi ddc read error\n");
+ ret = -1;
+ break;
+ }
+ if (--to_cnt == 0) {
+ if (!ret) {
+ pr_warn("hdmi ddc read timeout\n");
+ ret = -1;
+ }
+ break;
+ }
+ usleep_range(500, 800);
+ }
+ if (ret)
+ break;
+ nbyte--;
+ off++;
+ }
+ hdmi_unlock_read(priv);
+
+ return ret;
+}
+
+int hdmi_io_get_hpd(struct de2_hdmi_priv *priv)
+{
+ int ret;
+
+ hdmi_lock_read(priv);
+
+ if (priv->soc_type == SOC_H3)
+ ret = hdmi_readl(priv, HDMI_PHY_STATUS_REG) & 0x80000;
+ else
+ ret = hdmi_readb(priv, R_3005_HDMI_PHY_INT0) & 0x02;
+
+ hdmi_unlock_read(priv);
+
+ return ret != 0;
+}
+
+int hdmi_io_mode_valid(int cea_mode)
+{
+ /* check the known working resolutions */
+ switch (cea_mode) {
+ case VIC_720x480_60:
+ case VIC_1280x720_60:
+ case VIC_1920x1080i_60:
+ case VIC_720x480i_60:
+ case VIC_1920x1080_60:
+ case VIC_720x576_50:
+ case VIC_1280x720_50:
+ case VIC_1920x1080i_50:
+ case VIC_720x576i_50:
+ case VIC_1920x1080_50:
+ case VIC_1920x1080_24:
+ case VIC_1920x1080_25:
+ case VIC_1920x1080_30:
+ return 1;
+ }
+ return -1;
+}
--
2.10.2
--
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply related
* [PATCH v6 1/5] drm: sun8i: Add a basic DRM driver for Allwinner DE2
From: Jean-Francois Moine @ 2016-11-20 9:53 UTC (permalink / raw)
To: Dave Airlie, Maxime Ripard, Rob Herring
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
In-Reply-To: <cover.1479641523.git.moinejf-GANU6spQydw@public.gmane.org>
Allwinner's recent SoCs, as A64, A83T and H3, contain a new display
engine, DE2.
This patch adds a DRM video driver for this device.
Signed-off-by: Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
---
.../bindings/display/sunxi/sun8i-de2.txt | 83 +++
drivers/gpu/drm/Kconfig | 2 +
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/sun8i/Kconfig | 19 +
drivers/gpu/drm/sun8i/Makefile | 7 +
drivers/gpu/drm/sun8i/de2_crtc.c | 440 +++++++++++++
drivers/gpu/drm/sun8i/de2_crtc.h | 50 ++
drivers/gpu/drm/sun8i/de2_drm.h | 48 ++
drivers/gpu/drm/sun8i/de2_drv.c | 379 +++++++++++
drivers/gpu/drm/sun8i/de2_plane.c | 712 +++++++++++++++++++++
10 files changed, 1741 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/sunxi/sun8i-de2.txt
create mode 100644 drivers/gpu/drm/sun8i/Kconfig
create mode 100644 drivers/gpu/drm/sun8i/Makefile
create mode 100644 drivers/gpu/drm/sun8i/de2_crtc.c
create mode 100644 drivers/gpu/drm/sun8i/de2_crtc.h
create mode 100644 drivers/gpu/drm/sun8i/de2_drm.h
create mode 100644 drivers/gpu/drm/sun8i/de2_drv.c
create mode 100644 drivers/gpu/drm/sun8i/de2_plane.c
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun8i-de2.txt b/Documentation/devicetree/bindings/display/sunxi/sun8i-de2.txt
new file mode 100644
index 0000000..b9edd4b
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/sunxi/sun8i-de2.txt
@@ -0,0 +1,83 @@
+Allwinner sun8i Display Engine 2 subsystem
+==========================================
+
+The Allwinner DE2 subsystem contains a display controller (DE2),
+one or two LCD controllers (TCON) and their external interfaces.
+
+Display controller
+==================
+
+Required properties:
+
+- compatible: value should be one of the following
+ "allwinner,sun8i-a83t-display-engine"
+ "allwinner,sun8i-h3-display-engine"
+
+- clocks: must include clock specifiers corresponding to entries in the
+ clock-names property.
+
+- clock-names: must contain
+ "gate": DE bus gate
+ "clock": DE clock
+
+- resets: phandle to the reset of the device
+
+- ports: phandle's to the LCD ports
+
+LCD controller
+==============
+
+Required properties:
+
+- compatible: should be
+ "allwinner,sun8i-a83t-tcon"
+
+- clocks: must include clock specifiers corresponding to entries in the
+ clock-names property.
+
+- clock-names: must contain
+ "gate": TCON bus gate
+ "clock": TCON pixel clock
+
+- resets: phandle to the reset of the device
+
+- port: port node with endpoint definitions as defined in
+ Documentation/devicetree/bindings/media/video-interfaces.txt
+
+Example:
+
+ de: de-controller@01000000 {
+ compatible = "allwinner,sun8i-h3-display-engine";
+ ...
+ clocks = <&&ccu CLK_BUS_DE>, <&ccu CLK_DE>;
+ clock-names = "gate", "clock";
+ resets = <&ccu RST_BUS_DE>;
+ ports = <&lcd0_p>;
+ };
+
+ lcd0: lcd-controller@01c0c000 {
+ compatible = "allwinner,sun8i-a83t-tcon";
+ ...
+ clocks = <&ccu CLK_BUS_TCON0>, <&ccu CLK_TCON0>;
+ clock-names = "gate", "clock";
+ resets = <&ccu RST_BUS_TCON0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ lcd0_p: port {
+ lcd0_ep: endpoint {
+ remote-endpoint = <&hdmi_ep>;
+ };
+ };
+ };
+
+ hdmi: hdmi@01ee0000 {
+ ...
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port {
+ hdmi_ep: endpoint {
+ remote-endpoint = <&lcd0_ep>;
+ };
+ };
+ };
+
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 95fc041..bb1bfbc 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -202,6 +202,8 @@ source "drivers/gpu/drm/shmobile/Kconfig"
source "drivers/gpu/drm/sun4i/Kconfig"
+source "drivers/gpu/drm/sun8i/Kconfig"
+
source "drivers/gpu/drm/omapdrm/Kconfig"
source "drivers/gpu/drm/tilcdc/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 883f3e7..3e1eaa0 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/
obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
obj-y += omapdrm/
obj-$(CONFIG_DRM_SUN4I) += sun4i/
+obj-$(CONFIG_DRM_SUN8I) += sun8i/
obj-y += tilcdc/
obj-$(CONFIG_DRM_QXL) += qxl/
obj-$(CONFIG_DRM_BOCHS) += bochs/
diff --git a/drivers/gpu/drm/sun8i/Kconfig b/drivers/gpu/drm/sun8i/Kconfig
new file mode 100644
index 0000000..6940895
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/Kconfig
@@ -0,0 +1,19 @@
+#
+# Allwinner DE2 Video configuration
+#
+
+config DRM_SUN8I
+ bool
+
+config DRM_SUN8I_DE2
+ tristate "Support for Allwinner Video with DE2 interface"
+ depends on DRM && OF
+ depends on ARCH_SUNXI || COMPILE_TEST
+ select DRM_GEM_CMA_HELPER
+ select DRM_KMS_CMA_HELPER
+ select DRM_KMS_HELPER
+ select DRM_SUN8I
+ help
+ Choose this option if your Allwinner chipset has the DE2 interface
+ as the A64, A83T and H3. If M is selected the module will be called
+ sun8i-de2-drm.
diff --git a/drivers/gpu/drm/sun8i/Makefile b/drivers/gpu/drm/sun8i/Makefile
new file mode 100644
index 0000000..f107919
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for Allwinner's sun8i DRM device driver
+#
+
+sun8i-de2-drm-objs := de2_drv.o de2_crtc.o de2_plane.o
+
+obj-$(CONFIG_DRM_SUN8I_DE2) += sun8i-de2-drm.o
diff --git a/drivers/gpu/drm/sun8i/de2_crtc.c b/drivers/gpu/drm/sun8i/de2_crtc.c
new file mode 100644
index 0000000..65c9b93
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_crtc.c
@@ -0,0 +1,440 @@
+/*
+ * Allwinner DRM driver - DE2 CRTC
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/component.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <linux/io.h>
+#include <linux/of_irq.h>
+
+#include "de2_drm.h"
+#include "de2_crtc.h"
+
+/* I/O map */
+
+#define TCON_GCTL_REG 0x00
+#define TCON_GCTL_TCON_ENABLE BIT(31)
+#define TCON_GINT0_REG 0x04
+#define TCON_GINT0_TCON1_Vb_Int_En BIT(30)
+#define TCON_GINT0_TCON1_Vb_Int_Flag BIT(14)
+#define TCON0_CTL_REG 0x40
+#define TCON0_CTL_TCON_ENABLE BIT(31)
+#define TCON1_CTL_REG 0x90
+#define TCON1_CTL_TCON_ENABLE BIT(31)
+#define TCON1_CTL_INTERLACE_ENABLE BIT(20)
+#define TCON1_CTL_Start_Delay_SHIFT 4
+#define TCON1_CTL_Start_Delay_MASK GENMASK(8, 4)
+#define TCON1_BASIC0_REG 0x94 /* XI/YI */
+#define TCON1_BASIC1_REG 0x98 /* LS_XO/LS_YO */
+#define TCON1_BASIC2_REG 0x9c /* XO/YO */
+#define TCON1_BASIC3_REG 0xa0 /* HT/HBP */
+#define TCON1_BASIC4_REG 0xa4 /* VT/VBP */
+#define TCON1_BASIC5_REG 0xa8 /* HSPW/VSPW */
+#define TCON1_PS_SYNC_REG 0xb0
+#define TCON1_IO_POL_REG 0xf0
+#define TCON1_IO_POL_IO0_inv BIT(24)
+#define TCON1_IO_POL_IO1_inv BIT(25)
+#define TCON1_IO_POL_IO2_inv BIT(26)
+#define TCON1_IO_TRI_REG 0xf4
+#define TCON_CEU_CTL_REG 0x100
+#define TCON_CEU_CTL_ceu_en BIT(31)
+#define TCON1_FILL_CTL_REG 0x300
+#define TCON1_FILL_START0_REG 0x304
+#define TCON1_FILL_END0_REG 0x308
+#define TCON1_FILL_DATA0_REG 0x30c
+
+#define XY(x, y) (((x) << 16) | (y))
+
+#define andl_relaxed(addr, val) \
+ writel_relaxed(readl_relaxed(addr) & val, addr)
+#define orl_relaxed(addr, val) \
+ writel_relaxed(readl_relaxed(addr) | val, addr)
+
+/* vertical blank functions */
+
+static void de2_atomic_flush(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state)
+{
+ struct drm_pending_vblank_event *event = crtc->state->event;
+
+ if (event) {
+ crtc->state->event = NULL;
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (drm_crtc_vblank_get(crtc) == 0)
+ drm_crtc_arm_vblank_event(crtc, event);
+ else
+ drm_crtc_send_vblank_event(crtc, event);
+ spin_unlock_irq(&crtc->dev->event_lock);
+ }
+}
+
+static irqreturn_t de2_lcd_irq(int irq, void *dev_id)
+{
+ struct lcd *lcd = (struct lcd *) dev_id;
+ u32 isr;
+
+ isr = readl_relaxed(lcd->mmio + TCON_GINT0_REG);
+
+ drm_crtc_handle_vblank(&lcd->crtc);
+
+ writel_relaxed(isr & ~TCON_GINT0_TCON1_Vb_Int_Flag,
+ lcd->mmio + TCON_GINT0_REG);
+
+ return IRQ_HANDLED;
+}
+
+int de2_enable_vblank(struct drm_device *drm, unsigned int crtc_ix)
+{
+ struct priv *priv = drm_to_priv(drm);
+ struct lcd *lcd = priv->lcds[crtc_ix];
+
+ orl_relaxed(lcd->mmio + TCON_GINT0_REG, TCON_GINT0_TCON1_Vb_Int_En);
+
+ return 0;
+}
+
+void de2_disable_vblank(struct drm_device *drm, unsigned int crtc_ix)
+{
+ struct priv *priv = drm_to_priv(drm);
+ struct lcd *lcd = priv->lcds[crtc_ix];
+
+ andl_relaxed(lcd->mmio + TCON_GINT0_REG, ~TCON_GINT0_TCON1_Vb_Int_En);
+}
+
+void de2_vblank_reset(struct lcd *lcd)
+{
+ drm_crtc_vblank_reset(&lcd->crtc);
+}
+
+/* frame functions */
+static void de2_tcon_init(struct lcd *lcd)
+{
+ andl_relaxed(lcd->mmio + TCON0_CTL_REG, ~TCON0_CTL_TCON_ENABLE);
+ andl_relaxed(lcd->mmio + TCON1_CTL_REG, ~TCON1_CTL_TCON_ENABLE);
+ andl_relaxed(lcd->mmio + TCON_GCTL_REG, ~TCON_GCTL_TCON_ENABLE);
+
+ /* disable/ack interrupts */
+ writel_relaxed(0, lcd->mmio + TCON_GINT0_REG);
+}
+
+static void de2_tcon_enable(struct lcd *lcd)
+{
+ struct drm_crtc *crtc = &lcd->crtc;
+ const struct drm_display_mode *mode = &crtc->mode;
+ int interlace = mode->flags & DRM_MODE_FLAG_INTERLACE ? 2 : 1;
+ int start_delay;
+ u32 data;
+
+ orl_relaxed(lcd->mmio + TCON_GCTL_REG, TCON_GCTL_TCON_ENABLE);
+
+ data = XY(mode->hdisplay - 1, mode->vdisplay / interlace - 1);
+ writel_relaxed(data, lcd->mmio + TCON1_BASIC0_REG);
+ writel_relaxed(data, lcd->mmio + TCON1_BASIC1_REG);
+ writel_relaxed(data, lcd->mmio + TCON1_BASIC2_REG);
+ writel_relaxed(XY(mode->htotal - 1,
+ mode->htotal - mode->hsync_start - 1),
+ lcd->mmio + TCON1_BASIC3_REG);
+ writel_relaxed(XY(mode->vtotal * (3 - interlace),
+ mode->vtotal - mode->vsync_start - 1),
+ lcd->mmio + TCON1_BASIC4_REG);
+ writel_relaxed(XY(mode->hsync_end - mode->hsync_start - 1,
+ mode->vsync_end - mode->vsync_start - 1),
+ lcd->mmio + TCON1_BASIC5_REG);
+
+ writel_relaxed(XY(1, 1), lcd->mmio + TCON1_PS_SYNC_REG);
+
+ data = TCON1_IO_POL_IO2_inv;
+ if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+ data |= TCON1_IO_POL_IO0_inv;
+ if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+ data |= TCON1_IO_POL_IO1_inv;
+ writel_relaxed(data, lcd->mmio + TCON1_IO_POL_REG);
+
+ andl_relaxed(lcd->mmio + TCON_CEU_CTL_REG, ~TCON_CEU_CTL_ceu_en);
+
+ if (interlace == 2)
+ orl_relaxed(lcd->mmio + TCON1_CTL_REG,
+ TCON1_CTL_INTERLACE_ENABLE);
+ else
+ andl_relaxed(lcd->mmio + TCON1_CTL_REG,
+ ~TCON1_CTL_INTERLACE_ENABLE);
+
+ writel_relaxed(0, lcd->mmio + TCON1_FILL_CTL_REG);
+ writel_relaxed(mode->vtotal + 1, lcd->mmio + TCON1_FILL_START0_REG);
+ writel_relaxed(mode->vtotal, lcd->mmio + TCON1_FILL_END0_REG);
+ writel_relaxed(0, lcd->mmio + TCON1_FILL_DATA0_REG);
+
+ start_delay = (mode->vtotal - mode->vdisplay) / interlace - 5;
+ if (start_delay > 31)
+ start_delay = 31;
+ data = readl_relaxed(lcd->mmio + TCON1_CTL_REG);
+ data &= ~TCON1_CTL_Start_Delay_MASK;
+ data |= start_delay << TCON1_CTL_Start_Delay_SHIFT;
+ writel_relaxed(data, lcd->mmio + TCON1_CTL_REG);
+
+ writel_relaxed(0x0fffffff, /* TRI disabled */
+ lcd->mmio + TCON1_IO_TRI_REG);
+
+ orl_relaxed(lcd->mmio + TCON1_CTL_REG, TCON1_CTL_TCON_ENABLE);
+}
+
+static void de2_tcon_disable(struct lcd *lcd)
+{
+ andl_relaxed(lcd->mmio + TCON1_CTL_REG, ~TCON1_CTL_TCON_ENABLE);
+ andl_relaxed(lcd->mmio + TCON_GCTL_REG, ~TCON_GCTL_TCON_ENABLE);
+}
+
+static void de2_crtc_enable(struct drm_crtc *crtc)
+{
+ struct lcd *lcd = crtc_to_lcd(crtc);
+ struct drm_display_mode *mode = &crtc->mode;
+ struct clk *parent_clk;
+ u32 parent_rate;
+ int ret;
+
+ /* determine and set the best rate for the parent clock (pll-video) */
+ if (297000 % mode->clock == 0)
+ parent_rate = 297000000;
+ else if ((270000 * 2) % mode->clock == 0)
+ parent_rate = 270000000;
+ else
+ return; /* "640x480" rejected */
+ parent_clk = clk_get_parent(lcd->clk);
+
+ ret = clk_set_rate(parent_clk, parent_rate);
+ if (ret) {
+ dev_err(lcd->dev, "set parent rate failed %d\n", ret);
+ return;
+ }
+
+ /* then, set the TCON clock rate */
+ ret = clk_set_rate(lcd->clk, mode->clock * 1000);
+ if (ret) {
+ dev_err(lcd->dev, "set rate %dKHz failed %d\n",
+ mode->clock, ret);
+ return;
+ }
+
+ /* start the TCON */
+ reset_control_deassert(lcd->reset);
+ clk_prepare_enable(lcd->bus);
+ clk_prepare_enable(lcd->clk);
+ lcd->clk_enabled = true;
+
+ de2_tcon_enable(lcd);
+
+ de2_de_enable(lcd);
+
+ /* turn on blanking interrupt */
+ drm_crtc_vblank_on(crtc);
+}
+
+static void de2_crtc_disable(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_crtc_state)
+{
+ struct lcd *lcd = crtc_to_lcd(crtc);
+
+ if (!lcd->clk_enabled)
+ return; /* already disabled */
+ lcd->clk_enabled = false;
+
+ de2_de_disable(lcd);
+
+ drm_crtc_vblank_off(crtc);
+
+ de2_tcon_disable(lcd);
+
+ clk_disable_unprepare(lcd->clk);
+ clk_disable_unprepare(lcd->bus);
+ reset_control_assert(lcd->reset);
+}
+
+static const struct drm_crtc_funcs de2_crtc_funcs = {
+ .destroy = drm_crtc_cleanup,
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .reset = drm_atomic_helper_crtc_reset,
+ .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+};
+
+static const struct drm_crtc_helper_funcs de2_crtc_helper_funcs = {
+ .atomic_flush = de2_atomic_flush,
+ .enable = de2_crtc_enable,
+ .atomic_disable = de2_crtc_disable,
+};
+
+/* device init */
+static int de2_lcd_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct drm_device *drm = data;
+ struct priv *priv = drm_to_priv(drm);
+ struct lcd *lcd = dev_get_drvdata(dev);
+ struct drm_crtc *crtc = &lcd->crtc;
+ int ret, index;
+
+ lcd->priv = priv;
+
+ ret = de2_plane_init(drm, lcd);
+ if (ret < 0)
+ return ret;
+
+ drm_crtc_helper_add(crtc, &de2_crtc_helper_funcs);
+
+ ret = drm_crtc_init_with_planes(drm, crtc,
+ &lcd->planes[DE2_PRIMARY_PLANE],
+ &lcd->planes[DE2_CURSOR_PLANE],
+ &de2_crtc_funcs, NULL);
+ if (ret)
+ return ret;
+
+ /* set the lcd/crtc reference */
+ index = drm_crtc_index(crtc);
+ if (index >= ARRAY_SIZE(priv->lcds)) {
+ dev_err(drm->dev, "Bad crtc index");
+ return -ENOENT;
+ }
+ priv->lcds[index] = lcd;
+
+ return ret;
+}
+
+static void de2_lcd_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct lcd *lcd = platform_get_drvdata(pdev);
+
+ if (lcd->priv)
+ lcd->priv->lcds[drm_crtc_index(&lcd->crtc)] = NULL;
+}
+
+static const struct component_ops de2_lcd_ops = {
+ .bind = de2_lcd_bind,
+ .unbind = de2_lcd_unbind,
+};
+
+static int de2_lcd_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node, *tmp, *parent, *port;
+ struct lcd *lcd;
+ struct resource *res;
+ int id, irq, ret;
+
+ lcd = devm_kzalloc(dev, sizeof(*lcd), GFP_KERNEL);
+ if (!lcd)
+ return -ENOMEM;
+
+ /* get the LCD (mixer) number */
+ id = of_alias_get_id(np, "lcd");
+ if (id < 0 || id >= 2) {
+ dev_err(dev, "no or bad alias for lcd\n");
+ id = 0;
+ }
+ dev_set_drvdata(dev, lcd);
+ lcd->dev = dev;
+ lcd->mixer = id;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "failed to get memory resource\n");
+ return -EINVAL;
+ }
+
+ lcd->mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR(lcd->mmio)) {
+ dev_err(dev, "failed to map registers\n");
+ return PTR_ERR(lcd->mmio);
+ }
+
+ /* possible CRTCs */
+ parent = np;
+ tmp = of_get_child_by_name(np, "ports");
+ if (tmp)
+ parent = tmp;
+ port = of_get_child_by_name(parent, "port");
+ of_node_put(tmp);
+ if (!port) {
+ dev_err(dev, "no port node\n");
+ return -ENXIO;
+ }
+ lcd->crtc.port = port;
+
+ lcd->bus = devm_clk_get(dev, "bus");
+ if (IS_ERR(lcd->bus)) {
+ dev_err(dev, "get bus clock err %d\n", (int) PTR_ERR(lcd->bus));
+ ret = PTR_ERR(lcd->bus);
+ goto err;
+ }
+
+ lcd->clk = devm_clk_get(dev, "clock");
+ if (IS_ERR(lcd->clk)) {
+ ret = PTR_ERR(lcd->clk);
+ dev_err(dev, "get video clock err %d\n", ret);
+ goto err;
+ }
+
+ lcd->reset = devm_reset_control_get(dev, NULL);
+ if (IS_ERR(lcd->reset)) {
+ ret = PTR_ERR(lcd->reset);
+ dev_err(dev, "get reset err %d\n", ret);
+ goto err;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0) {
+ dev_err(dev, "unable to get irq\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ de2_tcon_init(lcd); /* stop TCON and avoid interrupts */
+
+ ret = devm_request_irq(dev, irq, de2_lcd_irq, 0,
+ dev_name(dev), lcd);
+ if (ret < 0) {
+ dev_err(dev, "unable to request irq %d\n", irq);
+ goto err;
+ }
+
+ return component_add(dev, &de2_lcd_ops);
+
+err:
+ of_node_put(lcd->crtc.port);
+ return ret;
+}
+
+static int de2_lcd_remove(struct platform_device *pdev)
+{
+ struct lcd *lcd = platform_get_drvdata(pdev);
+
+ component_del(&pdev->dev, &de2_lcd_ops);
+
+ of_node_put(lcd->crtc.port);
+
+ return 0;
+}
+
+static const struct of_device_id de2_lcd_ids[] = {
+ { .compatible = "allwinner,sun8i-a83t-tcon", },
+ { }
+};
+
+struct platform_driver de2_lcd_platform_driver = {
+ .probe = de2_lcd_probe,
+ .remove = de2_lcd_remove,
+ .driver = {
+ .name = "sun8i-de2-tcon",
+ .of_match_table = of_match_ptr(de2_lcd_ids),
+ },
+};
diff --git a/drivers/gpu/drm/sun8i/de2_crtc.h b/drivers/gpu/drm/sun8i/de2_crtc.h
new file mode 100644
index 0000000..f663ba4
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_crtc.h
@@ -0,0 +1,50 @@
+#ifndef __DE2_CRTC_H__
+#define __DE2_CRTC_H__
+/*
+ * Copyright (C) 2016 Jean-François Moine
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <drm/drm_plane_helper.h>
+
+struct clk;
+struct reset_control;
+struct priv;
+
+/* planes */
+#define DE2_PRIMARY_PLANE 0
+#define DE2_CURSOR_PLANE 1
+#define DE2_N_PLANES 5 /* number of planes - see plane_tb[] in de2_plane.c */
+
+struct lcd {
+ void __iomem *mmio;
+
+ struct device *dev;
+ struct drm_crtc crtc;
+
+ struct priv *priv; /* DRM/DE private data */
+
+ u8 mixer; /* LCD (mixer) number */
+ u8 delayed; /* bitmap of planes with delayed update */
+
+ u8 clk_enabled; /* used for error in crtc_enable */
+
+ struct clk *clk;
+ struct clk *bus;
+ struct reset_control *reset;
+
+ struct drm_plane planes[DE2_N_PLANES];
+};
+
+#define crtc_to_lcd(x) container_of(x, struct lcd, crtc)
+
+/* in de2_plane.c */
+void de2_de_enable(struct lcd *lcd);
+void de2_de_disable(struct lcd *lcd);
+int de2_plane_init(struct drm_device *drm, struct lcd *lcd);
+
+#endif /* __DE2_CRTC_H__ */
diff --git a/drivers/gpu/drm/sun8i/de2_drm.h b/drivers/gpu/drm/sun8i/de2_drm.h
new file mode 100644
index 0000000..c42c30a
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_drm.h
@@ -0,0 +1,48 @@
+#ifndef __DE2_DRM_H__
+#define __DE2_DRM_H__
+/*
+ * Copyright (C) 2016 Jean-François Moine
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <drm/drmP.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+
+struct drm_fbdev_cma;
+struct lcd;
+
+#define N_LCDS 2
+
+struct priv {
+ struct drm_device drm;
+ void __iomem *mmio;
+ struct clk *clk;
+ struct clk *gate;
+ struct reset_control *reset;
+
+ struct mutex mutex; /* protect DE I/O access */
+ u8 soc_type;
+#define SOC_A83T 0
+#define SOC_H3 1
+ u8 started; /* bitmap of started mixers */
+ u8 clean; /* bitmap of clean mixers */
+
+ struct drm_fbdev_cma *fbdev;
+
+ struct lcd *lcds[N_LCDS]; /* CRTCs */
+};
+
+#define drm_to_priv(x) container_of(x, struct priv, drm)
+
+/* in de2_crtc.c */
+int de2_enable_vblank(struct drm_device *drm, unsigned int crtc);
+void de2_disable_vblank(struct drm_device *drm, unsigned int crtc);
+void de2_vblank_reset(struct lcd *lcd);
+extern struct platform_driver de2_lcd_platform_driver;
+
+#endif /* __DE2_DRM_H__ */
diff --git a/drivers/gpu/drm/sun8i/de2_drv.c b/drivers/gpu/drm/sun8i/de2_drv.c
new file mode 100644
index 0000000..67368f5
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_drv.c
@@ -0,0 +1,379 @@
+/*
+ * Allwinner DRM driver - DE2 DRM driver
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/component.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "de2_drm.h"
+
+#define DRIVER_NAME "sun8i-de2"
+#define DRIVER_DESC "Allwinner DRM DE2"
+#define DRIVER_DATE "20161101"
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 0
+
+static const struct of_device_id de2_drm_of_match[] = {
+ { .compatible = "allwinner,sun8i-a83t-display-engine",
+ .data = (void *) SOC_A83T },
+ { .compatible = "allwinner,sun8i-h3-display-engine",
+ .data = (void *) SOC_H3 },
+ { },
+};
+MODULE_DEVICE_TABLE(of, de2_drm_of_match);
+
+static void de2_fb_output_poll_changed(struct drm_device *drm)
+{
+ struct priv *priv = drm_to_priv(drm);
+
+ if (priv->fbdev)
+ drm_fbdev_cma_hotplug_event(priv->fbdev);
+}
+
+static const struct drm_mode_config_funcs de2_mode_config_funcs = {
+ .fb_create = drm_fb_cma_create,
+ .output_poll_changed = de2_fb_output_poll_changed,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+/* -- DRM operations -- */
+
+static void de2_lastclose(struct drm_device *drm)
+{
+ struct priv *priv = drm_to_priv(drm);
+
+ if (priv->fbdev)
+ drm_fbdev_cma_restore_mode(priv->fbdev);
+}
+
+static const struct file_operations de2_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .unlocked_ioctl = drm_ioctl,
+ .poll = drm_poll,
+ .read = drm_read,
+ .llseek = no_llseek,
+ .mmap = drm_gem_cma_mmap,
+};
+
+static struct drm_driver de2_drm_driver = {
+ .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
+ DRIVER_ATOMIC,
+ .lastclose = de2_lastclose,
+ .get_vblank_counter = drm_vblank_no_hw_counter,
+ .enable_vblank = de2_enable_vblank,
+ .disable_vblank = de2_disable_vblank,
+ .gem_free_object = drm_gem_cma_free_object,
+ .gem_vm_ops = &drm_gem_cma_vm_ops,
+ .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+ .gem_prime_import = drm_gem_prime_import,
+ .gem_prime_export = drm_gem_prime_export,
+ .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
+ .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
+ .gem_prime_vmap = drm_gem_cma_prime_vmap,
+ .gem_prime_vunmap = drm_gem_cma_prime_vunmap,
+ .gem_prime_mmap = drm_gem_cma_prime_mmap,
+ .dumb_create = drm_gem_cma_dumb_create,
+ .dumb_map_offset = drm_gem_cma_dumb_map_offset,
+ .dumb_destroy = drm_gem_dumb_destroy,
+ .fops = &de2_fops,
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
+ .date = DRIVER_DATE,
+ .major = DRIVER_MAJOR,
+ .minor = DRIVER_MINOR,
+};
+
+/*
+ * Platform driver
+ */
+
+static int de2_drm_bind(struct device *dev)
+{
+ struct drm_device *drm;
+ struct priv *priv;
+ struct resource *res;
+ int ret;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ drm = &priv->drm;
+ dev_set_drvdata(dev, drm);
+
+ /* get the resources */
+ priv->soc_type = (int) of_match_device(de2_drm_of_match, dev)->data;
+
+ res = platform_get_resource(to_platform_device(dev),
+ IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "failed to get memory resource\n");
+ ret = -EINVAL;
+ goto out1;
+ }
+
+ priv->mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->mmio)) {
+ ret = PTR_ERR(priv->mmio);
+ dev_err(dev, "failed to map registers %d\n", ret);
+ goto out1;
+ }
+
+ priv->gate = devm_clk_get(dev, "bus");
+ if (IS_ERR(priv->gate)) {
+ ret = PTR_ERR(priv->gate);
+ dev_err(dev, "bus gate err %d\n", ret);
+ goto out1;
+ }
+
+ priv->clk = devm_clk_get(dev, "clock");
+ if (IS_ERR(priv->clk)) {
+ ret = PTR_ERR(priv->clk);
+ dev_err(dev, "clock err %d\n", ret);
+ goto out1;
+ }
+
+ priv->reset = devm_reset_control_get(dev, NULL);
+ if (IS_ERR(priv->reset)) {
+ ret = PTR_ERR(priv->reset);
+ dev_err(dev, "reset err %d\n", ret);
+ goto out1;
+ }
+
+ mutex_init(&priv->mutex); /* protect DE I/O accesses */
+
+ ret = drm_dev_init(drm, &de2_drm_driver, dev);
+ if (ret != 0) {
+ dev_err(dev, "out of memory\n");
+ goto out1;
+ }
+
+ drm_mode_config_init(drm);
+ drm->mode_config.min_width = 32; /* needed for cursor */
+ drm->mode_config.min_height = 32;
+ drm->mode_config.max_width = 1920;
+ drm->mode_config.max_height = 1080;
+ drm->mode_config.funcs = &de2_mode_config_funcs;
+
+ drm->irq_enabled = true;
+
+ /* start the subdevices */
+ ret = component_bind_all(dev, drm);
+ if (ret < 0)
+ goto out2;
+
+ /* initialize and disable vertical blanking on all CRTCs */
+ ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
+ if (ret < 0)
+ dev_warn(dev, "failed to initialize vblank\n");
+
+ {
+ struct lcd *lcd;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(priv->lcds); i++) {
+ lcd = priv->lcds[i];
+ if (lcd)
+ de2_vblank_reset(lcd);
+ }
+ }
+
+ drm_mode_config_reset(drm);
+
+ priv->fbdev = drm_fbdev_cma_init(drm,
+ 32, /* bpp */
+ drm->mode_config.num_crtc,
+ drm->mode_config.num_connector);
+ if (IS_ERR(priv->fbdev)) {
+ ret = PTR_ERR(priv->fbdev);
+ priv->fbdev = NULL;
+ goto out3;
+ }
+
+ drm_kms_helper_poll_init(drm);
+
+ ret = drm_dev_register(drm, 0);
+ if (ret < 0)
+ goto out4;
+
+ return 0;
+
+out4:
+ drm_fbdev_cma_fini(priv->fbdev);
+out3:
+ component_unbind_all(dev, drm);
+out2:
+ drm_dev_unref(drm);
+out1:
+ kfree(priv);
+ return ret;
+}
+
+static void de2_drm_unbind(struct device *dev)
+{
+ struct drm_device *drm = dev_get_drvdata(dev);
+ struct priv *priv = drm_to_priv(drm);
+
+ drm_dev_unregister(drm);
+
+ drm_fbdev_cma_fini(priv->fbdev);
+ drm_kms_helper_poll_fini(drm);
+ drm_vblank_cleanup(drm);
+ drm_mode_config_cleanup(drm);
+
+ component_unbind_all(dev, drm);
+
+ kfree(priv);
+}
+
+static const struct component_master_ops de2_drm_comp_ops = {
+ .bind = de2_drm_bind,
+ .unbind = de2_drm_unbind,
+};
+
+static int compare_of(struct device *dev, void *data)
+{
+ return dev->of_node == data;
+}
+
+static int de2_drm_add_components(struct device *dev,
+ int (*compare_of)(struct device *, void *),
+ const struct component_master_ops *m_ops)
+{
+ struct device_node *ep, *port, *remote;
+ struct component_match *match = NULL;
+ int i;
+
+ if (!dev->of_node)
+ return -EINVAL;
+
+ /* bind the CRTCs */
+ for (i = 0; ; i++) {
+ port = of_parse_phandle(dev->of_node, "ports", i);
+ if (!port)
+ break;
+
+ if (!of_device_is_available(port->parent)) {
+ of_node_put(port);
+ continue;
+ }
+
+ component_match_add(dev, &match, compare_of, port->parent);
+ of_node_put(port);
+ }
+
+ if (i == 0) {
+ dev_err(dev, "missing 'ports' property\n");
+ return -ENODEV;
+ }
+ if (!match) {
+ dev_err(dev, "no available port\n");
+ return -ENODEV;
+ }
+
+ /* bind the encoders/connectors */
+ for (i = 0; ; i++) {
+ port = of_parse_phandle(dev->of_node, "ports", i);
+ if (!port)
+ break;
+
+ if (!of_device_is_available(port->parent)) {
+ of_node_put(port);
+ continue;
+ }
+
+ for_each_child_of_node(port, ep) {
+ remote = of_graph_get_remote_port_parent(ep);
+ if (!remote || !of_device_is_available(remote)) {
+ of_node_put(remote);
+ continue;
+ }
+ if (!of_device_is_available(remote->parent)) {
+ dev_warn(dev,
+ "parent device of %s is not available\n",
+ remote->full_name);
+ of_node_put(remote);
+ continue;
+ }
+
+ component_match_add(dev, &match, compare_of, remote);
+ of_node_put(remote);
+ }
+ of_node_put(port);
+ }
+
+ return component_master_add_with_match(dev, m_ops, match);
+}
+
+static int de2_drm_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ ret = de2_drm_add_components(&pdev->dev,
+ compare_of,
+ &de2_drm_comp_ops);
+ if (ret == -EINVAL)
+ ret = -ENXIO;
+ return ret;
+}
+
+static int de2_drm_remove(struct platform_device *pdev)
+{
+ component_master_del(&pdev->dev, &de2_drm_comp_ops);
+
+ return 0;
+}
+
+static struct platform_driver de2_drm_platform_driver = {
+ .probe = de2_drm_probe,
+ .remove = de2_drm_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = de2_drm_of_match,
+ },
+};
+
+static int __init de2_drm_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&de2_lcd_platform_driver);
+ if (ret < 0)
+ return ret;
+
+ ret = platform_driver_register(&de2_drm_platform_driver);
+ if (ret < 0)
+ platform_driver_unregister(&de2_lcd_platform_driver);
+
+ return ret;
+}
+
+static void __exit de2_drm_fini(void)
+{
+ platform_driver_unregister(&de2_lcd_platform_driver);
+ platform_driver_unregister(&de2_drm_platform_driver);
+}
+
+module_init(de2_drm_init);
+module_exit(de2_drm_fini);
+
+MODULE_AUTHOR("Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>");
+MODULE_DESCRIPTION("Allwinner DE2 DRM Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/sun8i/de2_plane.c b/drivers/gpu/drm/sun8i/de2_plane.c
new file mode 100644
index 0000000..47c94dd
--- /dev/null
+++ b/drivers/gpu/drm/sun8i/de2_plane.c
@@ -0,0 +1,712 @@
+/*
+ * Allwinner DRM driver - Display Engine 2
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf-GANU6spQydw@public.gmane.org>
+ * Adapted from the sun8iw6 and sun8iw7 disp2 drivers
+ * Copyright (c) 2016 Allwinnertech Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/io.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "de2_drm.h"
+#include "de2_crtc.h"
+
+/* DE2 I/O map */
+
+#define DE2_MOD_REG 0x0000 /* 1 bit per LCD */
+#define DE2_GATE_REG 0x0004
+#define DE2_RESET_REG 0x0008
+#define DE2_DIV_REG 0x000c /* 4 bits per LCD */
+#define DE2_SEL_REG 0x0010
+
+#define DE2_MIXER0_BASE 0x00100000 /* LCD 0 */
+#define DE2_MIXER1_BASE 0x00200000 /* LCD 1 */
+
+/* mixer registers (addr / mixer base) */
+#define MIXER_GLB_REGS 0x00000 /* global control */
+#define MIXER_BLD_REGS 0x01000 /* alpha blending */
+#define MIXER_CHAN_REGS 0x02000 /* VI/UI overlay channels */
+#define MIXER_CHAN_SZ 0x1000 /* size of a channel */
+#define MIXER_VSU_REGS 0x20000 /* VSU */
+#define MIXER_GSU1_REGS 0x30000 /* GSUs */
+#define MIXER_GSU2_REGS 0x40000
+#define MIXER_GSU3_REGS 0x50000
+#define MIXER_FCE_REGS 0xa0000 /* FCE */
+#define MIXER_BWS_REGS 0xa2000 /* BWS */
+#define MIXER_LTI_REGS 0xa4000 /* LTI */
+#define MIXER_PEAK_REGS 0xa6000 /* PEAK */
+#define MIXER_ASE_REGS 0xa8000 /* ASE */
+#define MIXER_FCC_REGS 0xaa000 /* FCC */
+#define MIXER_DCSC_REGS 0xb0000 /* DCSC/SMBL */
+
+/* global control */
+#define MIXER_GLB_CTL_REG 0x00
+#define MIXER_GLB_CTL_rt_en BIT(0)
+#define MIXER_GLB_CTL_finish_irq_en BIT(4)
+#define MIXER_GLB_CTL_rtwb_port BIT(12)
+#define MIXER_GLB_STATUS_REG 0x04
+#define MIXER_GLB_DBUFF_REG 0x08
+#define MIXER_GLB_SIZE_REG 0x0c
+
+/* alpha blending */
+#define MIXER_BLD_FCOLOR_CTL_REG 0x00
+#define MIXER_BLD_FCOLOR_CTL_PEN(pipe) (0x0100 << (pipe))
+#define MIXER_BLD_ATTR_N 4 /* number of attribute blocks */
+#define MIXER_BLD_ATTR_SIZE (4 * 4) /* size of an attribute block */
+#define MIXER_BLD_ATTRx_FCOLOR(x) (0x04 + MIXER_BLD_ATTR_SIZE * (x))
+#define MIXER_BLD_ATTRx_INSIZE(x) (0x08 + MIXER_BLD_ATTR_SIZE * (x))
+#define MIXER_BLD_ATTRx_OFFSET(x) (0x0c + MIXER_BLD_ATTR_SIZE * (x))
+#define MIXER_BLD_ROUTE_REG 0x80
+#define MIXER_BLD_ROUTE(chan, pipe) ((chan) << ((pipe) * 4))
+#define MIXER_BLD_PREMULTIPLY_REG 0x84
+#define MIXER_BLD_BKCOLOR_REG 0x88
+#define MIXER_BLD_OUTPUT_SIZE_REG 0x8c
+#define MIXER_BLD_MODEx_REG(x) (0x90 + 4 * (x)) /* x = 0..3 */
+#define MIXER_BLD_MODE_SRCOVER 0x03010301
+#define MIXER_BLD_OUT_CTL_REG 0xfc
+
+/* VI channel (channel 0) */
+#define VI_CFG_N 4 /* number of layers */
+#define VI_CFG_SIZE 0x30 /* size of a layer */
+#define VI_CFGx_ATTR(l) (0x00 + VI_CFG_SIZE * (l))
+#define VI_CFG_ATTR_en BIT(0)
+#define VI_CFG_ATTR_fcolor_en BIT(4)
+#define VI_CFG_ATTR_fmt_SHIFT 8
+#define VI_CFG_ATTR_fmt_MASK GENMASK(12, 8)
+#define VI_CFG_ATTR_ui_sel BIT(15)
+#define VI_CFG_ATTR_top_down BIT(23)
+#define VI_CFGx_SIZE(l) (0x04 + VI_CFG_SIZE * (l))
+#define VI_CFGx_COORD(l) (0x08 + VI_CFG_SIZE * (l))
+#define VI_N_PLANES 3
+#define VI_CFGx_PITCHy(l, p) (0x0c + VI_CFG_SIZE * (l) + 4 * (p))
+#define VI_CFGx_TOP_LADDRy(l, p) (0x18 + VI_CFG_SIZE * (l) + 4 * (p))
+#define VI_CFGx_BOT_LADDRy(l, p) (0x24 + VI_CFG_SIZE * (l) + 4 * (p))
+#define VI_FCOLORx(l) (0xc0 + 4 * (l))
+#define VI_TOP_HADDRx(p) (0xd0 + 4 * (p))
+#define VI_BOT_HADDRx(p) (0xdc + 4 * (p))
+#define VI_OVL_SIZEx(n) (0xe8 + 4 * (n))
+#define VI_HORI_DSx(n) (0xf0 + 4 * (n))
+#define VI_VERT_DSx(n) (0xf8 + 4 * (n))
+#define VI_SIZE 0x100
+
+/* UI channel (channels 1..3) */
+#define UI_CFG_N 4 /* number of layers */
+#define UI_CFG_SIZE (8 * 4) /* size of a layer */
+#define UI_CFGx_ATTR(l) (0x00 + UI_CFG_SIZE * (l))
+#define UI_CFG_ATTR_en BIT(0)
+#define UI_CFG_ATTR_alpmod_SHIFT 1
+#define UI_CFG_ATTR_alpmod_MASK GENMASK(2, 1)
+#define UI_CFG_ATTR_fcolor_en BIT(4)
+#define UI_CFG_ATTR_fmt_SHIFT 8
+#define UI_CFG_ATTR_fmt_MASK GENMASK(12, 8)
+#define UI_CFG_ATTR_top_down BIT(23)
+#define UI_CFG_ATTR_alpha_SHIFT 24
+#define UI_CFG_ATTR_alpha_MASK GENMASK(31, 24)
+#define UI_CFGx_SIZE(l) (0x04 + UI_CFG_SIZE * (l))
+#define UI_CFGx_COORD(l) (0x08 + UI_CFG_SIZE * (l))
+#define UI_CFGx_PITCH(l) (0x0c + UI_CFG_SIZE * (l))
+#define UI_CFGx_TOP_LADDR(l) (0x10 + UI_CFG_SIZE * (l))
+#define UI_CFGx_BOT_LADDR(l) (0x14 + UI_CFG_SIZE * (l))
+#define UI_CFGx_FCOLOR(l) (0x18 + UI_CFG_SIZE * (l))
+#define UI_TOP_HADDR 0x80
+#define UI_BOT_HADDR 0x84
+#define UI_OVL_SIZE 0x88
+#define UI_SIZE 0x8c
+
+/* coordinates and sizes */
+#define XY(x, y) (((y) << 16) | (x))
+#define WH(w, h) ((((h) - 1) << 16) | ((w) - 1))
+
+/* UI video formats */
+#define DE2_FORMAT_ARGB_8888 0
+#define DE2_FORMAT_BGRA_8888 3
+#define DE2_FORMAT_XRGB_8888 4
+#define DE2_FORMAT_RGB_888 8
+#define DE2_FORMAT_BGR_888 9
+
+/* VI video formats */
+#define DE2_FORMAT_YUV422_I_YVYU 1 /* YVYU */
+#define DE2_FORMAT_YUV422_I_UYVY 2 /* UYVY */
+#define DE2_FORMAT_YUV422_I_YUYV 3 /* YUYV */
+#define DE2_FORMAT_YUV422_P 6 /* YYYY UU VV planar */
+#define DE2_FORMAT_YUV420_P 10 /* YYYY U V planar */
+
+/* plane formats */
+static const uint32_t ui_formats[] = {
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_BGRA8888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_BGR888,
+};
+
+static const uint32_t vi_formats[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_YUYV,
+ DRM_FORMAT_YVYU,
+ DRM_FORMAT_YUV422,
+ DRM_FORMAT_YUV420,
+ DRM_FORMAT_UYVY,
+ DRM_FORMAT_BGRA8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_BGR888,
+};
+
+/*
+ * plane table
+ *
+ * The chosen channel/layer assignment of the planes respects
+ * the following constraints:
+ * - the cursor must be in a channel higher than the primary channel
+ * - there are 4 channels in the LCD 0 and only 2 channels in the LCD 1
+ */
+static const struct {
+ u8 chan;
+ u8 layer;
+ u8 pipe;
+ u8 type; /* plane type */
+ const uint32_t *formats;
+ u8 n_formats;
+} plane_tb[] = {
+ [DE2_PRIMARY_PLANE] = { /* primary plane: channel 0 (VI) */
+ 0, 0, 0,
+ DRM_PLANE_TYPE_PRIMARY,
+ ui_formats, ARRAY_SIZE(ui_formats),
+ },
+ [DE2_CURSOR_PLANE] = { /* cursor: channel 1 (UI) */
+ 1, 0, 1,
+ DRM_PLANE_TYPE_CURSOR,
+ ui_formats, ARRAY_SIZE(ui_formats),
+ },
+ {
+ 0, 1, 0, /* 1st overlay: channel 0, layer 1 */
+ DRM_PLANE_TYPE_OVERLAY,
+ vi_formats, ARRAY_SIZE(vi_formats),
+ },
+ {
+ 0, 2, 0, /* 2nd overlay: channel 0, layer 2 */
+ DRM_PLANE_TYPE_OVERLAY,
+ vi_formats, ARRAY_SIZE(vi_formats),
+ },
+ {
+ 0, 3, 0, /* 3rd overlay: channel 0, layer 3 */
+ DRM_PLANE_TYPE_OVERLAY,
+ vi_formats, ARRAY_SIZE(vi_formats),
+ },
+};
+
+static inline void andl_relaxed(void __iomem *addr, u32 val)
+{
+ writel_relaxed(readl_relaxed(addr) & val, addr);
+}
+
+static inline void orl_relaxed(void __iomem *addr, u32 val)
+{
+ writel_relaxed(readl_relaxed(addr) | val, addr);
+}
+
+/* alert the DE processor about changes in a mixer configuration */
+static void de2_mixer_select(struct priv *priv,
+ int mixer,
+ void __iomem *mixer_io)
+{
+ /* select the mixer ? */
+ andl_relaxed(priv->mmio + DE2_SEL_REG, ~1);
+
+ /* double register switch */
+ writel_relaxed(1, mixer_io + MIXER_GLB_REGS + MIXER_GLB_DBUFF_REG);
+}
+
+/*
+ * cleanup a mixer
+ *
+ * This is needed only once after power on.
+ */
+static void de2_mixer_cleanup(struct priv *priv, int mixer)
+{
+ void __iomem *mixer_io = priv->mmio;
+ void __iomem *chan_io;
+ u32 size = WH(1920, 1080); /* (any size) */
+ unsigned int i;
+ u32 data;
+
+ mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE;
+ chan_io = mixer_io + MIXER_CHAN_REGS;
+
+ /* set the A83T clock divider (500 / 2) = 250MHz */
+ if (priv->soc_type == SOC_A83T)
+ writel_relaxed(0x00000011, /* div = 2 for both LCDs */
+ priv->mmio + DE2_DIV_REG);
+
+ de2_mixer_select(priv, mixer, mixer_io);
+ writel_relaxed(size, mixer_io + MIXER_GLB_REGS + MIXER_GLB_SIZE_REG);
+
+ /*
+ * clear the VI/UI channels
+ * LCD0: 1 VI and 3 UIs
+ * LCD1: 1 VI and 1 UI
+ */
+ memset_io(chan_io, 0, VI_SIZE);
+ memset_io(chan_io + MIXER_CHAN_SZ, 0, UI_SIZE);
+ if (mixer == 0) {
+ memset_io(chan_io + MIXER_CHAN_SZ * 2, 0, UI_SIZE);
+ memset_io(chan_io + MIXER_CHAN_SZ * 3, 0, UI_SIZE);
+ }
+
+ /* clear and set default values alpha blending */
+ memset_io(mixer_io + MIXER_BLD_REGS, 0,
+ MIXER_BLD_ATTR_SIZE * MIXER_BLD_ATTR_N);
+ writel_relaxed(0x00000001 | /* fcolor for primary */
+ MIXER_BLD_FCOLOR_CTL_PEN(0),
+ mixer_io + MIXER_BLD_REGS + MIXER_BLD_FCOLOR_CTL_REG);
+ for (i = 0; i < MIXER_BLD_ATTR_N; i++) {
+ writel_relaxed(0xff000000,
+ mixer_io + MIXER_BLD_REGS + MIXER_BLD_ATTRx_FCOLOR(i));
+ writel_relaxed(size,
+ mixer_io + MIXER_BLD_REGS + MIXER_BLD_ATTRx_INSIZE(i));
+ writel_relaxed(0,
+ mixer_io + MIXER_BLD_REGS + MIXER_BLD_ATTRx_OFFSET(i));
+ }
+ writel_relaxed(0, mixer_io + MIXER_BLD_REGS + MIXER_BLD_OUT_CTL_REG);
+
+ /* prepare the pipe route for the planes */
+ data = 0;
+ for (i = 0; i < DE2_N_PLANES; i++)
+ data |= MIXER_BLD_ROUTE(plane_tb[i].chan, plane_tb[i].pipe);
+ writel_relaxed(data, mixer_io + MIXER_BLD_REGS + MIXER_BLD_ROUTE_REG);
+
+ writel_relaxed(0, mixer_io + MIXER_BLD_REGS +
+ MIXER_BLD_PREMULTIPLY_REG);
+ writel_relaxed(0xff000000, mixer_io + MIXER_BLD_REGS +
+ MIXER_BLD_BKCOLOR_REG);
+ writel_relaxed(size, mixer_io + MIXER_BLD_REGS +
+ MIXER_BLD_OUTPUT_SIZE_REG);
+ writel_relaxed(MIXER_BLD_MODE_SRCOVER,
+ mixer_io + MIXER_BLD_REGS + MIXER_BLD_MODEx_REG(0));
+ writel_relaxed(MIXER_BLD_MODE_SRCOVER,
+ mixer_io + MIXER_BLD_REGS + MIXER_BLD_MODEx_REG(1));
+ writel_relaxed(0, mixer_io + MIXER_BLD_REGS + MIXER_BLD_OUT_CTL_REG);
+
+ /* disable the enhancements */
+ writel_relaxed(0, mixer_io + MIXER_VSU_REGS);
+ writel_relaxed(0, mixer_io + MIXER_GSU1_REGS);
+ writel_relaxed(0, mixer_io + MIXER_GSU2_REGS);
+ writel_relaxed(0, mixer_io + MIXER_GSU3_REGS);
+ writel_relaxed(0, mixer_io + MIXER_FCE_REGS);
+ writel_relaxed(0, mixer_io + MIXER_BWS_REGS);
+ writel_relaxed(0, mixer_io + MIXER_LTI_REGS);
+ writel_relaxed(0, mixer_io + MIXER_PEAK_REGS);
+ writel_relaxed(0, mixer_io + MIXER_ASE_REGS);
+ writel_relaxed(0, mixer_io + MIXER_FCC_REGS);
+ writel_relaxed(0, mixer_io + MIXER_DCSC_REGS);
+}
+
+/* enable a mixer */
+static void de2_mixer_enable(struct lcd *lcd)
+{
+ struct priv *priv = lcd->priv;
+ void __iomem *mixer_io = priv->mmio;
+ int mixer = lcd->mixer;
+ u32 data;
+
+ mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE;
+
+ if (priv->started & (1 << mixer))
+ return; /* mixer already enabled */
+
+ /* if not done yet, start the DE processor */
+ if (!priv->started) {
+ reset_control_deassert(priv->reset);
+ clk_prepare_enable(priv->gate);
+ clk_prepare_enable(priv->clk);
+ }
+ priv->started |= 1 << mixer;
+
+ /* deassert the mixer and enable the clock */
+ orl_relaxed(priv->mmio + DE2_RESET_REG, mixer == 0 ? 1 : 4);
+ data = 1 << mixer; /* 1 bit / lcd */
+ orl_relaxed(priv->mmio + DE2_GATE_REG, data);
+ orl_relaxed(priv->mmio + DE2_MOD_REG, data);
+
+ /* enable */
+ andl_relaxed(priv->mmio + DE2_SEL_REG, ~1); /* mixer select */
+ writel_relaxed(MIXER_GLB_CTL_rt_en | MIXER_GLB_CTL_rtwb_port,
+ mixer_io + MIXER_GLB_REGS + MIXER_GLB_CTL_REG);
+ writel_relaxed(0, mixer_io + MIXER_GLB_REGS + MIXER_GLB_STATUS_REG);
+
+ /* restore the frame buffer size */
+ writel_relaxed(WH(lcd->crtc.mode.hdisplay,
+ lcd->crtc.mode.vdisplay),
+ mixer_io + MIXER_GLB_REGS + MIXER_GLB_SIZE_REG);
+
+ /* if not yet done, cleanup */
+ if (!(priv->clean & (1 << mixer))) {
+ priv->clean |= 1 << mixer;
+ de2_mixer_cleanup(priv, mixer);
+ }
+}
+
+/* enable a LCD (DE mixer) */
+void de2_de_enable(struct lcd *lcd)
+{
+ mutex_lock(&lcd->priv->mutex);
+
+ de2_mixer_enable(lcd);
+
+ mutex_unlock(&lcd->priv->mutex);
+}
+
+/* disable a LCD (DE mixer) */
+void de2_de_disable(struct lcd *lcd)
+{
+ struct priv *priv = lcd->priv;
+ void __iomem *mixer_io = priv->mmio;
+ int mixer = lcd->mixer;
+ u32 data;
+
+ if (!(priv->started & (1 << mixer)))
+ return; /* mixer already disabled */
+
+ mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE;
+
+ mutex_lock(&priv->mutex);
+
+ de2_mixer_select(priv, mixer, mixer_io);
+
+ writel_relaxed(0, mixer_io + MIXER_GLB_REGS + MIXER_GLB_CTL_REG);
+
+ data = ~(1 << mixer);
+ andl_relaxed(priv->mmio + DE2_MOD_REG, data);
+ andl_relaxed(priv->mmio + DE2_GATE_REG, data);
+ andl_relaxed(priv->mmio + DE2_RESET_REG, data);
+
+ mutex_unlock(&priv->mutex);
+
+ /* if all mixers are disabled, stop the DE */
+ priv->started &= ~(1 << mixer);
+ if (!priv->started) {
+ clk_disable_unprepare(priv->clk);
+ clk_disable_unprepare(priv->gate);
+ reset_control_assert(priv->reset);
+ }
+}
+
+static void de2_vi_update(void __iomem *chan_io,
+ struct drm_gem_cma_object *gem,
+ int layer,
+ unsigned int fmt,
+ u32 ui_sel,
+ u32 size,
+ u32 coord,
+ struct drm_framebuffer *fb,
+ u32 screen_size)
+{
+ int i;
+
+ writel_relaxed(VI_CFG_ATTR_en |
+ (fmt << VI_CFG_ATTR_fmt_SHIFT) |
+ ui_sel,
+ chan_io + VI_CFGx_ATTR(layer));
+ writel_relaxed(size, chan_io + VI_CFGx_SIZE(layer));
+ writel_relaxed(coord, chan_io + VI_CFGx_COORD(layer));
+ for (i = 0; i < VI_N_PLANES; i++) {
+ writel_relaxed(fb->pitches[i] ? fb->pitches[i] :
+ fb->pitches[0],
+ chan_io + VI_CFGx_PITCHy(layer, i));
+ writel_relaxed(gem->paddr + fb->offsets[i],
+ chan_io + VI_CFGx_TOP_LADDRy(layer, i));
+ }
+ writel_relaxed(0xff000000, chan_io + VI_FCOLORx(layer));
+ if (layer == 0) {
+ writel_relaxed(screen_size,
+ chan_io + VI_OVL_SIZEx(0));
+ }
+}
+
+static void de2_ui_update(void __iomem *chan_io,
+ struct drm_gem_cma_object *gem,
+ int layer,
+ unsigned int fmt,
+ u32 alpha_glob,
+ u32 size,
+ u32 coord,
+ struct drm_framebuffer *fb,
+ u32 screen_size)
+{
+ writel_relaxed(UI_CFG_ATTR_en |
+ (fmt << UI_CFG_ATTR_fmt_SHIFT) |
+ alpha_glob,
+ chan_io + UI_CFGx_ATTR(layer));
+ writel_relaxed(size, chan_io + UI_CFGx_SIZE(layer));
+ writel_relaxed(coord, chan_io + UI_CFGx_COORD(layer));
+ writel_relaxed(fb->pitches[0], chan_io + UI_CFGx_PITCH(layer));
+ writel_relaxed(gem->paddr + fb->offsets[0],
+ chan_io + UI_CFGx_TOP_LADDR(layer));
+ if (layer == 0)
+ writel_relaxed(screen_size, chan_io + UI_OVL_SIZE);
+}
+
+static void de2_plane_update(struct priv *priv, struct lcd *lcd,
+ int plane_num,
+ struct drm_plane_state *state,
+ struct drm_plane_state *old_state)
+{
+ void __iomem *mixer_io = priv->mmio;
+ void __iomem *chan_io;
+ struct drm_framebuffer *fb = state->fb;
+ struct drm_gem_cma_object *gem;
+ u32 size = WH(state->crtc_w, state->crtc_h);
+ u32 coord, screen_size;
+ u32 fcolor;
+ u32 ui_sel, alpha_glob;
+ int mixer = lcd->mixer;
+ int chan, layer, x, y;
+ unsigned int fmt;
+
+ mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE;
+
+ chan = plane_tb[plane_num].chan;
+ layer = plane_tb[plane_num].layer;
+
+ chan_io = mixer_io + MIXER_CHAN_REGS + MIXER_CHAN_SZ * chan;
+
+ x = state->crtc_x >= 0 ? state->crtc_x : 0;
+ y = state->crtc_y >= 0 ? state->crtc_y : 0;
+ coord = XY(x, y);
+
+ /* if plane update was delayed, force a full update */
+ if (priv->lcds[drm_crtc_index(&lcd->crtc)]->delayed &
+ (1 << plane_num)) {
+ priv->lcds[drm_crtc_index(&lcd->crtc)]->delayed &=
+ ~(1 << plane_num);
+
+ /* handle plane move */
+ } else if (fb == old_state->fb) {
+ de2_mixer_select(priv, mixer, mixer_io);
+ if (chan == 0)
+ writel_relaxed(coord, chan_io + VI_CFGx_COORD(layer));
+ else
+ writel_relaxed(coord, chan_io + UI_CFGx_COORD(layer));
+ return;
+ }
+
+ gem = drm_fb_cma_get_gem_obj(fb, 0);
+
+ ui_sel = alpha_glob = 0;
+
+ switch (fb->pixel_format) {
+ case DRM_FORMAT_ARGB8888:
+ fmt = DE2_FORMAT_ARGB_8888;
+ ui_sel = VI_CFG_ATTR_ui_sel;
+ break;
+ case DRM_FORMAT_BGRA8888:
+ fmt = DE2_FORMAT_BGRA_8888;
+ ui_sel = VI_CFG_ATTR_ui_sel;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ fmt = DE2_FORMAT_XRGB_8888;
+ ui_sel = VI_CFG_ATTR_ui_sel;
+ alpha_glob = (1 << UI_CFG_ATTR_alpmod_SHIFT) |
+ (0xff << UI_CFG_ATTR_alpha_SHIFT);
+ break;
+ case DRM_FORMAT_RGB888:
+ fmt = DE2_FORMAT_RGB_888;
+ ui_sel = VI_CFG_ATTR_ui_sel;
+ break;
+ case DRM_FORMAT_BGR888:
+ fmt = DE2_FORMAT_BGR_888;
+ ui_sel = VI_CFG_ATTR_ui_sel;
+ break;
+ case DRM_FORMAT_YUYV:
+ fmt = DE2_FORMAT_YUV422_I_YUYV;
+ break;
+ case DRM_FORMAT_YVYU:
+ fmt = DE2_FORMAT_YUV422_I_YVYU;
+ break;
+ case DRM_FORMAT_YUV422:
+ fmt = DE2_FORMAT_YUV422_P;
+ break;
+ case DRM_FORMAT_YUV420:
+ fmt = DE2_FORMAT_YUV420_P;
+ break;
+ case DRM_FORMAT_UYVY:
+ fmt = DE2_FORMAT_YUV422_I_UYVY;
+ break;
+ default:
+ pr_err("de2_plane_update: format %.4s not yet treated\n",
+ (char *) &fb->pixel_format);
+ return;
+ }
+
+ /* the overlay size is the one of the primary plane */
+ screen_size = plane_num == DE2_PRIMARY_PLANE ?
+ size :
+ readl_relaxed(mixer_io + MIXER_GLB_REGS + MIXER_GLB_SIZE_REG);
+
+ /* prepare pipe enable */
+ fcolor = readl_relaxed(mixer_io + MIXER_BLD_REGS +
+ MIXER_BLD_FCOLOR_CTL_REG);
+ fcolor |= MIXER_BLD_FCOLOR_CTL_PEN(plane_tb[plane_num].pipe);
+
+ de2_mixer_select(priv, mixer, mixer_io);
+
+ if (chan == 0) /* VI channel */
+ de2_vi_update(chan_io, gem, layer, fmt, ui_sel, size, coord,
+ fb, screen_size);
+ else /* UI channel */
+ de2_ui_update(chan_io, gem, layer, fmt, alpha_glob, size, coord,
+ fb, screen_size);
+ writel_relaxed(fcolor, mixer_io + MIXER_BLD_REGS +
+ MIXER_BLD_FCOLOR_CTL_REG);
+}
+
+static void de2_plane_disable(struct priv *priv,
+ int mixer, int plane_num)
+{
+ void __iomem *mixer_io = priv->mmio;
+ void __iomem *chan_io;
+ u32 fcolor;
+ int chan, layer, chan_disable = 0;
+
+ mixer_io += (mixer == 0) ? DE2_MIXER0_BASE : DE2_MIXER1_BASE;
+
+ chan = plane_tb[plane_num].chan;
+ layer = plane_tb[plane_num].layer;
+
+ chan_io = mixer_io + MIXER_CHAN_REGS + MIXER_CHAN_SZ * chan;
+
+ /*
+ * check if the pipe should be disabled
+ * (this code works with only 2 layers)
+ */
+ if (chan == 0) {
+ if (readl_relaxed(chan_io + VI_CFGx_ATTR(1 - layer)) == 0)
+ chan_disable = 1;
+ } else {
+ if (readl_relaxed(chan_io + UI_CFGx_ATTR(1 - layer)) == 0)
+ chan_disable = 1;
+ }
+
+ fcolor = readl_relaxed(mixer_io + MIXER_BLD_REGS +
+ MIXER_BLD_FCOLOR_CTL_REG);
+
+ de2_mixer_select(priv, mixer, mixer_io);
+
+ if (chan == 0)
+ writel_relaxed(0, chan_io + VI_CFGx_ATTR(layer));
+ else
+ writel_relaxed(0, chan_io + UI_CFGx_ATTR(layer));
+
+ /* if no more layer in this channel, disable the pipe */
+ if (chan_disable) {
+ writel_relaxed(fcolor &
+ ~MIXER_BLD_FCOLOR_CTL_PEN(plane_tb[plane_num].pipe),
+ mixer_io + MIXER_BLD_REGS + MIXER_BLD_FCOLOR_CTL_REG);
+ }
+}
+
+static void de2_drm_plane_update(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct drm_plane_state *state = plane->state;
+ struct drm_crtc *crtc = state->crtc;
+ struct lcd *lcd = crtc_to_lcd(crtc);
+ struct priv *priv = lcd->priv;
+ int plane_num = plane - lcd->planes;
+
+ /* if the crtc is disabled, mark update delayed */
+ if (!(priv->started & (1 << lcd->mixer))) {
+ lcd->delayed |= 1 << plane_num;
+ return; /* mixer disabled */
+ }
+
+ mutex_lock(&priv->mutex);
+
+ de2_plane_update(priv, lcd, plane_num, state, old_state);
+
+ mutex_unlock(&priv->mutex);
+}
+
+static void de2_drm_plane_disable(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct drm_crtc *crtc = old_state->crtc;
+ struct lcd *lcd = crtc_to_lcd(crtc);
+ struct priv *priv = lcd->priv;
+ int plane_num = plane - lcd->planes;
+
+ if (!(priv->started & (1 << lcd->mixer)))
+ return; /* mixer disabled */
+
+ mutex_lock(&priv->mutex);
+
+ de2_plane_disable(lcd->priv, lcd->mixer, plane_num);
+
+ mutex_unlock(&priv->mutex);
+}
+
+static const struct drm_plane_helper_funcs plane_helper_funcs = {
+ .atomic_update = de2_drm_plane_update,
+ .atomic_disable = de2_drm_plane_disable,
+};
+
+static const struct drm_plane_funcs plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .destroy = drm_plane_cleanup,
+ .reset = drm_atomic_helper_plane_reset,
+ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+static int de2_one_plane_init(struct drm_device *drm,
+ struct drm_plane *plane,
+ int possible_crtcs,
+ int plane_num)
+{
+ int ret;
+
+ ret = drm_universal_plane_init(drm, plane, possible_crtcs,
+ &plane_funcs,
+ plane_tb[plane_num].formats,
+ plane_tb[plane_num].n_formats,
+ plane_tb[plane_num].type, NULL);
+ if (ret >= 0)
+ drm_plane_helper_add(plane, &plane_helper_funcs);
+
+ return ret;
+}
+
+/* initialize the planes */
+int de2_plane_init(struct drm_device *drm, struct lcd *lcd)
+{
+ int i, n, ret, possible_crtcs = 1 << drm_crtc_index(&lcd->crtc);
+
+ n = ARRAY_SIZE(plane_tb);
+ if (n != DE2_N_PLANES) {
+ dev_err(lcd->dev, "Bug: incorrect number of planes %d != "
+ __stringify(DE2_N_PLANES) "\n", n);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < n; i++) {
+ ret = de2_one_plane_init(drm, &lcd->planes[i],
+ possible_crtcs, i);
+ if (ret < 0) {
+ dev_err(lcd->dev, "plane init failed %d\n", ret);
+ break;
+ }
+ }
+
+ return ret;
+}
--
2.10.2
--
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply related
* Re: [RFC PATCH v2 5/7] iio: multiplexer: new iio category and iio-mux driver
From: Peter Rosin @ 2016-11-19 22:08 UTC (permalink / raw)
To: Jonathan Cameron, linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: Wolfram Sang, Rob Herring, Mark Rutland, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Arnd Bergmann,
Greg Kroah-Hartman, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <e462780e-305b-2cb9-1acd-c66dee312fac-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
On 2016-11-19 16:49, Jonathan Cameron wrote:
> On 17/11/16 21:48, Peter Rosin wrote:
>> When a multiplexer changes how an iio device behaves (for example
>> by feeding different signals to an ADC), this driver can be used
>> create one virtual iio channel for each multiplexer state.
>>
>> Depends on the generic multiplexer driver.
> I'm not really following what all the ext info stuff in here is about.
> Could you add a little more description of that?
Certainly. I have two needs for this series. The first one is simple
when it comes to the iio part and complex because the mux is shared
between three 8-way muxes on three of the inputs to an ADS-1015 ADC.
The forth ADC line to the ADS-1015 is not muxed. Those three muxes
are of course GPIO-controlled and share GPIO pins. And the GPIO
pins also control an i2c bus that is muxed 8-ways as well. There are
eight (possible) batteries, and we monitor voltage/current/temp with
the 3 muxed ADC lines, and 8 chargers sit on i2c behind the i2c mux.
I guess it felt natural for the HW designer to select battery with
the GPIO lines, but that do not fit too well with the code as it
is without this series...
For this first need, the iio mux does not need ext_info.
The second need is simple in the mux part but worse in the iio
department. It's another 8-way mux that simply muxes an ADC line,
so that is simple. However, the ADC line is the envelope detector
that just got added to linux-next, and it has two ext_info
attributes that needs to be set differently for different mux
states. Two of the states need "invert" to be false, the rest need
"invert" to be true. And it is also preferable to have different
values for "compare_interval" for different mux states since the
signals on the diffrent mux states have the different frequency
characteristics.
True, I could have the ext-info attributes go straight through
the mux, and just start by writing values to "invert"
and "compare_interval", and only then read a sample. But then I
would need to lock out other users during the duration of this
transaction. I believe that the best place to put that lock is
in the iio mux (when it locks its control-mux) and not leave it
to userspace to solve this in some brittle cooperative manner.
> Perhaps an example of how it is used and what the resulting interface
> looks like?
The resulting interface is just a copy of the (ext_info) interface
exposed by the parent channel (with a cache that is rewritten to
the parent on every iio mux state change). I have plans to add code
to not rewrite ext_info attributes that have never been changed in
any mux state.
Below I have an example file listing.
device0 is a dpot (mcp4561).
device1 is a dac (dpot-dac, wrapping the above dpot).
device2 is an adc (envelope-detector, wrapping the above dac)
device3 is a mux (iio-mux, wrapping the above adc)
The 8-way iio-mux have no signals attached on mux states 0 and 1, which
is why the first channel for device 3 is in_altvoltage2.
Ultimately, I would like some knob to hide devices 0, 1 and 2 from
userspace. They need/should only be visible to in-kernel users. Or
is there such a knob already?
Cheers,
Peter
$ ls /sys/bus/iio/devices/iio\:device*
/sys/bus/iio/devices/iio:device0:
dev out_resistance_raw_available
name out_resistance_scale
of_node power
out_resistance0_raw subsystem
out_resistance1_raw uevent
/sys/bus/iio/devices/iio:device1:
dev out_voltage0_scale
name power
of_node subsystem
out_voltage0_raw uevent
out_voltage0_raw_available
/sys/bus/iio/devices/iio:device2:
dev name
in_altvoltage0_compare_interval of_node
in_altvoltage0_invert power
in_altvoltage0_raw subsystem
in_altvoltage0_scale uevent
/sys/bus/iio/devices/iio:device3:
dev in_altvoltage5_raw
in_altvoltage2_compare_interval in_altvoltage5_scale
in_altvoltage2_invert in_altvoltage6_compare_interval
in_altvoltage2_raw in_altvoltage6_invert
in_altvoltage2_scale in_altvoltage6_raw
in_altvoltage3_compare_interval in_altvoltage6_scale
in_altvoltage3_invert in_altvoltage7_compare_interval
in_altvoltage3_raw in_altvoltage7_invert
in_altvoltage3_scale in_altvoltage7_raw
in_altvoltage4_compare_interval in_altvoltage7_scale
in_altvoltage4_invert name
in_altvoltage4_raw of_node
in_altvoltage4_scale power
in_altvoltage5_compare_interval subsystem
in_altvoltage5_invert uevent
^ permalink raw reply
* Re: [PATCH v7 4/9] drivers:input:tsc2007: add iio interface to read external ADC input and temperature
From: Jonathan Cameron @ 2016-11-19 20:09 UTC (permalink / raw)
To: Dmitry Torokhov, Jonathan Cameron
Cc: H. Nikolaus Schaller, Sebastian Reichel, Mark Rutland,
Benoît Cousson, Tony Lindgren, Russell King, Arnd Bergmann,
Michael Welling, Mika Penttilä, Javier Martinez Canillas,
Igor Grinberg, Andrew F. Davis, Mark Brown,
linux-input-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-omap-u79uwXL29TY76Z2rM5mHXA,
letux-kernel-S0jZdbWzriLCfDggNXIi3w,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
kernel-Jl6IXVxNIMRxAtABVqVhTwC/G2K4zDHf
In-Reply-To: <20161119183849.GD20446@dtor-ws>
On 19 November 2016 18:38:49 GMT+00:00, Dmitry Torokhov <dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
>On Sat, Nov 12, 2016 at 02:19:30PM +0000, Jonathan Cameron wrote:
>> On 12/11/16 14:04, Jonathan Cameron wrote:
>> > On 11/11/16 19:02, H. Nikolaus Schaller wrote:
>> >> The tsc2007 chip not only has a resistive touch screen controller
>but
>> >> also an external AUX adc imput which can be used for an ambient
>> >> light sensor, battery voltage monitoring or any general purpose.
>> >>
>> >> Additionally it can measure the chip temperature.
>> >>
>> >> This extension provides an iio interface for these adc channels.
>> >>
>> >> Since it is not wasting much resources and is very
>straightforward,
>> >> we simply provide all other adc channels as optional iio
>interfaces
>> >> as weel. This can be used for debugging or special applications.
>> >>
>> >> This patch also splits the tsc2007 driver in several source files:
>> >> tsc2007.h -- constants, structs and stubs
>> >> tsc2007_core.c -- functional parts of the original driver
>> >> tsc2007_iio.c -- the optional iio stuff
>> >>
>> >> Makefile magic allows to conditionally link the iio
>> >> stuff if CONFIG_IIO=y in a way that it works with
>> >> CONFIG_TOUCHSCREEN_TSC2007=m.
>> >>
>> >
>> > I ran a quick build test and it blows up in a number of ways
>related to
>> > the #ifdef CONFIG_IIO.
>> >
>> > I'm not entirely sure why but
>> > #if IS_ENABLED(CONFIG_IIO) works fine.
>> Ah, I'm being sleepy today. It's because I'm building IIO as a
>module
>> and the symbol defined would therefore be CONFIG_IIO_MODULE.
>>
>> The IS_ENABLED macro takes care of both cases.
>
>No it doesn't. Have you tried building the driver into the kernel with
>IIO as a module?
Fair point. Is there a clean way of handling this?
>
>> >
>> > Otherwise looks good to me
>> >> Signed-off-by: H. Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
>> > Reviewed-by: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
>> >> ---
>> >> drivers/input/touchscreen/Makefile | 2 +
>> >> drivers/input/touchscreen/tsc2007.h | 118
>++++++++++++++++
>> >> .../touchscreen/{tsc2007.c => tsc2007_core.c} | 119
>++++++----------
>> >> drivers/input/touchscreen/tsc2007_iio.c | 152
>+++++++++++++++++++++
>> >> 4 files changed, 310 insertions(+), 81 deletions(-)
>> >> create mode 100644 drivers/input/touchscreen/tsc2007.h
>> >> rename drivers/input/touchscreen/{tsc2007.c => tsc2007_core.c}
>(85%)
>> >> create mode 100644 drivers/input/touchscreen/tsc2007_iio.c
>> >>
>> >> diff --git a/drivers/input/touchscreen/Makefile
>b/drivers/input/touchscreen/Makefile
>> >> index 81b8645..d932e2d 100644
>> >> --- a/drivers/input/touchscreen/Makefile
>> >> +++ b/drivers/input/touchscreen/Makefile
>> >> @@ -80,6 +80,8 @@ obj-$(CONFIG_TOUCHSCREEN_TSC_SERIO) += tsc40.o
>> >> obj-$(CONFIG_TOUCHSCREEN_TSC200X_CORE) += tsc200x-core.o
>> >> obj-$(CONFIG_TOUCHSCREEN_TSC2004) += tsc2004.o
>> >> obj-$(CONFIG_TOUCHSCREEN_TSC2005) += tsc2005.o
>> >> +tsc2007-y := tsc2007_core.o
>> >> +tsc2007-$(CONFIG_IIO) += tsc2007_iio.o
>> >> obj-$(CONFIG_TOUCHSCREEN_TSC2007) += tsc2007.o
>> >> obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o
>> >> obj-$(CONFIG_TOUCHSCREEN_WACOM_W8001) += wacom_w8001.o
>> >> diff --git a/drivers/input/touchscreen/tsc2007.h
>b/drivers/input/touchscreen/tsc2007.h
>> >> new file mode 100644
>> >> index 0000000..5049bf8
>> >> --- /dev/null
>> >> +++ b/drivers/input/touchscreen/tsc2007.h
>> >> @@ -0,0 +1,118 @@
>> >> +/*
>> >> + * drivers/input/touchscreen/tsc2007.h
>> >> + *
>> >> + * Copyright (c) 2008 MtekVision Co., Ltd.
>> >> + * Kwangwoo Lee <kwlee-ec7hoAtq5SbSUeElwK9/Pw@public.gmane.org>
>> >> + *
>> >> + * Using code from:
>> >> + * - ads7846.c
>> >> + * Copyright (c) 2005 David Brownell
>> >> + * Copyright (c) 2006 Nokia Corporation
>> >> + * - corgi_ts.c
>> >> + * Copyright (C) 2004-2005 Richard Purdie
>> >> + * - omap_ts.[hc], ads7846.h, ts_osk.c
>> >> + * Copyright (C) 2002 MontaVista Software
>> >> + * Copyright (C) 2004 Texas Instruments
>> >> + * Copyright (C) 2005 Dirk Behme
>> >> + *
>> >> + * 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.
>> >> + */
>> >> +
>> >> +#include <linux/input/touchscreen.h>
>> >> +
>> >> +#define TSC2007_MEASURE_TEMP0 (0x0 << 4)
>> >> +#define TSC2007_MEASURE_AUX (0x2 << 4)
>> >> +#define TSC2007_MEASURE_TEMP1 (0x4 << 4)
>> >> +#define TSC2007_ACTIVATE_XN (0x8 << 4)
>> >> +#define TSC2007_ACTIVATE_YN (0x9 << 4)
>> >> +#define TSC2007_ACTIVATE_YP_XN (0xa << 4)
>> >> +#define TSC2007_SETUP (0xb << 4)
>> >> +#define TSC2007_MEASURE_X (0xc << 4)
>> >> +#define TSC2007_MEASURE_Y (0xd << 4)
>> >> +#define TSC2007_MEASURE_Z1 (0xe << 4)
>> >> +#define TSC2007_MEASURE_Z2 (0xf << 4)
>> >> +
>> >> +#define TSC2007_POWER_OFF_IRQ_EN (0x0 << 2)
>> >> +#define TSC2007_ADC_ON_IRQ_DIS0 (0x1 << 2)
>> >> +#define TSC2007_ADC_OFF_IRQ_EN (0x2 << 2)
>> >> +#define TSC2007_ADC_ON_IRQ_DIS1 (0x3 << 2)
>> >> +
>> >> +#define TSC2007_12BIT (0x0 << 1)
>> >> +#define TSC2007_8BIT (0x1 << 1)
>> >> +
>> >> +#define MAX_12BIT ((1 << 12) - 1)
>> >> +
>> >> +#define ADC_ON_12BIT (TSC2007_12BIT | TSC2007_ADC_ON_IRQ_DIS0)
>> >> +
>> >> +#define READ_Y (ADC_ON_12BIT | TSC2007_MEASURE_Y)
>> >> +#define READ_Z1 (ADC_ON_12BIT | TSC2007_MEASURE_Z1)
>> >> +#define READ_Z2 (ADC_ON_12BIT | TSC2007_MEASURE_Z2)
>> >> +#define READ_X (ADC_ON_12BIT | TSC2007_MEASURE_X)
>> >> +#define PWRDOWN (TSC2007_12BIT | TSC2007_POWER_OFF_IRQ_EN)
>> >> +
>> >> +struct ts_event {
>> >> + u16 x;
>> >> + u16 y;
>> >> + u16 z1, z2;
>> >> +};
>> >> +
>> >> +struct tsc2007 {
>> >> + struct input_dev *input;
>> >> + char phys[32];
>> >> +
>> >> + struct i2c_client *client;
>> >> +
>> >> + u16 model;
>> >> + u16 x_plate_ohms;
>> >> +
>> >> + struct touchscreen_properties prop;
>> >> +
>> >> + bool report_resistance;
>> >> + u16 min_x;
>> >> + u16 min_y;
>> >> + u16 max_x;
>> >> + u16 max_y;
>> >> + u16 max_rt;
>> >> + unsigned long poll_period; /* in jiffies */
>> >> + int fuzzx;
>> >> + int fuzzy;
>> >> + int fuzzz;
>> >> +
>> >> + unsigned int gpio;
>> >> + int irq;
>> >> +
>> >> + wait_queue_head_t wait;
>> >> + bool stopped;
>> >> + bool pendown;
>> >> +
>> >> + int (*get_pendown_state)(struct device *);
>> >> + void (*clear_penirq)(void);
>> >> +
>> >> + struct mutex mlock;
>> >> + struct iio_dev *iio_dev; /* optional */
>> >> +};
>> >> +
>> >> +int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd);
>> >> +u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
>> >> + struct ts_event *tc);
>> >> +bool tsc2007_is_pen_down(struct tsc2007 *ts);
>> >> +
>> >> +#ifdef CONFIG_IIO
>> >> +
>> >> +/* defined in tsc2007_iio.c */
>> >> +int tsc2007_iio_configure(struct tsc2007 *ts);
>> >> +void tsc2007_iio_unconfigure(struct tsc2007 *ts);
>> >> +
>> >> +#else /* CONFIG_IIO */
>> >> +
>> >> +static inline int tsc2007_iio_configure(struct tsc2007 *ts)
>> >> +{
>> >> + return 0;
>> >> +}
>> >> +static inline void tsc2007_iio_unconfigure(struct tsc2007 *ts)
>> >> +{
>> >> +}
>> >> +
>> >> +#endif /* CONFIG_IIO */
>> >> diff --git a/drivers/input/touchscreen/tsc2007.c
>b/drivers/input/touchscreen/tsc2007_core.c
>> >> similarity index 85%
>> >> rename from drivers/input/touchscreen/tsc2007.c
>> >> rename to drivers/input/touchscreen/tsc2007_core.c
>> >> index 5e3c4bf..72775ae 100644
>> >> --- a/drivers/input/touchscreen/tsc2007.c
>> >> +++ b/drivers/input/touchscreen/tsc2007_core.c
>> >> @@ -27,80 +27,10 @@
>> >> #include <linux/i2c.h>
>> >> #include <linux/i2c/tsc2007.h>
>> >> #include <linux/of_device.h>
>> >> -#include <linux/of.h>
>> >> #include <linux/of_gpio.h>
>> >> -#include <linux/input/touchscreen.h>
>> >> -
>> >> -#define TSC2007_MEASURE_TEMP0 (0x0 << 4)
>> >> -#define TSC2007_MEASURE_AUX (0x2 << 4)
>> >> -#define TSC2007_MEASURE_TEMP1 (0x4 << 4)
>> >> -#define TSC2007_ACTIVATE_XN (0x8 << 4)
>> >> -#define TSC2007_ACTIVATE_YN (0x9 << 4)
>> >> -#define TSC2007_ACTIVATE_YP_XN (0xa << 4)
>> >> -#define TSC2007_SETUP (0xb << 4)
>> >> -#define TSC2007_MEASURE_X (0xc << 4)
>> >> -#define TSC2007_MEASURE_Y (0xd << 4)
>> >> -#define TSC2007_MEASURE_Z1 (0xe << 4)
>> >> -#define TSC2007_MEASURE_Z2 (0xf << 4)
>> >> -
>> >> -#define TSC2007_POWER_OFF_IRQ_EN (0x0 << 2)
>> >> -#define TSC2007_ADC_ON_IRQ_DIS0 (0x1 << 2)
>> >> -#define TSC2007_ADC_OFF_IRQ_EN (0x2 << 2)
>> >> -#define TSC2007_ADC_ON_IRQ_DIS1 (0x3 << 2)
>> >> -
>> >> -#define TSC2007_12BIT (0x0 << 1)
>> >> -#define TSC2007_8BIT (0x1 << 1)
>> >> -
>> >> -#define MAX_12BIT ((1 << 12) - 1)
>> >> -
>> >> -#define ADC_ON_12BIT (TSC2007_12BIT | TSC2007_ADC_ON_IRQ_DIS0)
>> >> -
>> >> -#define READ_Y (ADC_ON_12BIT | TSC2007_MEASURE_Y)
>> >> -#define READ_Z1 (ADC_ON_12BIT | TSC2007_MEASURE_Z1)
>> >> -#define READ_Z2 (ADC_ON_12BIT | TSC2007_MEASURE_Z2)
>> >> -#define READ_X (ADC_ON_12BIT | TSC2007_MEASURE_X)
>> >> -#define PWRDOWN (TSC2007_12BIT | TSC2007_POWER_OFF_IRQ_EN)
>> >> -
>> >> -struct ts_event {
>> >> - u16 x;
>> >> - u16 y;
>> >> - u16 z1, z2;
>> >> -};
>> >> -
>> >> -struct tsc2007 {
>> >> - struct input_dev *input;
>> >> - char phys[32];
>> >> -
>> >> - struct i2c_client *client;
>> >> -
>> >> - u16 model;
>> >> - u16 x_plate_ohms;
>> >> -
>> >> - struct touchscreen_properties prop;
>> >> -
>> >> - bool report_resistance;
>> >> - u16 min_x;
>> >> - u16 min_y;
>> >> - u16 max_x;
>> >> - u16 max_y;
>> >> - u16 max_rt;
>> >> - unsigned long poll_period; /* in jiffies */
>> >> - int fuzzx;
>> >> - int fuzzy;
>> >> - int fuzzz;
>> >> +#include "tsc2007.h"
>> >>
>> >> - unsigned gpio;
>> >> - int irq;
>> >> -
>> >> - wait_queue_head_t wait;
>> >> - bool stopped;
>> >> - bool pendown;
>> >> -
>> >> - int (*get_pendown_state)(struct device *);
>> >> - void (*clear_penirq)(void);
>> >> -};
>> >> -
>> >> -static inline int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd)
>> >> +int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd)
>> >> {
>> >> s32 data;
>> >> u16 val;
>> >> @@ -121,6 +51,7 @@ static inline int tsc2007_xfer(struct tsc2007
>*tsc, u8 cmd)
>> >>
>> >> return val;
>> >> }
>> >> +EXPORT_SYMBOL(tsc2007_xfer);
>> >>
>> >> static void tsc2007_read_values(struct tsc2007 *tsc, struct
>ts_event *tc)
>> >> {
>> >> @@ -138,7 +69,7 @@ static void tsc2007_read_values(struct tsc2007
>*tsc, struct ts_event *tc)
>> >> tsc2007_xfer(tsc, PWRDOWN);
>> >> }
>> >>
>> >> -static u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
>> >> +u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
>> >> struct ts_event *tc)
>> >> {
>> >> u32 rt = 0;
>> >> @@ -158,8 +89,9 @@ static u32 tsc2007_calculate_resistance(struct
>tsc2007 *tsc,
>> >>
>> >> return rt;
>> >> }
>> >> +EXPORT_SYMBOL(tsc2007_calculate_resistance);
>> >>
>> >> -static bool tsc2007_is_pen_down(struct tsc2007 *ts)
>> >> +bool tsc2007_is_pen_down(struct tsc2007 *ts)
>> >> {
>> >> /*
>> >> * NOTE: We can't rely on the pressure to determine the pen down
>> >> @@ -180,6 +112,7 @@ static bool tsc2007_is_pen_down(struct tsc2007
>*ts)
>> >>
>> >> return ts->get_pendown_state(&ts->client->dev);
>> >> }
>> >> +EXPORT_SYMBOL(tsc2007_is_pen_down);
>> >>
>> >> static irqreturn_t tsc2007_soft_irq(int irq, void *handle)
>> >> {
>> >> @@ -192,7 +125,10 @@ static irqreturn_t tsc2007_soft_irq(int irq,
>void *handle)
>> >> while (!ts->stopped && tsc2007_is_pen_down(ts)) {
>> >>
>> >> /* pen is down, continue with the measurement */
>> >> +
>> >> + mutex_lock(&ts->mlock);
>> >> tsc2007_read_values(ts, &tc);
>> >> + mutex_unlock(&ts->mlock);
>> >>
>> >> rt = tsc2007_calculate_resistance(ts, &tc);
>> >>
>> >> @@ -450,7 +386,8 @@ static void tsc2007_call_exit_platform_hw(void
>*data)
>> >> static int tsc2007_probe(struct i2c_client *client,
>> >> const struct i2c_device_id *id)
>> >> {
>> >> - const struct tsc2007_platform_data *pdata =
>dev_get_platdata(&client->dev);
>> >> + const struct tsc2007_platform_data *pdata =
>> >> + dev_get_platdata(&client->dev);
>> >> struct tsc2007 *ts;
>> >> struct input_dev *input_dev;
>> >> int err;
>> >> @@ -472,7 +409,13 @@ static int tsc2007_probe(struct i2c_client
>*client,
>> >> ts->client = client;
>> >> ts->irq = client->irq;
>> >> ts->input = input_dev;
>> >> +
>> >> + err = tsc2007_iio_configure(ts);
>> >> + if (err < 0)
>> >> + return err;
>> >> +
>> >> init_waitqueue_head(&ts->wait);
>> >> + mutex_init(&ts->mlock);
>> >>
>> >> snprintf(ts->phys, sizeof(ts->phys),
>> >> "%s/input0", dev_name(&client->dev));
>> >> @@ -494,7 +437,7 @@ static int tsc2007_probe(struct i2c_client
>*client,
>> >> if (pdata) {
>> >> err = tsc2007_probe_pdev(client, ts, pdata, id);
>> >> if (err)
>> >> - return err;
>> >> + goto probe_err;
>> >> input_set_abs_params(input_dev, ABS_X, 0, ts->max_x-ts->min_x,
>> >> ts->fuzzx, 0);
>> >> input_set_abs_params(input_dev, ABS_Y, 0, ts->max_y-ts->min_y,
>> >> @@ -504,7 +447,7 @@ static int tsc2007_probe(struct i2c_client
>*client,
>> >> } else {
>> >> err = tsc2007_probe_dt(client, ts);
>> >> if (err)
>> >> - return err;
>> >> + goto probe_err;
>> >> }
>> >>
>> >> if (pdata) {
>> >> @@ -516,7 +459,7 @@ static int tsc2007_probe(struct i2c_client
>*client,
>> >> dev_err(&client->dev,
>> >> "Failed to register exit_platform_hw action, %d\n",
>> >> err);
>> >> - return err;
>> >> + goto probe_err;
>> >> }
>> >> }
>> >>
>> >> @@ -533,7 +476,7 @@ static int tsc2007_probe(struct i2c_client
>*client,
>> >> if (err) {
>> >> dev_err(&client->dev, "Failed to request irq %d: %d\n",
>> >> ts->irq, err);
>> >> - return err;
>> >> + goto probe_err;
>> >> }
>> >>
>> >> tsc2007_stop(ts);
>> >> @@ -543,17 +486,30 @@ static int tsc2007_probe(struct i2c_client
>*client,
>> >> if (err < 0) {
>> >> dev_err(&client->dev,
>> >> "Failed to setup chip: %d\n", err);
>> >> - return err; /* usually, chip does not respond */
>> >> + goto probe_err; /* chip does not respond */
>> >> }
>> >>
>> >> err = input_register_device(input_dev);
>> >> if (err) {
>> >> dev_err(&client->dev,
>> >> "Failed to register input device: %d\n", err);
>> >> - return err;
>> >> + goto probe_err;
>> >> }
>> >>
>> >> return 0;
>> >> +
>> >> +probe_err:
>> >> + tsc2007_iio_unconfigure(ts);
>> >> + return err;
>> >> +}
>> >> +
>> >> +static int tsc2007_remove(struct i2c_client *client)
>> >> +{
>> >> + struct tsc2007 *ts = i2c_get_clientdata(client);
>> >> +
>> >> + tsc2007_iio_unconfigure(ts);
>> >> + input_unregister_device(ts->input);
>> >> + return 0;
>> >> }
>> >>
>> >> static const struct i2c_device_id tsc2007_idtable[] = {
>> >> @@ -578,6 +534,7 @@ static struct i2c_driver tsc2007_driver = {
>> >> },
>> >> .id_table = tsc2007_idtable,
>> >> .probe = tsc2007_probe,
>> >> + .remove = tsc2007_remove,
>> >> };
>> >>
>> >> module_i2c_driver(tsc2007_driver);
>> >> diff --git a/drivers/input/touchscreen/tsc2007_iio.c
>b/drivers/input/touchscreen/tsc2007_iio.c
>> >> new file mode 100644
>> >> index 0000000..e492bba
>> >> --- /dev/null
>> >> +++ b/drivers/input/touchscreen/tsc2007_iio.c
>> >> @@ -0,0 +1,152 @@
>> >> +/*
>> >> + * drivers/input/touchscreen/tsc2007_iio.c
>> >> + *
>> >> + * Copyright (c) 2016 Golden Delicious Comp. GmbH&Co. KG
>> >> + * Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
>> >> + *
>> >> + * 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.
>> >> + */
>> >> +
>> >> +#include <linux/i2c.h>
>> >> +#include <linux/iio/iio.h>
>> >> +#include "tsc2007.h"
>> >> +
>> >> +struct tsc2007_iio {
>> >> + struct tsc2007 *ts;
>> >> +};
>> >> +
>> >> +#define TSC2007_CHAN_IIO(_chan, _name, _type, _chan_info) \
>> >> +{ \
>> >> + .datasheet_name = _name, \
>> >> + .type = _type, \
>> >> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
>> >> + BIT(_chan_info), \
>> >> + .indexed = 1, \
>> >> + .channel = _chan, \
>> >> +}
>> >> +
>> >> +static const struct iio_chan_spec tsc2007_iio_channel[] = {
>> >> + TSC2007_CHAN_IIO(0, "x", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
>> >> + TSC2007_CHAN_IIO(1, "y", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
>> >> + TSC2007_CHAN_IIO(2, "z1", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
>> >> + TSC2007_CHAN_IIO(3, "z2", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
>> >> + TSC2007_CHAN_IIO(4, "adc", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
>> >> + TSC2007_CHAN_IIO(5, "rt", IIO_VOLTAGE, IIO_CHAN_INFO_RAW), /*
>Ohms? */
>> >> + TSC2007_CHAN_IIO(6, "pen", IIO_PRESSURE, IIO_CHAN_INFO_RAW),
>> >> + TSC2007_CHAN_IIO(7, "temp0", IIO_TEMP, IIO_CHAN_INFO_RAW),
>> >> + TSC2007_CHAN_IIO(8, "temp1", IIO_TEMP, IIO_CHAN_INFO_RAW),
>> >> +};
>> >> +
>> >> +static int tsc2007_read_raw(struct iio_dev *indio_dev,
>> >> + struct iio_chan_spec const *chan, int *val, int *val2, long
>mask)
>> >> +{
>> >> + struct tsc2007_iio *iio = iio_priv(indio_dev);
>> >> + struct tsc2007 *tsc = iio->ts;
>> >> + int adc_chan = chan->channel;
>> >> + int ret = 0;
>> >> +
>> >> + if (adc_chan >= ARRAY_SIZE(tsc2007_iio_channel))
>> >> + return -EINVAL;
>> >> +
>> >> + if (mask != IIO_CHAN_INFO_RAW)
>> >> + return -EINVAL;
>> >> +
>> >> + mutex_lock(&tsc->mlock);
>> >> +
>> >> + switch (chan->channel) {
>> >> + case 0:
>> >> + *val = tsc2007_xfer(tsc, READ_X);
>> >> + break;
>> >> + case 1:
>> >> + *val = tsc2007_xfer(tsc, READ_Y);
>> >> + break;
>> >> + case 2:
>> >> + *val = tsc2007_xfer(tsc, READ_Z1);
>> >> + break;
>> >> + case 3:
>> >> + *val = tsc2007_xfer(tsc, READ_Z2);
>> >> + break;
>> >> + case 4:
>> >> + *val = tsc2007_xfer(tsc, (ADC_ON_12BIT | TSC2007_MEASURE_AUX));
>> >> + break;
>> >> + case 5: {
>> >> + struct ts_event tc;
>> >> +
>> >> + tc.x = tsc2007_xfer(tsc, READ_X);
>> >> + tc.z1 = tsc2007_xfer(tsc, READ_Z1);
>> >> + tc.z2 = tsc2007_xfer(tsc, READ_Z2);
>> >> + *val = tsc2007_calculate_resistance(tsc, &tc);
>> >> + break;
>> >> + }
>> >> + case 6:
>> >> + *val = tsc2007_is_pen_down(tsc);
>> >> + break;
>> >> + case 7:
>> >> + *val = tsc2007_xfer(tsc,
>> >> + (ADC_ON_12BIT | TSC2007_MEASURE_TEMP0));
>> >> + break;
>> >> + case 8:
>> >> + *val = tsc2007_xfer(tsc,
>> >> + (ADC_ON_12BIT | TSC2007_MEASURE_TEMP1));
>> >> + break;
>> >> + }
>> >> +
>> >> + /* Prepare for next touch reading - power down ADC, enable
>PENIRQ */
>> >> + tsc2007_xfer(tsc, PWRDOWN);
>> >> +
>> >> + mutex_unlock(&tsc->mlock);
>> >> +
>> >> + ret = IIO_VAL_INT;
>> >> +
>> >> + return ret;
>> >> +}
>> >> +
>> >> +static const struct iio_info tsc2007_iio_info = {
>> >> + .read_raw = tsc2007_read_raw,
>> >> + .driver_module = THIS_MODULE,
>> >> +};
>> >> +
>> >> +int tsc2007_iio_configure(struct tsc2007 *ts)
>> >> +{
>> >> + int err;
>> >> + struct iio_dev *indio_dev;
>> >> + struct tsc2007_iio *iio;
>> >> +
>> >> + indio_dev = devm_iio_device_alloc(&ts->client->dev,
>> >> + sizeof(struct tsc2007_iio));
>> >> + if (!indio_dev) {
>> >> + dev_err(&ts->client->dev, "iio_device_alloc failed\n");
>> >> + return -ENOMEM;
>> >> + }
>> >> +
>> >> + iio = iio_priv(indio_dev);
>> >> + iio->ts = ts;
>> >> + ts->iio_dev = (void *) indio_dev;
>> >> +
>> >> + indio_dev->name = "tsc2007";
>> >> + indio_dev->dev.parent = &ts->client->dev;
>> >> + indio_dev->info = &tsc2007_iio_info;
>> >> + indio_dev->modes = INDIO_DIRECT_MODE;
>> >> + indio_dev->channels = tsc2007_iio_channel;
>> >> + indio_dev->num_channels = ARRAY_SIZE(tsc2007_iio_channel);
>> >> +
>> >> + err = iio_device_register(indio_dev);
>> >> + if (err < 0) {
>> >> + dev_err(&ts->client->dev, "iio_device_register() failed: %d\n",
>> >> + err);
>> >> + return err;
>> >> + }
>> >> +
>> >> + return 0;
>> >> +}
>> >> +EXPORT_SYMBOL(tsc2007_iio_configure);
>> >> +
>> >> +void tsc2007_iio_unconfigure(struct tsc2007 *ts)
>> >> +{
>> >> + struct iio_dev *indio_dev = ts->iio_dev;
>> >> +
>> >> + iio_device_unregister(indio_dev);
>> >> +}
>> >> +EXPORT_SYMBOL(tsc2007_iio_unconfigure);
>> >>
>> >
>> > --
>> > To unsubscribe from this list: send the line "unsubscribe
>linux-iio" in
>> > the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
>> > More majordomo info at http://vger.kernel.org/majordomo-info.html
>> >
>>
--
Sent from my Android device with K-9 Mail. Please excuse my brevity.
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH RFC] ARM: dts: add support for Turris Omnia
From: tomas.hlavacek-x+rMaJPWets @ 2016-11-19 20:09 UTC (permalink / raw)
To: Andrew Lunn
Cc: Uwe Kleine-König, Mark Rutland, marex-ynQEQJNshbs,
Jason Cooper, Martin Strba??ka, devicetree-u79uwXL29TY76Z2rM5mHXA,
Rob Herring, Gregory Clement,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
Sebastian Hesselbarth
In-Reply-To: <20161114202832.GG24546-g2DYL2Zd6BY@public.gmane.org>
Hello Uwe!
On Mon, Nov 14, 2016 at 9:28 PM, Andrew Lunn <andrew-g2DYL2Zd6BY@public.gmane.org> wrote:
>>
>> + i2c@7 {
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> + reg = <7>;
>> +
>> + pcawan: gpio@71 {
>> + compatible = "nxp,pca9538";
>> + reg = <0x71>;
>> +
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&pcawan_pins>;
>> +
>> + interrupt-parent = <&gpio1>;
>> + interrupts = <14
>> IRQ_TYPE_LEVEL_LOW>;
>> +
>> + gpio-controller;
>> + #gpio-cells = <2>;
>> +
>> + interrupt-controller;
>> + #interrupt-cells = <2>;
>> + };
>> + };
>>
>> The interrupt-controller part doesn't seem to work though, at least
>>
>> + interrupt-parent = <&pcawan>;
>> + interrupts = <7 IRQ_TYPE_LEVEL_LOW>;
>>
>> in the phy node gives an error.
>
> Interrupts don't seem to work very well with the nxp,pca9538. Which
> is probably why it is disabled by default.
I was thinking about this issue and I can remember that there was an
earlier prototype that had a shared interrupt line from PHY (88E1514)
and from the PCA9538. In this case we needed to specifically disable
the interrupt of the PHY to release the interrupt line (which needed a
hack into PHY driver code). The IRQ from PHY is connected as an
ordinary input to PCA9538 in later board prototype. And the same holds
for the production version.
Do you have CZ11NIC13 or older board revision?
Tomas
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH v7 4/9] drivers:input:tsc2007: add iio interface to read external ADC input and temperature
From: Dmitry Torokhov @ 2016-11-19 18:38 UTC (permalink / raw)
To: Jonathan Cameron
Cc: H. Nikolaus Schaller, Sebastian Reichel, Mark Rutland,
Benoît Cousson, Tony Lindgren, Russell King, Arnd Bergmann,
Michael Welling, Mika Penttilä, Javier Martinez Canillas,
Igor Grinberg, Andrew F. Davis, Mark Brown, linux-input,
devicetree, linux-kernel, linux-omap, letux-kernel, linux-iio,
kernel
In-Reply-To: <b4d79a26-4353-9860-a884-49c751bbe617@kernel.org>
On Sat, Nov 12, 2016 at 02:19:30PM +0000, Jonathan Cameron wrote:
> On 12/11/16 14:04, Jonathan Cameron wrote:
> > On 11/11/16 19:02, H. Nikolaus Schaller wrote:
> >> The tsc2007 chip not only has a resistive touch screen controller but
> >> also an external AUX adc imput which can be used for an ambient
> >> light sensor, battery voltage monitoring or any general purpose.
> >>
> >> Additionally it can measure the chip temperature.
> >>
> >> This extension provides an iio interface for these adc channels.
> >>
> >> Since it is not wasting much resources and is very straightforward,
> >> we simply provide all other adc channels as optional iio interfaces
> >> as weel. This can be used for debugging or special applications.
> >>
> >> This patch also splits the tsc2007 driver in several source files:
> >> tsc2007.h -- constants, structs and stubs
> >> tsc2007_core.c -- functional parts of the original driver
> >> tsc2007_iio.c -- the optional iio stuff
> >>
> >> Makefile magic allows to conditionally link the iio
> >> stuff if CONFIG_IIO=y in a way that it works with
> >> CONFIG_TOUCHSCREEN_TSC2007=m.
> >>
> >
> > I ran a quick build test and it blows up in a number of ways related to
> > the #ifdef CONFIG_IIO.
> >
> > I'm not entirely sure why but
> > #if IS_ENABLED(CONFIG_IIO) works fine.
> Ah, I'm being sleepy today. It's because I'm building IIO as a module
> and the symbol defined would therefore be CONFIG_IIO_MODULE.
>
> The IS_ENABLED macro takes care of both cases.
No it doesn't. Have you tried building the driver into the kernel with
IIO as a module?
> >
> > Otherwise looks good to me
> >> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
> > Reviewed-by: Jonathan Cameron <jic23@kernel.org>
> >> ---
> >> drivers/input/touchscreen/Makefile | 2 +
> >> drivers/input/touchscreen/tsc2007.h | 118 ++++++++++++++++
> >> .../touchscreen/{tsc2007.c => tsc2007_core.c} | 119 ++++++----------
> >> drivers/input/touchscreen/tsc2007_iio.c | 152 +++++++++++++++++++++
> >> 4 files changed, 310 insertions(+), 81 deletions(-)
> >> create mode 100644 drivers/input/touchscreen/tsc2007.h
> >> rename drivers/input/touchscreen/{tsc2007.c => tsc2007_core.c} (85%)
> >> create mode 100644 drivers/input/touchscreen/tsc2007_iio.c
> >>
> >> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> >> index 81b8645..d932e2d 100644
> >> --- a/drivers/input/touchscreen/Makefile
> >> +++ b/drivers/input/touchscreen/Makefile
> >> @@ -80,6 +80,8 @@ obj-$(CONFIG_TOUCHSCREEN_TSC_SERIO) += tsc40.o
> >> obj-$(CONFIG_TOUCHSCREEN_TSC200X_CORE) += tsc200x-core.o
> >> obj-$(CONFIG_TOUCHSCREEN_TSC2004) += tsc2004.o
> >> obj-$(CONFIG_TOUCHSCREEN_TSC2005) += tsc2005.o
> >> +tsc2007-y := tsc2007_core.o
> >> +tsc2007-$(CONFIG_IIO) += tsc2007_iio.o
> >> obj-$(CONFIG_TOUCHSCREEN_TSC2007) += tsc2007.o
> >> obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o
> >> obj-$(CONFIG_TOUCHSCREEN_WACOM_W8001) += wacom_w8001.o
> >> diff --git a/drivers/input/touchscreen/tsc2007.h b/drivers/input/touchscreen/tsc2007.h
> >> new file mode 100644
> >> index 0000000..5049bf8
> >> --- /dev/null
> >> +++ b/drivers/input/touchscreen/tsc2007.h
> >> @@ -0,0 +1,118 @@
> >> +/*
> >> + * drivers/input/touchscreen/tsc2007.h
> >> + *
> >> + * Copyright (c) 2008 MtekVision Co., Ltd.
> >> + * Kwangwoo Lee <kwlee@mtekvision.com>
> >> + *
> >> + * Using code from:
> >> + * - ads7846.c
> >> + * Copyright (c) 2005 David Brownell
> >> + * Copyright (c) 2006 Nokia Corporation
> >> + * - corgi_ts.c
> >> + * Copyright (C) 2004-2005 Richard Purdie
> >> + * - omap_ts.[hc], ads7846.h, ts_osk.c
> >> + * Copyright (C) 2002 MontaVista Software
> >> + * Copyright (C) 2004 Texas Instruments
> >> + * Copyright (C) 2005 Dirk Behme
> >> + *
> >> + * 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.
> >> + */
> >> +
> >> +#include <linux/input/touchscreen.h>
> >> +
> >> +#define TSC2007_MEASURE_TEMP0 (0x0 << 4)
> >> +#define TSC2007_MEASURE_AUX (0x2 << 4)
> >> +#define TSC2007_MEASURE_TEMP1 (0x4 << 4)
> >> +#define TSC2007_ACTIVATE_XN (0x8 << 4)
> >> +#define TSC2007_ACTIVATE_YN (0x9 << 4)
> >> +#define TSC2007_ACTIVATE_YP_XN (0xa << 4)
> >> +#define TSC2007_SETUP (0xb << 4)
> >> +#define TSC2007_MEASURE_X (0xc << 4)
> >> +#define TSC2007_MEASURE_Y (0xd << 4)
> >> +#define TSC2007_MEASURE_Z1 (0xe << 4)
> >> +#define TSC2007_MEASURE_Z2 (0xf << 4)
> >> +
> >> +#define TSC2007_POWER_OFF_IRQ_EN (0x0 << 2)
> >> +#define TSC2007_ADC_ON_IRQ_DIS0 (0x1 << 2)
> >> +#define TSC2007_ADC_OFF_IRQ_EN (0x2 << 2)
> >> +#define TSC2007_ADC_ON_IRQ_DIS1 (0x3 << 2)
> >> +
> >> +#define TSC2007_12BIT (0x0 << 1)
> >> +#define TSC2007_8BIT (0x1 << 1)
> >> +
> >> +#define MAX_12BIT ((1 << 12) - 1)
> >> +
> >> +#define ADC_ON_12BIT (TSC2007_12BIT | TSC2007_ADC_ON_IRQ_DIS0)
> >> +
> >> +#define READ_Y (ADC_ON_12BIT | TSC2007_MEASURE_Y)
> >> +#define READ_Z1 (ADC_ON_12BIT | TSC2007_MEASURE_Z1)
> >> +#define READ_Z2 (ADC_ON_12BIT | TSC2007_MEASURE_Z2)
> >> +#define READ_X (ADC_ON_12BIT | TSC2007_MEASURE_X)
> >> +#define PWRDOWN (TSC2007_12BIT | TSC2007_POWER_OFF_IRQ_EN)
> >> +
> >> +struct ts_event {
> >> + u16 x;
> >> + u16 y;
> >> + u16 z1, z2;
> >> +};
> >> +
> >> +struct tsc2007 {
> >> + struct input_dev *input;
> >> + char phys[32];
> >> +
> >> + struct i2c_client *client;
> >> +
> >> + u16 model;
> >> + u16 x_plate_ohms;
> >> +
> >> + struct touchscreen_properties prop;
> >> +
> >> + bool report_resistance;
> >> + u16 min_x;
> >> + u16 min_y;
> >> + u16 max_x;
> >> + u16 max_y;
> >> + u16 max_rt;
> >> + unsigned long poll_period; /* in jiffies */
> >> + int fuzzx;
> >> + int fuzzy;
> >> + int fuzzz;
> >> +
> >> + unsigned int gpio;
> >> + int irq;
> >> +
> >> + wait_queue_head_t wait;
> >> + bool stopped;
> >> + bool pendown;
> >> +
> >> + int (*get_pendown_state)(struct device *);
> >> + void (*clear_penirq)(void);
> >> +
> >> + struct mutex mlock;
> >> + struct iio_dev *iio_dev; /* optional */
> >> +};
> >> +
> >> +int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd);
> >> +u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
> >> + struct ts_event *tc);
> >> +bool tsc2007_is_pen_down(struct tsc2007 *ts);
> >> +
> >> +#ifdef CONFIG_IIO
> >> +
> >> +/* defined in tsc2007_iio.c */
> >> +int tsc2007_iio_configure(struct tsc2007 *ts);
> >> +void tsc2007_iio_unconfigure(struct tsc2007 *ts);
> >> +
> >> +#else /* CONFIG_IIO */
> >> +
> >> +static inline int tsc2007_iio_configure(struct tsc2007 *ts)
> >> +{
> >> + return 0;
> >> +}
> >> +static inline void tsc2007_iio_unconfigure(struct tsc2007 *ts)
> >> +{
> >> +}
> >> +
> >> +#endif /* CONFIG_IIO */
> >> diff --git a/drivers/input/touchscreen/tsc2007.c b/drivers/input/touchscreen/tsc2007_core.c
> >> similarity index 85%
> >> rename from drivers/input/touchscreen/tsc2007.c
> >> rename to drivers/input/touchscreen/tsc2007_core.c
> >> index 5e3c4bf..72775ae 100644
> >> --- a/drivers/input/touchscreen/tsc2007.c
> >> +++ b/drivers/input/touchscreen/tsc2007_core.c
> >> @@ -27,80 +27,10 @@
> >> #include <linux/i2c.h>
> >> #include <linux/i2c/tsc2007.h>
> >> #include <linux/of_device.h>
> >> -#include <linux/of.h>
> >> #include <linux/of_gpio.h>
> >> -#include <linux/input/touchscreen.h>
> >> -
> >> -#define TSC2007_MEASURE_TEMP0 (0x0 << 4)
> >> -#define TSC2007_MEASURE_AUX (0x2 << 4)
> >> -#define TSC2007_MEASURE_TEMP1 (0x4 << 4)
> >> -#define TSC2007_ACTIVATE_XN (0x8 << 4)
> >> -#define TSC2007_ACTIVATE_YN (0x9 << 4)
> >> -#define TSC2007_ACTIVATE_YP_XN (0xa << 4)
> >> -#define TSC2007_SETUP (0xb << 4)
> >> -#define TSC2007_MEASURE_X (0xc << 4)
> >> -#define TSC2007_MEASURE_Y (0xd << 4)
> >> -#define TSC2007_MEASURE_Z1 (0xe << 4)
> >> -#define TSC2007_MEASURE_Z2 (0xf << 4)
> >> -
> >> -#define TSC2007_POWER_OFF_IRQ_EN (0x0 << 2)
> >> -#define TSC2007_ADC_ON_IRQ_DIS0 (0x1 << 2)
> >> -#define TSC2007_ADC_OFF_IRQ_EN (0x2 << 2)
> >> -#define TSC2007_ADC_ON_IRQ_DIS1 (0x3 << 2)
> >> -
> >> -#define TSC2007_12BIT (0x0 << 1)
> >> -#define TSC2007_8BIT (0x1 << 1)
> >> -
> >> -#define MAX_12BIT ((1 << 12) - 1)
> >> -
> >> -#define ADC_ON_12BIT (TSC2007_12BIT | TSC2007_ADC_ON_IRQ_DIS0)
> >> -
> >> -#define READ_Y (ADC_ON_12BIT | TSC2007_MEASURE_Y)
> >> -#define READ_Z1 (ADC_ON_12BIT | TSC2007_MEASURE_Z1)
> >> -#define READ_Z2 (ADC_ON_12BIT | TSC2007_MEASURE_Z2)
> >> -#define READ_X (ADC_ON_12BIT | TSC2007_MEASURE_X)
> >> -#define PWRDOWN (TSC2007_12BIT | TSC2007_POWER_OFF_IRQ_EN)
> >> -
> >> -struct ts_event {
> >> - u16 x;
> >> - u16 y;
> >> - u16 z1, z2;
> >> -};
> >> -
> >> -struct tsc2007 {
> >> - struct input_dev *input;
> >> - char phys[32];
> >> -
> >> - struct i2c_client *client;
> >> -
> >> - u16 model;
> >> - u16 x_plate_ohms;
> >> -
> >> - struct touchscreen_properties prop;
> >> -
> >> - bool report_resistance;
> >> - u16 min_x;
> >> - u16 min_y;
> >> - u16 max_x;
> >> - u16 max_y;
> >> - u16 max_rt;
> >> - unsigned long poll_period; /* in jiffies */
> >> - int fuzzx;
> >> - int fuzzy;
> >> - int fuzzz;
> >> +#include "tsc2007.h"
> >>
> >> - unsigned gpio;
> >> - int irq;
> >> -
> >> - wait_queue_head_t wait;
> >> - bool stopped;
> >> - bool pendown;
> >> -
> >> - int (*get_pendown_state)(struct device *);
> >> - void (*clear_penirq)(void);
> >> -};
> >> -
> >> -static inline int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd)
> >> +int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd)
> >> {
> >> s32 data;
> >> u16 val;
> >> @@ -121,6 +51,7 @@ static inline int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd)
> >>
> >> return val;
> >> }
> >> +EXPORT_SYMBOL(tsc2007_xfer);
> >>
> >> static void tsc2007_read_values(struct tsc2007 *tsc, struct ts_event *tc)
> >> {
> >> @@ -138,7 +69,7 @@ static void tsc2007_read_values(struct tsc2007 *tsc, struct ts_event *tc)
> >> tsc2007_xfer(tsc, PWRDOWN);
> >> }
> >>
> >> -static u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
> >> +u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
> >> struct ts_event *tc)
> >> {
> >> u32 rt = 0;
> >> @@ -158,8 +89,9 @@ static u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
> >>
> >> return rt;
> >> }
> >> +EXPORT_SYMBOL(tsc2007_calculate_resistance);
> >>
> >> -static bool tsc2007_is_pen_down(struct tsc2007 *ts)
> >> +bool tsc2007_is_pen_down(struct tsc2007 *ts)
> >> {
> >> /*
> >> * NOTE: We can't rely on the pressure to determine the pen down
> >> @@ -180,6 +112,7 @@ static bool tsc2007_is_pen_down(struct tsc2007 *ts)
> >>
> >> return ts->get_pendown_state(&ts->client->dev);
> >> }
> >> +EXPORT_SYMBOL(tsc2007_is_pen_down);
> >>
> >> static irqreturn_t tsc2007_soft_irq(int irq, void *handle)
> >> {
> >> @@ -192,7 +125,10 @@ static irqreturn_t tsc2007_soft_irq(int irq, void *handle)
> >> while (!ts->stopped && tsc2007_is_pen_down(ts)) {
> >>
> >> /* pen is down, continue with the measurement */
> >> +
> >> + mutex_lock(&ts->mlock);
> >> tsc2007_read_values(ts, &tc);
> >> + mutex_unlock(&ts->mlock);
> >>
> >> rt = tsc2007_calculate_resistance(ts, &tc);
> >>
> >> @@ -450,7 +386,8 @@ static void tsc2007_call_exit_platform_hw(void *data)
> >> static int tsc2007_probe(struct i2c_client *client,
> >> const struct i2c_device_id *id)
> >> {
> >> - const struct tsc2007_platform_data *pdata = dev_get_platdata(&client->dev);
> >> + const struct tsc2007_platform_data *pdata =
> >> + dev_get_platdata(&client->dev);
> >> struct tsc2007 *ts;
> >> struct input_dev *input_dev;
> >> int err;
> >> @@ -472,7 +409,13 @@ static int tsc2007_probe(struct i2c_client *client,
> >> ts->client = client;
> >> ts->irq = client->irq;
> >> ts->input = input_dev;
> >> +
> >> + err = tsc2007_iio_configure(ts);
> >> + if (err < 0)
> >> + return err;
> >> +
> >> init_waitqueue_head(&ts->wait);
> >> + mutex_init(&ts->mlock);
> >>
> >> snprintf(ts->phys, sizeof(ts->phys),
> >> "%s/input0", dev_name(&client->dev));
> >> @@ -494,7 +437,7 @@ static int tsc2007_probe(struct i2c_client *client,
> >> if (pdata) {
> >> err = tsc2007_probe_pdev(client, ts, pdata, id);
> >> if (err)
> >> - return err;
> >> + goto probe_err;
> >> input_set_abs_params(input_dev, ABS_X, 0, ts->max_x-ts->min_x,
> >> ts->fuzzx, 0);
> >> input_set_abs_params(input_dev, ABS_Y, 0, ts->max_y-ts->min_y,
> >> @@ -504,7 +447,7 @@ static int tsc2007_probe(struct i2c_client *client,
> >> } else {
> >> err = tsc2007_probe_dt(client, ts);
> >> if (err)
> >> - return err;
> >> + goto probe_err;
> >> }
> >>
> >> if (pdata) {
> >> @@ -516,7 +459,7 @@ static int tsc2007_probe(struct i2c_client *client,
> >> dev_err(&client->dev,
> >> "Failed to register exit_platform_hw action, %d\n",
> >> err);
> >> - return err;
> >> + goto probe_err;
> >> }
> >> }
> >>
> >> @@ -533,7 +476,7 @@ static int tsc2007_probe(struct i2c_client *client,
> >> if (err) {
> >> dev_err(&client->dev, "Failed to request irq %d: %d\n",
> >> ts->irq, err);
> >> - return err;
> >> + goto probe_err;
> >> }
> >>
> >> tsc2007_stop(ts);
> >> @@ -543,17 +486,30 @@ static int tsc2007_probe(struct i2c_client *client,
> >> if (err < 0) {
> >> dev_err(&client->dev,
> >> "Failed to setup chip: %d\n", err);
> >> - return err; /* usually, chip does not respond */
> >> + goto probe_err; /* chip does not respond */
> >> }
> >>
> >> err = input_register_device(input_dev);
> >> if (err) {
> >> dev_err(&client->dev,
> >> "Failed to register input device: %d\n", err);
> >> - return err;
> >> + goto probe_err;
> >> }
> >>
> >> return 0;
> >> +
> >> +probe_err:
> >> + tsc2007_iio_unconfigure(ts);
> >> + return err;
> >> +}
> >> +
> >> +static int tsc2007_remove(struct i2c_client *client)
> >> +{
> >> + struct tsc2007 *ts = i2c_get_clientdata(client);
> >> +
> >> + tsc2007_iio_unconfigure(ts);
> >> + input_unregister_device(ts->input);
> >> + return 0;
> >> }
> >>
> >> static const struct i2c_device_id tsc2007_idtable[] = {
> >> @@ -578,6 +534,7 @@ static struct i2c_driver tsc2007_driver = {
> >> },
> >> .id_table = tsc2007_idtable,
> >> .probe = tsc2007_probe,
> >> + .remove = tsc2007_remove,
> >> };
> >>
> >> module_i2c_driver(tsc2007_driver);
> >> diff --git a/drivers/input/touchscreen/tsc2007_iio.c b/drivers/input/touchscreen/tsc2007_iio.c
> >> new file mode 100644
> >> index 0000000..e492bba
> >> --- /dev/null
> >> +++ b/drivers/input/touchscreen/tsc2007_iio.c
> >> @@ -0,0 +1,152 @@
> >> +/*
> >> + * drivers/input/touchscreen/tsc2007_iio.c
> >> + *
> >> + * Copyright (c) 2016 Golden Delicious Comp. GmbH&Co. KG
> >> + * Nikolaus Schaller <hns@goldelico.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.
> >> + */
> >> +
> >> +#include <linux/i2c.h>
> >> +#include <linux/iio/iio.h>
> >> +#include "tsc2007.h"
> >> +
> >> +struct tsc2007_iio {
> >> + struct tsc2007 *ts;
> >> +};
> >> +
> >> +#define TSC2007_CHAN_IIO(_chan, _name, _type, _chan_info) \
> >> +{ \
> >> + .datasheet_name = _name, \
> >> + .type = _type, \
> >> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
> >> + BIT(_chan_info), \
> >> + .indexed = 1, \
> >> + .channel = _chan, \
> >> +}
> >> +
> >> +static const struct iio_chan_spec tsc2007_iio_channel[] = {
> >> + TSC2007_CHAN_IIO(0, "x", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> >> + TSC2007_CHAN_IIO(1, "y", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> >> + TSC2007_CHAN_IIO(2, "z1", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> >> + TSC2007_CHAN_IIO(3, "z2", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> >> + TSC2007_CHAN_IIO(4, "adc", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> >> + TSC2007_CHAN_IIO(5, "rt", IIO_VOLTAGE, IIO_CHAN_INFO_RAW), /* Ohms? */
> >> + TSC2007_CHAN_IIO(6, "pen", IIO_PRESSURE, IIO_CHAN_INFO_RAW),
> >> + TSC2007_CHAN_IIO(7, "temp0", IIO_TEMP, IIO_CHAN_INFO_RAW),
> >> + TSC2007_CHAN_IIO(8, "temp1", IIO_TEMP, IIO_CHAN_INFO_RAW),
> >> +};
> >> +
> >> +static int tsc2007_read_raw(struct iio_dev *indio_dev,
> >> + struct iio_chan_spec const *chan, int *val, int *val2, long mask)
> >> +{
> >> + struct tsc2007_iio *iio = iio_priv(indio_dev);
> >> + struct tsc2007 *tsc = iio->ts;
> >> + int adc_chan = chan->channel;
> >> + int ret = 0;
> >> +
> >> + if (adc_chan >= ARRAY_SIZE(tsc2007_iio_channel))
> >> + return -EINVAL;
> >> +
> >> + if (mask != IIO_CHAN_INFO_RAW)
> >> + return -EINVAL;
> >> +
> >> + mutex_lock(&tsc->mlock);
> >> +
> >> + switch (chan->channel) {
> >> + case 0:
> >> + *val = tsc2007_xfer(tsc, READ_X);
> >> + break;
> >> + case 1:
> >> + *val = tsc2007_xfer(tsc, READ_Y);
> >> + break;
> >> + case 2:
> >> + *val = tsc2007_xfer(tsc, READ_Z1);
> >> + break;
> >> + case 3:
> >> + *val = tsc2007_xfer(tsc, READ_Z2);
> >> + break;
> >> + case 4:
> >> + *val = tsc2007_xfer(tsc, (ADC_ON_12BIT | TSC2007_MEASURE_AUX));
> >> + break;
> >> + case 5: {
> >> + struct ts_event tc;
> >> +
> >> + tc.x = tsc2007_xfer(tsc, READ_X);
> >> + tc.z1 = tsc2007_xfer(tsc, READ_Z1);
> >> + tc.z2 = tsc2007_xfer(tsc, READ_Z2);
> >> + *val = tsc2007_calculate_resistance(tsc, &tc);
> >> + break;
> >> + }
> >> + case 6:
> >> + *val = tsc2007_is_pen_down(tsc);
> >> + break;
> >> + case 7:
> >> + *val = tsc2007_xfer(tsc,
> >> + (ADC_ON_12BIT | TSC2007_MEASURE_TEMP0));
> >> + break;
> >> + case 8:
> >> + *val = tsc2007_xfer(tsc,
> >> + (ADC_ON_12BIT | TSC2007_MEASURE_TEMP1));
> >> + break;
> >> + }
> >> +
> >> + /* Prepare for next touch reading - power down ADC, enable PENIRQ */
> >> + tsc2007_xfer(tsc, PWRDOWN);
> >> +
> >> + mutex_unlock(&tsc->mlock);
> >> +
> >> + ret = IIO_VAL_INT;
> >> +
> >> + return ret;
> >> +}
> >> +
> >> +static const struct iio_info tsc2007_iio_info = {
> >> + .read_raw = tsc2007_read_raw,
> >> + .driver_module = THIS_MODULE,
> >> +};
> >> +
> >> +int tsc2007_iio_configure(struct tsc2007 *ts)
> >> +{
> >> + int err;
> >> + struct iio_dev *indio_dev;
> >> + struct tsc2007_iio *iio;
> >> +
> >> + indio_dev = devm_iio_device_alloc(&ts->client->dev,
> >> + sizeof(struct tsc2007_iio));
> >> + if (!indio_dev) {
> >> + dev_err(&ts->client->dev, "iio_device_alloc failed\n");
> >> + return -ENOMEM;
> >> + }
> >> +
> >> + iio = iio_priv(indio_dev);
> >> + iio->ts = ts;
> >> + ts->iio_dev = (void *) indio_dev;
> >> +
> >> + indio_dev->name = "tsc2007";
> >> + indio_dev->dev.parent = &ts->client->dev;
> >> + indio_dev->info = &tsc2007_iio_info;
> >> + indio_dev->modes = INDIO_DIRECT_MODE;
> >> + indio_dev->channels = tsc2007_iio_channel;
> >> + indio_dev->num_channels = ARRAY_SIZE(tsc2007_iio_channel);
> >> +
> >> + err = iio_device_register(indio_dev);
> >> + if (err < 0) {
> >> + dev_err(&ts->client->dev, "iio_device_register() failed: %d\n",
> >> + err);
> >> + return err;
> >> + }
> >> +
> >> + return 0;
> >> +}
> >> +EXPORT_SYMBOL(tsc2007_iio_configure);
> >> +
> >> +void tsc2007_iio_unconfigure(struct tsc2007 *ts)
> >> +{
> >> + struct iio_dev *indio_dev = ts->iio_dev;
> >> +
> >> + iio_device_unregister(indio_dev);
> >> +}
> >> +EXPORT_SYMBOL(tsc2007_iio_unconfigure);
> >>
> >
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at http://vger.kernel.org/majordomo-info.html
> >
>
--
Dmitry
^ permalink raw reply
* Re: [PATCH v6 4/8] drivers:input:tsc2007: add iio interface to read external ADC input and temperature
From: Dmitry Torokhov @ 2016-11-19 18:36 UTC (permalink / raw)
To: H. Nikolaus Schaller
Cc: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
Russell King, Arnd Bergmann, Michael Welling, Mika Penttilä,
Javier Martinez Canillas, Igor Grinberg, Sebastian Reichel,
Andrew F. Davis, Mark Brown, Jonathan Cameron, Hans de Goede,
Sangwon Jee, linux-input-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-omap-u79uwXL29TY76Z2rM5mHXA, letux-kernel
In-Reply-To: <810571cc83159c674116f6f55624ca6227510f96.1477557860.git.hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
Hi Nikolaus,
On Thu, Oct 27, 2016 at 10:44:17AM +0200, H. Nikolaus Schaller wrote:
> The tsc2007 chip not only has a resistive touch screen controller but
> also an external AUX adc imput which can be used for an ambient
> light sensor, battery voltage monitoring or any general purpose.
>
> Additionally it can measure the chip temperature.
>
> This extension provides an iio interface for these adc channels.
>
> Since it is not wasting much resources and is very straightforward,
> we simply provide all other adc channels as optional iio interfaces
> as weel. This can be used for debugging or special applications.
>
> This patch also splits the tsc2007 driver in several source files:
> tsc2007.h -- constants, structs and stubs
> tsc2007_core.c -- functional parts of the original driver
> tsc2007_iio.c -- the optional iio stuff
>
> Makefile magic allows to conditionally link the iio
> stuff if CONFIG_IIO=y in a way that it works with
> CONFIG_TOUCHSCREEN_TSC2007=m.
What about CONFIG_TOUCHSCREEN_TSC2007=y and CONFIG_IIO=m?
>
> Signed-off-by: H. Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
> ---
> drivers/input/touchscreen/Makefile | 2 +
> drivers/input/touchscreen/tsc2007.h | 129 ++++++++++++++++++
> .../touchscreen/{tsc2007.c => tsc2007_core.c} | 127 ++++++-----------
> drivers/input/touchscreen/tsc2007_iio.c | 151 +++++++++++++++++++++
> 4 files changed, 320 insertions(+), 89 deletions(-)
> create mode 100644 drivers/input/touchscreen/tsc2007.h
> rename drivers/input/touchscreen/{tsc2007.c => tsc2007_core.c} (84%)
> create mode 100644 drivers/input/touchscreen/tsc2007_iio.c
>
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index 81b8645..d932e2d 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -80,6 +80,8 @@ obj-$(CONFIG_TOUCHSCREEN_TSC_SERIO) += tsc40.o
> obj-$(CONFIG_TOUCHSCREEN_TSC200X_CORE) += tsc200x-core.o
> obj-$(CONFIG_TOUCHSCREEN_TSC2004) += tsc2004.o
> obj-$(CONFIG_TOUCHSCREEN_TSC2005) += tsc2005.o
> +tsc2007-y := tsc2007_core.o
> +tsc2007-$(CONFIG_IIO) += tsc2007_iio.o
> obj-$(CONFIG_TOUCHSCREEN_TSC2007) += tsc2007.o
> obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o
> obj-$(CONFIG_TOUCHSCREEN_WACOM_W8001) += wacom_w8001.o
> diff --git a/drivers/input/touchscreen/tsc2007.h b/drivers/input/touchscreen/tsc2007.h
> new file mode 100644
> index 0000000..87d5ce5
> --- /dev/null
> +++ b/drivers/input/touchscreen/tsc2007.h
> @@ -0,0 +1,129 @@
> +/*
> + * drivers/input/touchscreen/tsc2007.h
No file names in comments please.
> + *
> + * Copyright (c) 2008 MtekVision Co., Ltd.
> + * Kwangwoo Lee <kwlee-ec7hoAtq5SbSUeElwK9/Pw@public.gmane.org>
> + *
> + * Using code from:
> + * - ads7846.c
> + * Copyright (c) 2005 David Brownell
> + * Copyright (c) 2006 Nokia Corporation
> + * - corgi_ts.c
> + * Copyright (C) 2004-2005 Richard Purdie
> + * - omap_ts.[hc], ads7846.h, ts_osk.c
> + * Copyright (C) 2002 MontaVista Software
> + * Copyright (C) 2004 Texas Instruments
> + * Copyright (C) 2005 Dirk Behme
> + *
> + * 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.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/input.h>
> +#include <linux/interrupt.h>
> +#include <linux/i2c.h>
> +#include <linux/i2c/tsc2007.h>
> +#include <linux/of_device.h>
> +#include <linux/of.h>
> +#include <linux/of_gpio.h>
> +#include <linux/input/touchscreen.h>
> +
> +#define TSC2007_MEASURE_TEMP0 (0x0 << 4)
> +#define TSC2007_MEASURE_AUX (0x2 << 4)
> +#define TSC2007_MEASURE_TEMP1 (0x4 << 4)
> +#define TSC2007_ACTIVATE_XN (0x8 << 4)
> +#define TSC2007_ACTIVATE_YN (0x9 << 4)
> +#define TSC2007_ACTIVATE_YP_XN (0xa << 4)
> +#define TSC2007_SETUP (0xb << 4)
> +#define TSC2007_MEASURE_X (0xc << 4)
> +#define TSC2007_MEASURE_Y (0xd << 4)
> +#define TSC2007_MEASURE_Z1 (0xe << 4)
> +#define TSC2007_MEASURE_Z2 (0xf << 4)
> +
> +#define TSC2007_POWER_OFF_IRQ_EN (0x0 << 2)
> +#define TSC2007_ADC_ON_IRQ_DIS0 (0x1 << 2)
> +#define TSC2007_ADC_OFF_IRQ_EN (0x2 << 2)
> +#define TSC2007_ADC_ON_IRQ_DIS1 (0x3 << 2)
> +
> +#define TSC2007_12BIT (0x0 << 1)
> +#define TSC2007_8BIT (0x1 << 1)
> +
> +#define MAX_12BIT ((1 << 12) - 1)
> +
> +#define ADC_ON_12BIT (TSC2007_12BIT | TSC2007_ADC_ON_IRQ_DIS0)
> +
> +#define READ_Y (ADC_ON_12BIT | TSC2007_MEASURE_Y)
> +#define READ_Z1 (ADC_ON_12BIT | TSC2007_MEASURE_Z1)
> +#define READ_Z2 (ADC_ON_12BIT | TSC2007_MEASURE_Z2)
> +#define READ_X (ADC_ON_12BIT | TSC2007_MEASURE_X)
> +#define PWRDOWN (TSC2007_12BIT | TSC2007_POWER_OFF_IRQ_EN)
> +
> +struct ts_event {
> + u16 x;
> + u16 y;
> + u16 z1, z2;
> +};
> +
> +struct tsc2007 {
> + struct input_dev *input;
> + char phys[32];
> +
> + struct i2c_client *client;
> +
> + u16 model;
> + u16 x_plate_ohms;
> +
> + struct touchscreen_properties prop;
> +
> + bool report_resistance;
> + u16 min_x;
> + u16 min_y;
> + u16 max_x;
> + u16 max_y;
> + u16 max_rt;
> + unsigned long poll_period; /* in jiffies */
> + int fuzzx;
> + int fuzzy;
> + int fuzzz;
> +
> + unsigned int gpio;
> + int irq;
> +
> + wait_queue_head_t wait;
> + bool stopped;
> + bool pendown;
> +
> + int (*get_pendown_state)(struct device *);
> + void (*clear_penirq)(void);
> +
> + struct mutex mlock;
> +#ifdef CONFIG_IIO
> + void *private;
> +#endif
> +};
> +
> +int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd);
> +u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
> + struct ts_event *tc);
> +bool tsc2007_is_pen_down(struct tsc2007 *ts);
> +
> +#ifdef CONFIG_IIO
> +
> +/* defined in tsc2007_iio.c */
> +int tsc2007_iio_configure(struct tsc2007 *ts);
> +void tsc2007_iio_unconfigure(struct tsc2007 *ts);
> +
> +#else /* CONFIG_IIO */
> +
> +static inline int tsc2007_iio_configure(struct tsc2007 *ts)
> +{
> + return 0;
> +}
> +static inline void tsc2007_iio_unconfigure(struct tsc2007 *ts)
> +{
> +}
> +
> +#endif /* CONFIG_IIO */
> diff --git a/drivers/input/touchscreen/tsc2007.c b/drivers/input/touchscreen/tsc2007_core.c
> similarity index 84%
> rename from drivers/input/touchscreen/tsc2007.c
> rename to drivers/input/touchscreen/tsc2007_core.c
> index 5e3c4bf..56c9a52 100644
> --- a/drivers/input/touchscreen/tsc2007.c
> +++ b/drivers/input/touchscreen/tsc2007_core.c
> @@ -20,87 +20,9 @@
> * published by the Free Software Foundation.
> */
>
> -#include <linux/module.h>
> -#include <linux/slab.h>
> -#include <linux/input.h>
> -#include <linux/interrupt.h>
> -#include <linux/i2c.h>
> -#include <linux/i2c/tsc2007.h>
> -#include <linux/of_device.h>
> -#include <linux/of.h>
> -#include <linux/of_gpio.h>
> -#include <linux/input/touchscreen.h>
> -
> -#define TSC2007_MEASURE_TEMP0 (0x0 << 4)
> -#define TSC2007_MEASURE_AUX (0x2 << 4)
> -#define TSC2007_MEASURE_TEMP1 (0x4 << 4)
> -#define TSC2007_ACTIVATE_XN (0x8 << 4)
> -#define TSC2007_ACTIVATE_YN (0x9 << 4)
> -#define TSC2007_ACTIVATE_YP_XN (0xa << 4)
> -#define TSC2007_SETUP (0xb << 4)
> -#define TSC2007_MEASURE_X (0xc << 4)
> -#define TSC2007_MEASURE_Y (0xd << 4)
> -#define TSC2007_MEASURE_Z1 (0xe << 4)
> -#define TSC2007_MEASURE_Z2 (0xf << 4)
> -
> -#define TSC2007_POWER_OFF_IRQ_EN (0x0 << 2)
> -#define TSC2007_ADC_ON_IRQ_DIS0 (0x1 << 2)
> -#define TSC2007_ADC_OFF_IRQ_EN (0x2 << 2)
> -#define TSC2007_ADC_ON_IRQ_DIS1 (0x3 << 2)
> -
> -#define TSC2007_12BIT (0x0 << 1)
> -#define TSC2007_8BIT (0x1 << 1)
> -
> -#define MAX_12BIT ((1 << 12) - 1)
> -
> -#define ADC_ON_12BIT (TSC2007_12BIT | TSC2007_ADC_ON_IRQ_DIS0)
> -
> -#define READ_Y (ADC_ON_12BIT | TSC2007_MEASURE_Y)
> -#define READ_Z1 (ADC_ON_12BIT | TSC2007_MEASURE_Z1)
> -#define READ_Z2 (ADC_ON_12BIT | TSC2007_MEASURE_Z2)
> -#define READ_X (ADC_ON_12BIT | TSC2007_MEASURE_X)
> -#define PWRDOWN (TSC2007_12BIT | TSC2007_POWER_OFF_IRQ_EN)
> -
> -struct ts_event {
> - u16 x;
> - u16 y;
> - u16 z1, z2;
> -};
> -
> -struct tsc2007 {
> - struct input_dev *input;
> - char phys[32];
> -
> - struct i2c_client *client;
> -
> - u16 model;
> - u16 x_plate_ohms;
> -
> - struct touchscreen_properties prop;
> -
> - bool report_resistance;
> - u16 min_x;
> - u16 min_y;
> - u16 max_x;
> - u16 max_y;
> - u16 max_rt;
> - unsigned long poll_period; /* in jiffies */
> - int fuzzx;
> - int fuzzy;
> - int fuzzz;
> +#include "tsc2007.h"
>
> - unsigned gpio;
> - int irq;
> -
> - wait_queue_head_t wait;
> - bool stopped;
> - bool pendown;
> -
> - int (*get_pendown_state)(struct device *);
> - void (*clear_penirq)(void);
> -};
> -
> -static inline int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd)
> +int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd)
> {
> s32 data;
> u16 val;
> @@ -121,6 +43,7 @@ static inline int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd)
>
> return val;
> }
> +EXPORT_SYMBOL(tsc2007_xfer);
Why do you export all these symbols? As far as I can tell it is still
the same module, but now comprising of maybe 2 object files liked
together. You do not need to export symbols in this case, liker will
resolve them when building the module.
>
> static void tsc2007_read_values(struct tsc2007 *tsc, struct ts_event *tc)
> {
> @@ -138,7 +61,7 @@ static void tsc2007_read_values(struct tsc2007 *tsc, struct ts_event *tc)
> tsc2007_xfer(tsc, PWRDOWN);
> }
>
> -static u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
> +u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
> struct ts_event *tc)
> {
> u32 rt = 0;
> @@ -158,8 +81,9 @@ static u32 tsc2007_calculate_resistance(struct tsc2007 *tsc,
>
> return rt;
> }
> +EXPORT_SYMBOL(tsc2007_calculate_resistance);
>
> -static bool tsc2007_is_pen_down(struct tsc2007 *ts)
> +bool tsc2007_is_pen_down(struct tsc2007 *ts)
> {
> /*
> * NOTE: We can't rely on the pressure to determine the pen down
> @@ -180,6 +104,7 @@ static bool tsc2007_is_pen_down(struct tsc2007 *ts)
>
> return ts->get_pendown_state(&ts->client->dev);
> }
> +EXPORT_SYMBOL(tsc2007_is_pen_down);
>
> static irqreturn_t tsc2007_soft_irq(int irq, void *handle)
> {
> @@ -192,7 +117,10 @@ static irqreturn_t tsc2007_soft_irq(int irq, void *handle)
> while (!ts->stopped && tsc2007_is_pen_down(ts)) {
>
> /* pen is down, continue with the measurement */
> +
> + mutex_lock(&ts->mlock);
> tsc2007_read_values(ts, &tc);
> + mutex_unlock(&ts->mlock);
>
> rt = tsc2007_calculate_resistance(ts, &tc);
>
> @@ -450,7 +378,8 @@ static void tsc2007_call_exit_platform_hw(void *data)
> static int tsc2007_probe(struct i2c_client *client,
> const struct i2c_device_id *id)
> {
> - const struct tsc2007_platform_data *pdata = dev_get_platdata(&client->dev);
> + const struct tsc2007_platform_data *pdata =
> + dev_get_platdata(&client->dev);
> struct tsc2007 *ts;
> struct input_dev *input_dev;
> int err;
> @@ -472,7 +401,13 @@ static int tsc2007_probe(struct i2c_client *client,
> ts->client = client;
> ts->irq = client->irq;
> ts->input = input_dev;
> +
> + err = tsc2007_iio_configure(ts);
No, this is not going to work. From teh cursory glance at the new iio
code it uses resources that you set up later in tsc2007_probe(). I'd say
this needs to go last.
> + if (err < 0)
> + return err;
> +
> init_waitqueue_head(&ts->wait);
> + mutex_init(&ts->mlock);
>
> snprintf(ts->phys, sizeof(ts->phys),
> "%s/input0", dev_name(&client->dev));
> @@ -494,7 +429,7 @@ static int tsc2007_probe(struct i2c_client *client,
> if (pdata) {
> err = tsc2007_probe_pdev(client, ts, pdata, id);
> if (err)
> - return err;
> + goto probe_err;
> input_set_abs_params(input_dev, ABS_X, 0, ts->max_x-ts->min_x,
> ts->fuzzx, 0);
> input_set_abs_params(input_dev, ABS_Y, 0, ts->max_y-ts->min_y,
> @@ -504,7 +439,7 @@ static int tsc2007_probe(struct i2c_client *client,
> } else {
> err = tsc2007_probe_dt(client, ts);
> if (err)
> - return err;
> + goto probe_err;
> }
>
> if (pdata) {
> @@ -516,7 +451,7 @@ static int tsc2007_probe(struct i2c_client *client,
> dev_err(&client->dev,
> "Failed to register exit_platform_hw action, %d\n",
> err);
> - return err;
> + goto probe_err;
> }
> }
>
> @@ -533,7 +468,7 @@ static int tsc2007_probe(struct i2c_client *client,
> if (err) {
> dev_err(&client->dev, "Failed to request irq %d: %d\n",
> ts->irq, err);
> - return err;
> + goto probe_err;
> }
>
> tsc2007_stop(ts);
> @@ -543,17 +478,30 @@ static int tsc2007_probe(struct i2c_client *client,
> if (err < 0) {
> dev_err(&client->dev,
> "Failed to setup chip: %d\n", err);
> - return err; /* usually, chip does not respond */
> + goto probe_err; /* chip does not respond */
> }
>
> err = input_register_device(input_dev);
> if (err) {
> dev_err(&client->dev,
> "Failed to register input device: %d\n", err);
> - return err;
> + goto probe_err;
> }
>
> return 0;
> +
> +probe_err:
> + tsc2007_iio_unconfigure(ts);
> + return err;
> +}
> +
> +static int tsc2007_remove(struct i2c_client *client)
> +{
> + struct tsc2007 *ts = i2c_get_clientdata(client);
> +
> + tsc2007_iio_unconfigure(ts);
> + input_unregister_device(ts->input);
Why did you add input_unregister_device() for devm-managed device?
> + return 0;
> }
>
> static const struct i2c_device_id tsc2007_idtable[] = {
> @@ -578,6 +526,7 @@ static struct i2c_driver tsc2007_driver = {
> },
> .id_table = tsc2007_idtable,
> .probe = tsc2007_probe,
> + .remove = tsc2007_remove,
> };
>
> module_i2c_driver(tsc2007_driver);
> diff --git a/drivers/input/touchscreen/tsc2007_iio.c b/drivers/input/touchscreen/tsc2007_iio.c
> new file mode 100644
> index 0000000..0375d8b
> --- /dev/null
> +++ b/drivers/input/touchscreen/tsc2007_iio.c
> @@ -0,0 +1,151 @@
> +/*
> + * drivers/input/touchscreen/tsc2007_iio.c
> + *
> + * Copyright (c) 2016 Golden Delicious Comp. GmbH&Co. KG
> + * Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
> + *
> + * 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.
> + */
> +
> +#include "tsc2007.h"
> +#include <linux/iio/iio.h>
> +
> +struct tsc2007_iio {
> + struct tsc2007 *ts;
> +};
> +
> +#define TSC2007_CHAN_IIO(_chan, _name, _type, _chan_info) \
> +{ \
> + .datasheet_name = _name, \
> + .type = _type, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
> + BIT(_chan_info), \
> + .indexed = 1, \
> + .channel = _chan, \
> +}
> +
> +static const struct iio_chan_spec tsc2007_iio_channel[] = {
> + TSC2007_CHAN_IIO(0, "x", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> + TSC2007_CHAN_IIO(1, "y", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> + TSC2007_CHAN_IIO(2, "z1", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> + TSC2007_CHAN_IIO(3, "z2", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> + TSC2007_CHAN_IIO(4, "adc", IIO_VOLTAGE, IIO_CHAN_INFO_RAW),
> + TSC2007_CHAN_IIO(5, "rt", IIO_VOLTAGE, IIO_CHAN_INFO_RAW), /* Ohms? */
> + TSC2007_CHAN_IIO(6, "pen", IIO_PRESSURE, IIO_CHAN_INFO_RAW),
> + TSC2007_CHAN_IIO(7, "temp0", IIO_TEMP, IIO_CHAN_INFO_RAW),
> + TSC2007_CHAN_IIO(8, "temp1", IIO_TEMP, IIO_CHAN_INFO_RAW),
> +};
> +
> +static int tsc2007_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val, int *val2, long mask)
> +{
> + struct tsc2007_iio *iio = iio_priv(indio_dev);
> + struct tsc2007 *tsc = iio->ts;
> + int adc_chan = chan->channel;
> + int ret = 0;
> +
> + if (adc_chan >= ARRAY_SIZE(tsc2007_iio_channel))
> + return -EINVAL;
> +
> + if (mask != IIO_CHAN_INFO_RAW)
> + return -EINVAL;
> +
> + mutex_lock(&tsc->mlock);
> +
> + switch (chan->channel) {
> + case 0:
> + *val = tsc2007_xfer(tsc, READ_X);
> + break;
> + case 1:
> + *val = tsc2007_xfer(tsc, READ_Y);
> + break;
> + case 2:
> + *val = tsc2007_xfer(tsc, READ_Z1);
> + break;
> + case 3:
> + *val = tsc2007_xfer(tsc, READ_Z2);
> + break;
> + case 4:
> + *val = tsc2007_xfer(tsc, (ADC_ON_12BIT | TSC2007_MEASURE_AUX));
> + break;
> + case 5: {
> + struct ts_event tc;
> +
> + tc.x = tsc2007_xfer(tsc, READ_X);
> + tc.z1 = tsc2007_xfer(tsc, READ_Z1);
> + tc.z2 = tsc2007_xfer(tsc, READ_Z2);
> + *val = tsc2007_calculate_resistance(tsc, &tc);
> + break;
> + }
> + case 6:
> + *val = tsc2007_is_pen_down(tsc);
> + break;
> + case 7:
> + *val = tsc2007_xfer(tsc,
> + (ADC_ON_12BIT | TSC2007_MEASURE_TEMP0));
> + break;
> + case 8:
> + *val = tsc2007_xfer(tsc,
> + (ADC_ON_12BIT | TSC2007_MEASURE_TEMP1));
> + break;
> + }
> +
> + /* Prepare for next touch reading - power down ADC, enable PENIRQ */
> + tsc2007_xfer(tsc, PWRDOWN);
> +
> + mutex_unlock(&tsc->mlock);
> +
> + ret = IIO_VAL_INT;
> +
> + return ret;
> +}
> +
> +static const struct iio_info tsc2007_iio_info = {
> + .read_raw = tsc2007_read_raw,
> + .driver_module = THIS_MODULE,
> +};
> +
> +int tsc2007_iio_configure(struct tsc2007 *ts)
> +{
> + int err;
> + struct iio_dev *indio_dev;
> + struct tsc2007_iio *iio;
> +
> + indio_dev = devm_iio_device_alloc(&ts->client->dev,
> + sizeof(struct tsc2007_iio));
> + if (!indio_dev) {
> + dev_err(&ts->client->dev, "iio_device_alloc failed\n");
> + return -ENOMEM;
> + }
> +
> + iio = iio_priv(indio_dev);
> + iio->ts = ts;
> + ts->private = (void *) indio_dev;
> +
> + indio_dev->name = "tsc2007";
> + indio_dev->dev.parent = &ts->client->dev;
> + indio_dev->info = &tsc2007_iio_info;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> + indio_dev->channels = tsc2007_iio_channel;
> + indio_dev->num_channels = ARRAY_SIZE(tsc2007_iio_channel);
> +
> + err = iio_device_register(indio_dev);
> + if (err < 0) {
> + dev_err(&ts->client->dev, "iio_device_register() failed: %d\n",
> + err);
> + return err;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(tsc2007_iio_configure);
> +
> +void tsc2007_iio_unconfigure(struct tsc2007 *ts)
> +{
> + struct iio_dev *indio_dev = ts->private;
> +
> + iio_device_unregister(indio_dev);
> +}
> +EXPORT_SYMBOL(tsc2007_iio_unconfigure);
> --
> 2.7.3
>
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v6 7/8] drivers:input:ads7846(+tsc2046): fix spi module table
From: Dmitry Torokhov @ 2016-11-19 18:18 UTC (permalink / raw)
To: H. Nikolaus Schaller
Cc: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
Russell King, Arnd Bergmann, Michael Welling, Mika Penttilä,
Javier Martinez Canillas, Igor Grinberg, Sebastian Reichel,
Andrew F. Davis, Mark Brown, Jonathan Cameron, Hans de Goede,
Sangwon Jee, linux-input-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-omap-u79uwXL29TY76Z2rM5mHXA, letux-kernel
In-Reply-To: <54f282e1adebeeb133687b6c430ecbb6f2fe6d5b.1477557860.git.hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
On Thu, Oct 27, 2016 at 10:44:20AM +0200, H. Nikolaus Schaller wrote:
> Fix module table so that the driver is loaded if compiled
> as module and requested by DT.
We really need to fix it between spi/i23c core and module utils instead
of keeping adding duplicate IDs all over drivers. We already have OF
module device table containing the same data, we should be able to use
it.
Thanks.
>
> Signed-off-by: H. Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
> ---
> drivers/input/touchscreen/ads7846.c | 11 ++++++++++-
> 1 file changed, 10 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c
> index 400e421..50c85d2 100644
> --- a/drivers/input/touchscreen/ads7846.c
> +++ b/drivers/input/touchscreen/ads7846.c
> @@ -1532,6 +1532,16 @@ static int ads7846_remove(struct spi_device *spi)
> return 0;
> }
>
> +static const struct spi_device_id ads7846_idtable[] = {
> + { "tsc2046", 0 },
> + { "ads7843", 0 },
> + { "ads7845", 0 },
> + { "ads7846", 0 },
> + { "ads7873", 0 },
> + { }
> +};
> +MODULE_DEVICE_TABLE(spi, ads7846_idtable);
> +
> static struct spi_driver ads7846_driver = {
> .driver = {
> .name = "ads7846",
> @@ -1546,4 +1556,3 @@ module_spi_driver(ads7846_driver);
>
> MODULE_DESCRIPTION("ADS7846 TouchScreen Driver");
> MODULE_LICENSE("GPL");
> -MODULE_ALIAS("spi:ads7846");
> --
> 2.7.3
>
--
Dmitry
^ permalink raw reply
* Re: [PATCH v6 2/8] drivers:input:tsc2007: send pendown and penup only once like ads7846(+tsc2046) driver does
From: Dmitry Torokhov @ 2016-11-19 18:12 UTC (permalink / raw)
To: H. Nikolaus Schaller
Cc: Rob Herring, Mark Rutland, Benoît Cousson, Tony Lindgren,
Russell King, Arnd Bergmann, Michael Welling, Mika Penttilä,
Javier Martinez Canillas, Igor Grinberg, Sebastian Reichel,
Andrew F. Davis, Mark Brown, Jonathan Cameron, Hans de Goede,
Sangwon Jee, linux-input-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-omap-u79uwXL29TY76Z2rM5mHXA, letux-kernel
In-Reply-To: <5e961a92f11b1509bd0effb96ef560f19839aa9d.1477557860.git.hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
On Thu, Oct 27, 2016 at 10:44:15AM +0200, H. Nikolaus Schaller wrote:
> this should reduce unnecessary input events.
The duplicates will be filtered out by the input core anyway. I like to
keep the drivers simple.
Thanks.
>
> Signed-off-by: H. Nikolaus Schaller <hns-xXXSsgcRVICgSpxsJD1C4w@public.gmane.org>
> ---
> drivers/input/touchscreen/tsc2007.c | 17 +++++++++++++----
> 1 file changed, 13 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/input/touchscreen/tsc2007.c b/drivers/input/touchscreen/tsc2007.c
> index c1d9593..e9d5086 100644
> --- a/drivers/input/touchscreen/tsc2007.c
> +++ b/drivers/input/touchscreen/tsc2007.c
> @@ -94,6 +94,7 @@ struct tsc2007 {
>
> wait_queue_head_t wait;
> bool stopped;
> + bool pendown;
>
> int (*get_pendown_state)(struct device *);
> void (*clear_penirq)(void);
> @@ -227,7 +228,11 @@ static irqreturn_t tsc2007_soft_irq(int irq, void *handle)
> sx, sy, rt);
>
> /* report event */
> - input_report_key(input, BTN_TOUCH, 1);
> + if (!ts->pendown) {
> + input_report_key(input, BTN_TOUCH, 1);
> + ts->pendown = true;
> + }
> +
> touchscreen_report_pos(ts->input, &ts->prop,
> (unsigned int) sx,
> (unsigned int) sy,
> @@ -250,9 +255,13 @@ static irqreturn_t tsc2007_soft_irq(int irq, void *handle)
>
> dev_dbg(&ts->client->dev, "UP\n");
>
> - input_report_key(input, BTN_TOUCH, 0);
> - input_report_abs(input, ABS_PRESSURE, 0);
> - input_sync(input);
> + if (ts->pendown) {
> + input_report_key(input, BTN_TOUCH, 0);
> + input_report_abs(input, ABS_PRESSURE, 0);
> + input_sync(input);
> +
> + ts->pendown = false;
> + }
>
> if (ts->clear_penirq)
> ts->clear_penirq();
> --
> 2.7.3
>
--
Dmitry
^ permalink raw reply
* Re: [v2,2/3] hwmon: (mcp3021) add devicetree bindings documentation
From: Guenter Roeck @ 2016-11-19 16:28 UTC (permalink / raw)
To: Clemens Gruber
Cc: linux-hwmon-u79uwXL29TY76Z2rM5mHXA, Rob Herring, Jean Delvare,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20161027223345.16733-2-clemens.gruber-lZxf/j91sTNWk0Htik3J/w@public.gmane.org>
On Fri, Oct 28, 2016 at 12:33:44AM +0200, Clemens Gruber wrote:
> Document the devicetree bindings for the Microchip MCP3021/3221.
>
> Signed-off-by: Clemens Gruber <clemens.gruber-lZxf/j91sTNWk0Htik3J/w@public.gmane.org>
> Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Applied to -next.
Guenter
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [RFC PATCH v2 5/7] iio: multiplexer: new iio category and iio-mux driver
From: Jonathan Cameron @ 2016-11-19 15:49 UTC (permalink / raw)
To: Peter Rosin, linux-kernel
Cc: Wolfram Sang, Rob Herring, Mark Rutland, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Arnd Bergmann,
Greg Kroah-Hartman, linux-i2c, devicetree, linux-iio
In-Reply-To: <1479419289-17553-6-git-send-email-peda@axentia.se>
On 17/11/16 21:48, Peter Rosin wrote:
> When a multiplexer changes how an iio device behaves (for example
> by feeding different signals to an ADC), this driver can be used
> create one virtual iio channel for each multiplexer state.
>
> Depends on the generic multiplexer driver.
I'm not really following what all the ext info stuff in here is about.
Could you add a little more description of that?
Perhaps an example of how it is used and what the resulting interface
looks like?
Thanks,
Jonathan
> ---
> drivers/iio/Kconfig | 1 +
> drivers/iio/Makefile | 1 +
> drivers/iio/multiplexer/Kconfig | 17 ++
> drivers/iio/multiplexer/Makefile | 6 +
> drivers/iio/multiplexer/iio-mux.c | 444 ++++++++++++++++++++++++++++++++++++++
> 5 files changed, 469 insertions(+)
> create mode 100644 drivers/iio/multiplexer/Kconfig
> create mode 100644 drivers/iio/multiplexer/Makefile
> create mode 100644 drivers/iio/multiplexer/iio-mux.c
>
> diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
> index 6743b18194fb..dcb541d0d70e 100644
> --- a/drivers/iio/Kconfig
> +++ b/drivers/iio/Kconfig
> @@ -82,6 +82,7 @@ source "drivers/iio/humidity/Kconfig"
> source "drivers/iio/imu/Kconfig"
> source "drivers/iio/light/Kconfig"
> source "drivers/iio/magnetometer/Kconfig"
> +source "drivers/iio/multiplexer/Kconfig"
> source "drivers/iio/orientation/Kconfig"
> if IIO_TRIGGER
> source "drivers/iio/trigger/Kconfig"
> diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
> index 87e4c4369e2f..f9879c29cf6f 100644
> --- a/drivers/iio/Makefile
> +++ b/drivers/iio/Makefile
> @@ -27,6 +27,7 @@ obj-y += humidity/
> obj-y += imu/
> obj-y += light/
> obj-y += magnetometer/
> +obj-y += multiplexer/
> obj-y += orientation/
> obj-y += potentiometer/
> obj-y += pressure/
> diff --git a/drivers/iio/multiplexer/Kconfig b/drivers/iio/multiplexer/Kconfig
> new file mode 100644
> index 000000000000..31cbe4509366
> --- /dev/null
> +++ b/drivers/iio/multiplexer/Kconfig
> @@ -0,0 +1,17 @@
> +#
> +# Multiplexer drivers
> +#
> +# When adding new entries keep the list in alphabetical order
> +
> +menu "Multiplexers"
> +
> +config IIO_MUX
> + tristate "IIO multiplexer driver"
> + depends on OF && MUX_GPIO
> + help
> + Say yes here to build support for the IIO multiplexer.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called iio-mux.
> +
> +endmenu
> diff --git a/drivers/iio/multiplexer/Makefile b/drivers/iio/multiplexer/Makefile
> new file mode 100644
> index 000000000000..68be3c4abd07
> --- /dev/null
> +++ b/drivers/iio/multiplexer/Makefile
> @@ -0,0 +1,6 @@
> +#
> +# Makefile for industrial I/O multiplexer drivers
> +#
> +
> +# When adding new entries keep the list in alphabetical order
> +obj-$(CONFIG_IIO_MUX) += iio-mux.o
> diff --git a/drivers/iio/multiplexer/iio-mux.c b/drivers/iio/multiplexer/iio-mux.c
> new file mode 100644
> index 000000000000..2a8d00da990b
> --- /dev/null
> +++ b/drivers/iio/multiplexer/iio-mux.c
> @@ -0,0 +1,444 @@
> +/*
> + * IIO multiplexer driver
> + *
> + * Copyright (C) 2016 Axentia Technologies AB
> + *
> + * Author: Peter Rosin <peda@axentia.se>
> + *
> + * 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.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/iio/consumer.h>
> +#include <linux/iio/iio.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/mux.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +
> +struct mux_ext_info_cache {
> + char *data;
> + size_t size;
> +};
> +
> +struct mux_child {
> + u32 state;
> +
> + struct mux_ext_info_cache *ext_info_cache;
> +};
> +
> +struct mux {
> + u32 cached_state;
> + struct mux_control *control;
> + struct iio_channel *parent;
> + struct iio_dev *indio_dev;
> + struct iio_chan_spec *c;
> + struct iio_chan_spec_ext_info *ext_info;
> + struct mux_child *child;
> +};
> +
> +static int iio_mux_select(struct mux *mux, int idx)
> +{
> + struct mux_child *child = &mux->child[idx];
> + int ret;
> + int i;
> +
> + ret = mux_control_select(mux->control, child->state);
> + if (ret < 0) {
> + mux->cached_state = -1;
> + return ret;
> + }
> +
> + if (mux->cached_state == child->state)
> + return 0;
> +
I don't follow what is going on here.. Perhaps you could explain
futher?
> + if (mux->c[idx].ext_info) {
> + for (i = 0; mux->c[idx].ext_info[i].name; ++i) {
> + const char *attr = mux->c[idx].ext_info[i].name;
> + struct mux_ext_info_cache *cache;
> +
> + cache = &child->ext_info_cache[i];
> +
> + if (cache->size < 0)
> + continue;
> +
> + ret = iio_write_channel_ext_info(mux->parent, attr,
> + cache->data,
> + cache->size);
> +
> + if (ret < 0) {
> + mux_control_deselect(mux->control);
> + mux->cached_state = -1;
> + return ret;
> + }
> + }
> + }
> + mux->cached_state = child->state;
> +
> + return 0;
> +}
> +
> +static void iio_mux_deselect(struct mux *mux)
> +{
> + mux_control_deselect(mux->control);
> +}
> +
> +static int mux_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val, int *val2, long mask)
> +{
> + struct mux *mux = iio_priv(indio_dev);
> + int idx = chan - mux->c;
> + int ret;
> +
> + ret = iio_mux_select(mux, idx);
> + if (ret < 0)
> + return ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + ret = iio_read_channel_raw(mux->parent, val);
> + break;
> +
> + case IIO_CHAN_INFO_SCALE:
> + ret = iio_read_channel_scale(mux->parent, val, val2);
> + break;
> +
> + default:
> + ret = -EINVAL;
> + }
> +
> + iio_mux_deselect(mux);
> +
> + return ret;
> +}
> +
> +static int mux_read_avail(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + const int **vals, int *type, int *length,
> + long mask)
> +{
> + struct mux *mux = iio_priv(indio_dev);
> + int idx = chan - mux->c;
> + int ret;
> +
> + ret = iio_mux_select(mux, idx);
> + if (ret < 0)
> + return ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + *type = IIO_VAL_INT;
> + ret = iio_read_avail_channel_raw(mux->parent, vals, length);
> + break;
> +
> + default:
> + ret = -EINVAL;
> + }
> +
> + iio_mux_deselect(mux);
> +
> + return ret;
> +}
> +
> +static int mux_write_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int val, int val2, long mask)
> +{
> + struct mux *mux = iio_priv(indio_dev);
> + int idx = chan - mux->c;
> + int ret;
> +
> + ret = iio_mux_select(mux, idx);
> + if (ret < 0)
> + return ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + ret = iio_write_channel_raw(mux->parent, val);
> + break;
> +
> + default:
> + ret = -EINVAL;
> + }
> +
> + iio_mux_deselect(mux);
> +
> + return ret;
> +}
> +
> +static const struct iio_info mux_info = {
> + .read_raw = mux_read_raw,
> + .read_avail = mux_read_avail,
> + .write_raw = mux_write_raw,
> + .driver_module = THIS_MODULE,
> +};
> +
> +static ssize_t mux_read_ext_info(struct iio_dev *indio_dev, uintptr_t private,
> + struct iio_chan_spec const *chan, char *buf)
> +{
> + struct mux *mux = iio_priv(indio_dev);
> + int idx = chan - mux->c;
> + ssize_t ret;
> +
> + ret = iio_mux_select(mux, idx);
> + if (ret < 0)
> + return ret;
> +
> + ret = iio_read_channel_ext_info(mux->parent,
> + mux->ext_info[private].name,
> + buf);
> +
> + iio_mux_deselect(mux);
> +
> + return ret;
> +}
> +
> +static ssize_t mux_write_ext_info(struct iio_dev *indio_dev, uintptr_t private,
> + struct iio_chan_spec const *chan,
> + const char *buf, size_t len)
> +{
> + struct device *dev = indio_dev->dev.parent;
> + struct mux *mux = iio_priv(indio_dev);
> + int idx = chan - mux->c;
> + char *new;
> + ssize_t ret;
> +
> + ret = iio_mux_select(mux, idx);
> + if (ret < 0)
> + return ret;
> +
> + new = devm_kmemdup(dev, buf, len + 1, GFP_KERNEL);
> + if (!new) {
> + iio_mux_deselect(mux);
> + return -ENOMEM;
> + }
> +
> + new[len] = 0;
> +
> + ret = iio_write_channel_ext_info(mux->parent,
> + mux->ext_info[private].name,
> + buf, len);
> + if (ret < 0) {
> + iio_mux_deselect(mux);
> + devm_kfree(dev, new);
> + return ret;
> + }
> +
> + devm_kfree(dev, mux->child[idx].ext_info_cache[private].data);
> + mux->child[idx].ext_info_cache[private].data = new;
> + mux->child[idx].ext_info_cache[private].size = len;
> +
> + iio_mux_deselect(mux);
> +
> + return ret;
> +}
> +
> +static int mux_configure_channel(struct device *dev, struct mux *mux,
> + struct device_node *child_np, int idx)
> +{
> + struct mux_child *child = &mux->child[idx];
> + struct iio_chan_spec *c = &mux->c[idx];
> + const struct iio_chan_spec *pc = mux->parent->channel;
> + char *page;
> + int num_ext_info;
> + int i;
> + int ret;
> +
> + c->indexed = 1;
> + c->channel = idx;
> + c->output = pc->output;
> + c->datasheet_name = child_np->name;
> + c->ext_info = mux->ext_info;
> +
> + ret = iio_get_channel_type(mux->parent, &c->type);
> + if (ret < 0) {
> + dev_err(dev, "failed to get parent channel type\n");
> + return ret;
> + }
> +
> + if (iio_channel_has_info(pc, IIO_CHAN_INFO_RAW))
> + c->info_mask_separate |= BIT(IIO_CHAN_INFO_RAW);
> + if (iio_channel_has_info(pc, IIO_CHAN_INFO_SCALE))
> + c->info_mask_separate |= BIT(IIO_CHAN_INFO_SCALE);
> +
> + if (iio_channel_has_available(pc, IIO_CHAN_INFO_RAW))
> + c->info_mask_separate_available |= BIT(IIO_CHAN_INFO_RAW);
> +
> + ret = of_property_read_u32(child_np, "reg", &child->state);
> + if (ret < 0) {
> + dev_err(dev, "no reg property for node '%s'\n", child_np->name);
> + return ret;
> + }
> +
> + for (i = 0; i < idx; ++i) {
> + if (mux->child[i].state == child->state) {
> + dev_err(dev, "double use of reg %d\n", child->state);
> + return -EINVAL;
> + }
> + }
> +
> + num_ext_info = iio_get_channel_ext_info_count(mux->parent);
> + if (num_ext_info) {
> + page = devm_kzalloc(dev, PAGE_SIZE, GFP_KERNEL);
> + if (!page)
> + return -ENOMEM;
> + }
> + child->ext_info_cache = devm_kzalloc(dev,
> + sizeof(*child->ext_info_cache) *
> + num_ext_info, GFP_KERNEL);
> + for (i = 0; i < num_ext_info; ++i) {
> + child->ext_info_cache[i].size = -1;
> +
> + if (!pc->ext_info[i].write)
> + continue;
> + if (!pc->ext_info[i].read)
> + continue;
> +
> + ret = iio_read_channel_ext_info(mux->parent,
> + mux->ext_info[i].name,
> + page);
> + if (ret < 0) {
> + dev_err(dev, "failed to get ext_info '%s'\n",
> + pc->ext_info[i].name);
> + return ret;
> + }
> + if (ret >= PAGE_SIZE) {
> + dev_err(dev, "too large ext_info '%s'\n",
> + pc->ext_info[i].name);
> + return -EINVAL;
> + }
> +
> + child->ext_info_cache[i].data = devm_kmemdup(dev, page, ret + 1,
> + GFP_KERNEL);
> + child->ext_info_cache[i].data[ret] = 0;
> + child->ext_info_cache[i].size = ret;
> + }
> +
> + if (num_ext_info)
> + devm_kfree(dev, page);
> +
> + return 0;
> +}
> +
> +static int mux_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *np = pdev->dev.of_node;
> + struct device_node *child_np;
> + struct iio_dev *indio_dev;
> + struct iio_channel *parent;
> + struct mux *mux;
> + int sizeof_ext_info;
> + int children;
> + int sizeof_priv;
> + int i;
> + int ret;
> +
> + if (!np)
> + return -ENODEV;
> +
> + parent = devm_iio_channel_get(dev, "parent");
> + if (IS_ERR(parent)) {
> + if (PTR_ERR(parent) != -EPROBE_DEFER)
> + dev_err(dev, "failed to get parent channel\n");
> + return PTR_ERR(parent);
> + }
> +
> + sizeof_ext_info = iio_get_channel_ext_info_count(parent);
> + if (sizeof_ext_info) {
> + sizeof_ext_info += 1; /* one extra entry for the sentinel */
> + sizeof_ext_info *= sizeof(*mux->ext_info);
> + }
> +
> + children = of_get_child_count(np);
> + if (children <= 0) {
> + dev_err(dev, "not even a single child\n");
> + return -EINVAL;
> + }
> +
> + sizeof_priv = sizeof(*mux);
> + sizeof_priv += sizeof(*mux->child) * children;
> + sizeof_priv += sizeof(*mux->c) * children;
> + sizeof_priv += sizeof_ext_info;
> +
> + indio_dev = devm_iio_device_alloc(dev, sizeof_priv);
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + mux = iio_priv(indio_dev);
> + mux->child = (struct mux_child *)(mux + 1);
> + mux->c = (struct iio_chan_spec *)(mux->child + children);
> +
> + platform_set_drvdata(pdev, indio_dev);
> +
> + mux->parent = parent;
> + mux->cached_state = -1;
> +
> + indio_dev->name = dev_name(dev);
> + indio_dev->dev.parent = dev;
> + indio_dev->info = &mux_info;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> + indio_dev->channels = mux->c;
> + indio_dev->num_channels = children;
> + if (sizeof_ext_info) {
> + mux->ext_info = devm_kmemdup(dev,
> + parent->channel->ext_info,
> + sizeof_ext_info, GFP_KERNEL);
> + if (!mux->ext_info)
> + return -ENOMEM;
> +
> + for (i = 0; mux->ext_info[i].name; ++i) {
> + if (parent->channel->ext_info[i].read)
> + mux->ext_info[i].read = mux_read_ext_info;
> + if (parent->channel->ext_info[i].write)
> + mux->ext_info[i].write = mux_write_ext_info;
> + mux->ext_info[i].private = i;
> + }
> + }
> +
> + i = 0;
> + for_each_child_of_node(np, child_np) {
> + ret = mux_configure_channel(dev, mux, child_np, i);
> + if (ret < 0)
> + return ret;
> + i++;
> + }
> +
> + mux->control = devm_mux_control_get(dev, "mux");
> + if (IS_ERR(mux->control)) {
> + if (PTR_ERR(mux->control) != -EPROBE_DEFER)
> + dev_err(dev, "failed to get control-mux\n");
> + return PTR_ERR(mux->control);
> + }
> +
> + ret = devm_iio_device_register(dev, indio_dev);
> + if (ret) {
> + dev_err(dev, "failed to register iio device\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static const struct of_device_id mux_match[] = {
> + { .compatible = "iio-mux" },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, mux_match);
> +
> +static struct platform_driver mux_driver = {
> + .probe = mux_probe,
> + .driver = {
> + .name = "iio-mux",
> + .of_match_table = mux_match,
> + },
> +};
> +module_platform_driver(mux_driver);
> +
> +MODULE_DESCRIPTION("IIO multiplexer driver");
> +MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
> +MODULE_LICENSE("GPL v2");
>
^ 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