diff mbox series

[v1,3/7] mfd: add atmel-lcdc driver

Message ID 20180812184629.3808-3-sam@ravnborg.org (mailing list archive)
State New, archived
Headers show
Series add at91sam9 LCDC DRM driver | expand

Commit Message

Sam Ravnborg Aug. 12, 2018, 6:46 p.m. UTC
The LCDC IP used by some Atmel SOC's have a
    multifunction device that include two sub-devices:
    - pwm
    - display controller

This mfd device provide a regmap that can be used by the
sub-devices to safely access the registers.
The mfd device also support the clock used by the
LCDC IP + a bus clock that in some cases are required.

The driver is based on the atmel-hlcdc driver.

The Atmel SOC's are at91sam9261, at91sam9263 etc.

Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
Cc: Lee Jones <lee.jones@linaro.org>
Cc: Boris Brezillon <boris.brezillon@free-electrons.com>
---
 drivers/mfd/Kconfig            |  10 +++
 drivers/mfd/Makefile           |   1 +
 drivers/mfd/atmel-lcdc.c       | 158 +++++++++++++++++++++++++++++++++++
 include/linux/mfd/atmel-lcdc.h | 184 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 353 insertions(+)
 create mode 100644 drivers/mfd/atmel-lcdc.c
 create mode 100644 include/linux/mfd/atmel-lcdc.h

Comments

kernel test robot Aug. 14, 2018, 11:09 a.m. UTC | #1
Hi Sam,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on at91/at91-next]
[also build test WARNING on v4.18 next-20180813]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Sam-Ravnborg/add-at91sam9-LCDC-DRM-driver/20180814-163056
base:   https://git.kernel.org/pub/scm/linux/kernel/git/nferre/linux-at91.git at91-next
config: sparc64-allyesconfig (attached as .config)
compiler: sparc64-linux-gnu-gcc (Debian 7.2.0-11) 7.2.0
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        GCC_VERSION=7.2.0 make.cross ARCH=sparc64 

All warnings (new ones prefixed by >>):

   drivers/mfd/atmel-lcdc.c: In function 'lcdc_probe':
>> drivers/mfd/atmel-lcdc.c:127:32: warning: format '%d' expects argument of type 'int', but argument 3 has type 'long unsigned int' [-Wformat=]
      dev_err(dev, "Failed to add %d mfd devices (%d)\n",
                                  ~^
                                  %ld

vim +127 drivers/mfd/atmel-lcdc.c

    66	
    67	static int lcdc_probe(struct platform_device *pdev)
    68	{
    69		struct atmel_mfd_lcdc *lcdc;
    70		struct lcdc_regmap *regmap;
    71		struct resource *res;
    72		struct device *dev;
    73		int ret;
    74	
    75		dev = &pdev->dev;
    76	
    77		regmap = devm_kzalloc(dev, sizeof(*regmap), GFP_KERNEL);
    78		if (!regmap)
    79			return -ENOMEM;
    80	
    81		lcdc = devm_kzalloc(dev, sizeof(*lcdc), GFP_KERNEL);
    82		if (!lcdc)
    83			return -ENOMEM;
    84	
    85		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    86		regmap->regs = devm_ioremap_resource(dev, res);
    87		if (IS_ERR(regmap->regs)) {
    88			dev_err(dev, "Failed to allocate IO mem (%ld)\n",
    89				PTR_ERR(regmap->regs));
    90			return PTR_ERR(regmap->regs);
    91		}
    92	
    93		lcdc->irq = platform_get_irq(pdev, 0);
    94		if (lcdc->irq < 0) {
    95			dev_err(dev, "Failed to get irq (%d)\n", lcdc->irq);
    96			return lcdc->irq;
    97		}
    98	
    99		lcdc->lcdc_clk = devm_clk_get(dev, "lcdc_clk");
   100		if (IS_ERR(lcdc->lcdc_clk)) {
   101			dev_err(dev, "failed to get lcdc clock (%ld)\n",
   102				PTR_ERR(lcdc->lcdc_clk));
   103			return PTR_ERR(lcdc->lcdc_clk);
   104		}
   105	
   106		lcdc->bus_clk = devm_clk_get(dev, "hclk");
   107		if (IS_ERR(lcdc->bus_clk)) {
   108			dev_err(dev, "failed to get bus clock (%ld)\n",
   109				PTR_ERR(lcdc->bus_clk));
   110			return PTR_ERR(lcdc->bus_clk);
   111		}
   112	
   113		lcdc->regmap = devm_regmap_init(dev, NULL, regmap,
   114						 &lcdc_regmap_config);
   115		if (IS_ERR(lcdc->regmap)) {
   116			dev_err(dev, "Failed to init regmap (%ld)\n",
   117				PTR_ERR(lcdc->regmap));
   118			return PTR_ERR(lcdc->regmap);
   119		}
   120	
   121		dev_set_drvdata(dev, lcdc);
   122	
   123		ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
   124					   lcdc_cells, ARRAY_SIZE(lcdc_cells),
   125					   NULL, 0, NULL);
   126		if (ret < 0)
 > 127			dev_err(dev, "Failed to add %d mfd devices (%d)\n",
   128				ARRAY_SIZE(lcdc_cells), ret);
   129	
   130		return ret;
   131	}
   132	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
Lee Jones Aug. 15, 2018, 5:24 a.m. UTC | #2
On Sun, 12 Aug 2018, Sam Ravnborg wrote:

> The LCDC IP used by some Atmel SOC's have a
>     multifunction device that include two sub-devices:
>     - pwm
>     - display controller
> 
> This mfd device provide a regmap that can be used by the
> sub-devices to safely access the registers.
> The mfd device also support the clock used by the
> LCDC IP + a bus clock that in some cases are required.
> 
> The driver is based on the atmel-hlcdc driver.
> 
> The Atmel SOC's are at91sam9261, at91sam9263 etc.
> 
> Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
> Cc: Lee Jones <lee.jones@linaro.org>
> Cc: Boris Brezillon <boris.brezillon@free-electrons.com>
> ---
>  drivers/mfd/Kconfig            |  10 +++
>  drivers/mfd/Makefile           |   1 +
>  drivers/mfd/atmel-lcdc.c       | 158 +++++++++++++++++++++++++++++++++++
>  include/linux/mfd/atmel-lcdc.h | 184 +++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 353 insertions(+)
>  create mode 100644 drivers/mfd/atmel-lcdc.c
>  create mode 100644 include/linux/mfd/atmel-lcdc.h
> 
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index b860eb5aa194..f4851f0f033f 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -121,6 +121,16 @@ config MFD_ATMEL_HLCDC
>  	  additional drivers must be enabled in order to use the
>  	  functionality of the device.
>  
> +config MFD_ATMEL_LCDC
> +	tristate "Atmel LCDC (LCD Controller)"
> +	select MFD_CORE
> +	depends on OF
> +	help
> +	  If you say yes here you get support for the LCDC block.
> +	  This driver provides common support for accessing the device,
> +	  additional drivers must be enabled in order to use the
> +	  functionality of the device.
> +
>  config MFD_ATMEL_SMC
>  	bool
>  	select MFD_SYSCON
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index e9fd20dba18d..dba8465e0d96 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -186,6 +186,7 @@ obj-$(CONFIG_MFD_TPS65090)	+= tps65090.o
>  obj-$(CONFIG_MFD_AAT2870_CORE)	+= aat2870-core.o
>  obj-$(CONFIG_MFD_ATMEL_FLEXCOM)	+= atmel-flexcom.o
>  obj-$(CONFIG_MFD_ATMEL_HLCDC)	+= atmel-hlcdc.o
> +obj-$(CONFIG_MFD_ATMEL_LCDC)	+= atmel-lcdc.o
>  obj-$(CONFIG_MFD_ATMEL_SMC)	+= atmel-smc.o
>  obj-$(CONFIG_MFD_INTEL_LPSS)	+= intel-lpss.o
>  obj-$(CONFIG_MFD_INTEL_LPSS_PCI)	+= intel-lpss-pci.o
> diff --git a/drivers/mfd/atmel-lcdc.c b/drivers/mfd/atmel-lcdc.c
> new file mode 100644
> index 000000000000..8928976bafca
> --- /dev/null
> +++ b/drivers/mfd/atmel-lcdc.c
> @@ -0,0 +1,158 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2018 Sam Ravnborg
> + *
> + * Author: Sam Ravnborg <sam@ravnborg.org>
> + *
> + * Based on atmel-hlcdc.c wich is:
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + * Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
> + */
> +
> +#include <linux/mfd/atmel-lcdc.h>
> +#include <linux/mfd/core.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +
> +#define ATMEL_LCDC_REG_MAX		(0x1000 - 0x4)
> +
> +struct lcdc_regmap {
> +	void __iomem *regs;
> +};
> +
> +static const struct mfd_cell lcdc_cells[] = {
> +	{
> +		.name = "atmel-lcdc-pwm",
> +		.of_compatible = "atmel,lcdc-pwm",
> +	},
> +	{
> +		.name = "atmel-lcdc-dc",
> +		.of_compatible = "atmel,lcdc-display-controller",
> +	},
> +};

Will you be adding any more devices, or is this the entirety of the
device?  If the latter, I suggest that this doesn't warrant being an
MFD.
kernel test robot Aug. 15, 2018, 8:11 a.m. UTC | #3
Hi Sam,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on at91/at91-next]
[also build test WARNING on v4.18 next-20180814]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Sam-Ravnborg/add-at91sam9-LCDC-DRM-driver/20180814-163056
base:   https://git.kernel.org/pub/scm/linux/kernel/git/nferre/linux-at91.git at91-next
config: x86_64-randconfig-g0-08151425 (attached as .config)
compiler: gcc-4.9 (Debian 4.9.4-2) 4.9.4
reproduce:
        # save the attached .config to linux build tree
        make ARCH=x86_64 

All warnings (new ones prefixed by >>):

   In file included from include/linux/kernel.h:15:0,
                    from include/linux/list.h:9,
                    from include/linux/regmap.h:16,
                    from include/linux/mfd/atmel-lcdc.h:11,
                    from drivers/mfd/atmel-lcdc.c:13:
   drivers/mfd/atmel-lcdc.c: In function 'lcdc_probe':
>> include/linux/build_bug.h:29:45: warning: format '%d' expects argument of type 'int', but argument 3 has type 'long unsigned int' [-Wformat=]
    #define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:(-!!(e)); }))
                                                ^
   include/linux/compiler-gcc.h:65:28: note: in expansion of macro 'BUILD_BUG_ON_ZERO'
    #define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))
                               ^
   include/linux/kernel.h:72:59: note: in expansion of macro '__must_be_array'
    #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
                                                              ^
>> drivers/mfd/atmel-lcdc.c:128:4: note: in expansion of macro 'ARRAY_SIZE'
       ARRAY_SIZE(lcdc_cells), ret);
       ^
--
   In file included from include/linux/kernel.h:15:0,
                    from include/linux/list.h:9,
                    from include/linux/regmap.h:16,
                    from include/linux/mfd/atmel-lcdc.h:11,
                    from drivers//mfd/atmel-lcdc.c:13:
   drivers//mfd/atmel-lcdc.c: In function 'lcdc_probe':
>> include/linux/build_bug.h:29:45: warning: format '%d' expects argument of type 'int', but argument 3 has type 'long unsigned int' [-Wformat=]
    #define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:(-!!(e)); }))
                                                ^
   include/linux/compiler-gcc.h:65:28: note: in expansion of macro 'BUILD_BUG_ON_ZERO'
    #define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))
                               ^
   include/linux/kernel.h:72:59: note: in expansion of macro '__must_be_array'
    #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
                                                              ^
   drivers//mfd/atmel-lcdc.c:128:4: note: in expansion of macro 'ARRAY_SIZE'
       ARRAY_SIZE(lcdc_cells), ret);
       ^

vim +/ARRAY_SIZE +128 drivers/mfd/atmel-lcdc.c

    66	
    67	static int lcdc_probe(struct platform_device *pdev)
    68	{
    69		struct atmel_mfd_lcdc *lcdc;
    70		struct lcdc_regmap *regmap;
    71		struct resource *res;
    72		struct device *dev;
    73		int ret;
    74	
    75		dev = &pdev->dev;
    76	
    77		regmap = devm_kzalloc(dev, sizeof(*regmap), GFP_KERNEL);
    78		if (!regmap)
    79			return -ENOMEM;
    80	
    81		lcdc = devm_kzalloc(dev, sizeof(*lcdc), GFP_KERNEL);
    82		if (!lcdc)
    83			return -ENOMEM;
    84	
    85		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    86		regmap->regs = devm_ioremap_resource(dev, res);
    87		if (IS_ERR(regmap->regs)) {
    88			dev_err(dev, "Failed to allocate IO mem (%ld)\n",
    89				PTR_ERR(regmap->regs));
    90			return PTR_ERR(regmap->regs);
    91		}
    92	
    93		lcdc->irq = platform_get_irq(pdev, 0);
    94		if (lcdc->irq < 0) {
    95			dev_err(dev, "Failed to get irq (%d)\n", lcdc->irq);
    96			return lcdc->irq;
    97		}
    98	
    99		lcdc->lcdc_clk = devm_clk_get(dev, "lcdc_clk");
   100		if (IS_ERR(lcdc->lcdc_clk)) {
   101			dev_err(dev, "failed to get lcdc clock (%ld)\n",
   102				PTR_ERR(lcdc->lcdc_clk));
   103			return PTR_ERR(lcdc->lcdc_clk);
   104		}
   105	
   106		lcdc->bus_clk = devm_clk_get(dev, "hclk");
   107		if (IS_ERR(lcdc->bus_clk)) {
   108			dev_err(dev, "failed to get bus clock (%ld)\n",
   109				PTR_ERR(lcdc->bus_clk));
   110			return PTR_ERR(lcdc->bus_clk);
   111		}
   112	
   113		lcdc->regmap = devm_regmap_init(dev, NULL, regmap,
   114						 &lcdc_regmap_config);
   115		if (IS_ERR(lcdc->regmap)) {
   116			dev_err(dev, "Failed to init regmap (%ld)\n",
   117				PTR_ERR(lcdc->regmap));
   118			return PTR_ERR(lcdc->regmap);
   119		}
   120	
   121		dev_set_drvdata(dev, lcdc);
   122	
   123		ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
   124					   lcdc_cells, ARRAY_SIZE(lcdc_cells),
   125					   NULL, 0, NULL);
   126		if (ret < 0)
   127			dev_err(dev, "Failed to add %d mfd devices (%d)\n",
 > 128				ARRAY_SIZE(lcdc_cells), ret);
   129	
   130		return ret;
   131	}
   132	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
Sam Ravnborg Aug. 15, 2018, 8:40 p.m. UTC | #4
Hi Lee.

> > +
> > +static const struct mfd_cell lcdc_cells[] = {
> > +	{
> > +		.name = "atmel-lcdc-pwm",
> > +		.of_compatible = "atmel,lcdc-pwm",
> > +	},
> > +	{
> > +		.name = "atmel-lcdc-dc",
> > +		.of_compatible = "atmel,lcdc-display-controller",
> > +	},
> > +};
> 
> Will you be adding any more devices, or is this the entirety of the
> device?  If the latter, I suggest that this doesn't warrant being an
> MFD.
Thats it. And others agree with you that this is not a good approach.
So in v2 there will be no MFD.

Thanks for confirming that the non-mfd way is the better approach.

	Sam
Nicolas Ferre Aug. 16, 2018, 8:28 a.m. UTC | #5
On 15/08/2018 at 22:40, Sam Ravnborg wrote:
> Hi Lee.
> 
>>> +
>>> +static const struct mfd_cell lcdc_cells[] = {
>>> +	{
>>> +		.name = "atmel-lcdc-pwm",
>>> +		.of_compatible = "atmel,lcdc-pwm",
>>> +	},
>>> +	{
>>> +		.name = "atmel-lcdc-dc",
>>> +		.of_compatible = "atmel,lcdc-display-controller",
>>> +	},
>>> +};
>>
>> Will you be adding any more devices, or is this the entirety of the
>> device?  If the latter, I suggest that this doesn't warrant being an
>> MFD.
> Thats it. And others agree with you that this is not a good approach.
> So in v2 there will be no MFD.
> 
> Thanks for confirming that the non-mfd way is the better approach.

MFD approach would have had the benefit of keeping this driver series 
architecture close to the HLCD one. This would have been easier to 
understand and use one SoC or another one from the AT91 product line....

Anyway, I'd wait for Boris' feedback for making a decision.

Best regards,
Lee Jones Aug. 16, 2018, 8:42 a.m. UTC | #6
On Thu, 16 Aug 2018, Nicolas Ferre wrote:

> On 15/08/2018 at 22:40, Sam Ravnborg wrote:
> > Hi Lee.
> > 
> > > > +
> > > > +static const struct mfd_cell lcdc_cells[] = {
> > > > +	{
> > > > +		.name = "atmel-lcdc-pwm",
> > > > +		.of_compatible = "atmel,lcdc-pwm",
> > > > +	},
> > > > +	{
> > > > +		.name = "atmel-lcdc-dc",
> > > > +		.of_compatible = "atmel,lcdc-display-controller",
> > > > +	},
> > > > +};
> > > 
> > > Will you be adding any more devices, or is this the entirety of the
> > > device?  If the latter, I suggest that this doesn't warrant being an
> > > MFD.
> > Thats it. And others agree with you that this is not a good approach.
> > So in v2 there will be no MFD.
> > 
> > Thanks for confirming that the non-mfd way is the better approach.
> 
> MFD approach would have had the benefit of keeping this driver series
> architecture close to the HLCD one. This would have been easier to
> understand and use one SoC or another one from the AT91 product line....

Yes, that is true.  They are very similar drivers.  Would it make
sense to use the same driver for both devices?

> Anyway, I'd wait for Boris' feedback for making a decision.
Boris Brezillon Aug. 24, 2018, 8:15 a.m. UTC | #7
Hi Lee,

On Wed, 15 Aug 2018 06:24:35 +0100
Lee Jones <lee.jones@linaro.org> wrote:

> > +static const struct mfd_cell lcdc_cells[] = {
> > +	{
> > +		.name = "atmel-lcdc-pwm",
> > +		.of_compatible = "atmel,lcdc-pwm",
> > +	},
> > +	{
> > +		.name = "atmel-lcdc-dc",
> > +		.of_compatible = "atmel,lcdc-display-controller",
> > +	},
> > +};  
> 
> Will you be adding any more devices, or is this the entirety of the
> device?  If the latter, I suggest that this doesn't warrant being an
> MFD.
> 

Is there a lower limit to define when an MFD is recommended, or is it
that you find a PWM (driving a backlight) and a display controller
close enough to be implemented in a single driver?

I personally prefer the separation we have today, because I can then
place the drivers where they belong (PWM subsystem and DRM subsystem)
and the respective maintainers know about these drivers.

Regards,

Boris
Boris Brezillon Aug. 24, 2018, 8:37 a.m. UTC | #8
On Thu, 16 Aug 2018 10:28:54 +0200
Nicolas Ferre <nicolas.ferre@microchip.com> wrote:

> On 15/08/2018 at 22:40, Sam Ravnborg wrote:
> > Hi Lee.
> >   
> >>> +
> >>> +static const struct mfd_cell lcdc_cells[] = {
> >>> +	{
> >>> +		.name = "atmel-lcdc-pwm",
> >>> +		.of_compatible = "atmel,lcdc-pwm",
> >>> +	},
> >>> +	{
> >>> +		.name = "atmel-lcdc-dc",
> >>> +		.of_compatible = "atmel,lcdc-display-controller",
> >>> +	},
> >>> +};  
> >>
> >> Will you be adding any more devices, or is this the entirety of the
> >> device?  If the latter, I suggest that this doesn't warrant being an
> >> MFD.  
> > Thats it. And others agree with you that this is not a good approach.
> > So in v2 there will be no MFD.
> > 
> > Thanks for confirming that the non-mfd way is the better approach.  
> 
> MFD approach would have had the benefit of keeping this driver series 
> architecture close to the HLCD one. This would have been easier to 
> understand and use one SoC or another one from the AT91 product line....
> 
> Anyway, I'd wait for Boris' feedback for making a decision.

If possible I'd like to keep the MFD approach, but let's see if we can
have a single node in the DT instead of one for the MFD and 2 child
nodes (for the display controller and the PWM).
Boris Brezillon Aug. 24, 2018, 8:48 a.m. UTC | #9
On Sun, 12 Aug 2018 20:46:25 +0200
Sam Ravnborg <sam@ravnborg.org> wrote:

> The LCDC IP used by some Atmel SOC's have a
>     multifunction device that include two sub-devices:
>     - pwm
>     - display controller
> 
> This mfd device provide a regmap that can be used by the
> sub-devices to safely access the registers.
> The mfd device also support the clock used by the
> LCDC IP + a bus clock that in some cases are required.
> 
> The driver is based on the atmel-hlcdc driver.

Looks like it's (almost?) the same logic. It's probably better to have
only one driver and just add new compatibles.

> 
> The Atmel SOC's are at91sam9261, at91sam9263 etc.
> 
> Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
> Cc: Lee Jones <lee.jones@linaro.org>
> Cc: Boris Brezillon <boris.brezillon@free-electrons.com>
> ---
>  drivers/mfd/Kconfig            |  10 +++
>  drivers/mfd/Makefile           |   1 +
>  drivers/mfd/atmel-lcdc.c       | 158 +++++++++++++++++++++++++++++++++++
>  include/linux/mfd/atmel-lcdc.h | 184 +++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 353 insertions(+)
>  create mode 100644 drivers/mfd/atmel-lcdc.c
>  create mode 100644 include/linux/mfd/atmel-lcdc.h
> 
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index b860eb5aa194..f4851f0f033f 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -121,6 +121,16 @@ config MFD_ATMEL_HLCDC
>  	  additional drivers must be enabled in order to use the
>  	  functionality of the device.
>  
> +config MFD_ATMEL_LCDC
> +	tristate "Atmel LCDC (LCD Controller)"
> +	select MFD_CORE
> +	depends on OF
> +	help
> +	  If you say yes here you get support for the LCDC block.
> +	  This driver provides common support for accessing the device,
> +	  additional drivers must be enabled in order to use the
> +	  functionality of the device.
> +
>  config MFD_ATMEL_SMC
>  	bool
>  	select MFD_SYSCON
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index e9fd20dba18d..dba8465e0d96 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -186,6 +186,7 @@ obj-$(CONFIG_MFD_TPS65090)	+= tps65090.o
>  obj-$(CONFIG_MFD_AAT2870_CORE)	+= aat2870-core.o
>  obj-$(CONFIG_MFD_ATMEL_FLEXCOM)	+= atmel-flexcom.o
>  obj-$(CONFIG_MFD_ATMEL_HLCDC)	+= atmel-hlcdc.o
> +obj-$(CONFIG_MFD_ATMEL_LCDC)	+= atmel-lcdc.o
>  obj-$(CONFIG_MFD_ATMEL_SMC)	+= atmel-smc.o
>  obj-$(CONFIG_MFD_INTEL_LPSS)	+= intel-lpss.o
>  obj-$(CONFIG_MFD_INTEL_LPSS_PCI)	+= intel-lpss-pci.o
> diff --git a/drivers/mfd/atmel-lcdc.c b/drivers/mfd/atmel-lcdc.c
> new file mode 100644
> index 000000000000..8928976bafca
> --- /dev/null
> +++ b/drivers/mfd/atmel-lcdc.c
> @@ -0,0 +1,158 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2018 Sam Ravnborg
> + *
> + * Author: Sam Ravnborg <sam@ravnborg.org>
> + *
> + * Based on atmel-hlcdc.c wich is:
> + * Copyright (C) 2014 Free Electrons
> + * Copyright (C) 2014 Atmel
> + * Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
> + */
> +
> +#include <linux/mfd/atmel-lcdc.h>
> +#include <linux/mfd/core.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +
> +#define ATMEL_LCDC_REG_MAX		(0x1000 - 0x4)
> +
> +struct lcdc_regmap {
> +	void __iomem *regs;
> +};
> +
> +static const struct mfd_cell lcdc_cells[] = {
> +	{
> +		.name = "atmel-lcdc-pwm",
> +		.of_compatible = "atmel,lcdc-pwm",
> +	},
> +	{
> +		.name = "atmel-lcdc-dc",
> +		.of_compatible = "atmel,lcdc-display-controller",
> +	},
> +};
> +
> +static int regmap_lcdc_reg_write(void *context, unsigned int reg,
> +				 unsigned int val)
> +{
> +	struct lcdc_regmap *regmap = context;
> +
> +	writel(val, regmap->regs + reg);
> +
> +	return 0;
> +}
> +
> +static int regmap_lcdc_reg_read(void *context, unsigned int reg,
> +				unsigned int *val)
> +{
> +	struct lcdc_regmap *regmap = context;
> +
> +	*val = readl(regmap->regs + reg);
> +
> +	return 0;
> +}
> +
> +static const struct regmap_config lcdc_regmap_config = {
> +	.reg_bits = 32,
> +	.val_bits = 32,
> +	.reg_stride = 4,
> +	.max_register = ATMEL_LCDC_REG_MAX,
> +	.reg_write = regmap_lcdc_reg_write,
> +	.reg_read = regmap_lcdc_reg_read,
> +	.fast_io = true,
> +};
> +
> +static int lcdc_probe(struct platform_device *pdev)
> +{
> +	struct atmel_mfd_lcdc *lcdc;
> +	struct lcdc_regmap *regmap;
> +	struct resource *res;
> +	struct device *dev;
> +	int ret;
> +
> +	dev = &pdev->dev;
> +
> +	regmap = devm_kzalloc(dev, sizeof(*regmap), GFP_KERNEL);
> +	if (!regmap)
> +		return -ENOMEM;
> +
> +	lcdc = devm_kzalloc(dev, sizeof(*lcdc), GFP_KERNEL);
> +	if (!lcdc)
> +		return -ENOMEM;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	regmap->regs = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(regmap->regs)) {
> +		dev_err(dev, "Failed to allocate IO mem (%ld)\n",
> +			PTR_ERR(regmap->regs));
> +		return PTR_ERR(regmap->regs);
> +	}
> +
> +	lcdc->irq = platform_get_irq(pdev, 0);
> +	if (lcdc->irq < 0) {
> +		dev_err(dev, "Failed to get irq (%d)\n", lcdc->irq);
> +		return lcdc->irq;
> +	}
> +
> +	lcdc->lcdc_clk = devm_clk_get(dev, "lcdc_clk");
> +	if (IS_ERR(lcdc->lcdc_clk)) {
> +		dev_err(dev, "failed to get lcdc clock (%ld)\n",
> +			PTR_ERR(lcdc->lcdc_clk));
> +		return PTR_ERR(lcdc->lcdc_clk);
> +	}
> +
> +	lcdc->bus_clk = devm_clk_get(dev, "hclk");
> +	if (IS_ERR(lcdc->bus_clk)) {
> +		dev_err(dev, "failed to get bus clock (%ld)\n",
> +			PTR_ERR(lcdc->bus_clk));
> +		return PTR_ERR(lcdc->bus_clk);
> +	}
> +
> +	lcdc->regmap = devm_regmap_init(dev, NULL, regmap,
> +					 &lcdc_regmap_config);
> +	if (IS_ERR(lcdc->regmap)) {
> +		dev_err(dev, "Failed to init regmap (%ld)\n",
> +			PTR_ERR(lcdc->regmap));
> +		return PTR_ERR(lcdc->regmap);
> +	}
> +
> +	dev_set_drvdata(dev, lcdc);
> +
> +	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
> +				   lcdc_cells, ARRAY_SIZE(lcdc_cells),
> +				   NULL, 0, NULL);
> +	if (ret < 0)
> +		dev_err(dev, "Failed to add %d mfd devices (%d)\n",
> +			ARRAY_SIZE(lcdc_cells), ret);
> +
> +	return ret;
> +}
> +
> +static const struct of_device_id lcdc_match[] = {
> +	{ .compatible = "atmel,at91sam9261-lcdc-mfd" },
> +	{ .compatible = "atmel,at91sam9263-lcdc-mfd" },
> +	{ .compatible = "atmel,at91sam9g10-lcdc-mfd" },
> +	{ .compatible = "atmel,at91sam9g45-lcdc-mfd" },
> +	{ .compatible = "atmel,at91sam9g46-lcdc-mfd" },
> +	{ .compatible = "atmel,at91sam9m10-lcdc-mfd" },
> +	{ .compatible = "atmel,at91sam9m11-lcdc-mfd" },
> +	{ .compatible = "atmel,at91sam9rl-lcdc-mfd" },
> +	{ /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, lcdc_match);
> +
> +static struct platform_driver lcdc_driver = {
> +	.probe = lcdc_probe,
> +	.driver = {
> +		.name = "atmel-lcdc",
> +		.of_match_table = lcdc_match,
> +	},
> +};
> +module_platform_driver(lcdc_driver);
> +
> +MODULE_ALIAS("platform:atmel-lcdc");
> +MODULE_AUTHOR("Sam Ravnborg <sam@ravnborg.org>");
> +MODULE_DESCRIPTION("Atmel LCDC mfd driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/mfd/atmel-lcdc.h b/include/linux/mfd/atmel-lcdc.h
> new file mode 100644
> index 000000000000..fdab269baa8e
> --- /dev/null
> +++ b/include/linux/mfd/atmel-lcdc.h
> @@ -0,0 +1,184 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2018 Sam Ravnborg
> + *
> + * Author: Sam Ravnborg <sam@ravnborg.org>
> + */
> +
> +#ifndef __LINUX_MFD_LCDC_H
> +#define __LINUX_MFD_LCDC_H
> +
> +#include <linux/regmap.h>
> +#include <linux/clk.h>
> +
> +/**
> + * Structure shared by the Atmel LCD Controller device and its sub-devices.
> + *
> + * @regmap: register map used to access LCDC IP registers
> + * @lcdc_clk: the lcd controller peripheral clock
> + * @bus_clk: the bus clock clock (often the same as lcdc_clk)
> + * @irq: the lcdc irq
> + */
> +struct atmel_mfd_lcdc {
> +	struct regmap *regmap;
> +	struct clk *lcdc_clk;
> +	struct clk *bus_clk;
> +	int irq;
> +};
> +
> +#define ATMEL_LCDC_DMABADDR1	0x00
> +#define ATMEL_LCDC_DMABADDR2	0x04
> +#define ATMEL_LCDC_DMAFRMPT1	0x08
> +#define ATMEL_LCDC_DMAFRMPT2	0x0c
> +#define ATMEL_LCDC_DMAFRMADD1	0x10
> +#define ATMEL_LCDC_DMAFRMADD2	0x14
> +
> +#define ATMEL_LCDC_DMAFRMCFG	0x18
> +#define	ATMEL_LCDC_FRSIZE	(0x7fffff <<  0)
> +#define	ATMEL_LCDC_BLENGTH_OFFSET	24
> +#define	ATMEL_LCDC_BLENGTH	(0x7f     << ATMEL_LCDC_BLENGTH_OFFSET)
> +
> +#define ATMEL_LCDC_DMACON	0x1c
> +#define	ATMEL_LCDC_DMAEN	(0x1 << 0)
> +#define	ATMEL_LCDC_DMARST	(0x1 << 1)
> +#define	ATMEL_LCDC_DMABUSY	(0x1 << 2)
> +#define		ATMEL_LCDC_DMAUPDT	(0x1 << 3)
> +#define		ATMEL_LCDC_DMA2DEN	(0x1 << 4)
> +
> +#define ATMEL_LCDC_DMA2DCFG	0x20
> +#define		ATMEL_LCDC_ADDRINC_OFFSET	0
> +#define		ATMEL_LCDC_ADDRINC		(0xffff)
> +#define		ATMEL_LCDC_PIXELOFF_OFFSET	24
> +#define		ATMEL_LCDC_PIXELOFF		(0x1f << 24)
> +
> +#define ATMEL_LCDC_LCDCON1	0x0800
> +#define	ATMEL_LCDC_BYPASS	(1     <<  0)
> +#define	ATMEL_LCDC_CLKVAL_OFFSET	12
> +#define	ATMEL_LCDC_CLKVAL	(0x1ff << ATMEL_LCDC_CLKVAL_OFFSET)
> +#define	ATMEL_LCDC_LINCNT	(0x7ff << 21)
> +
> +#define ATMEL_LCDC_LCDCON2	0x0804
> +#define	ATMEL_LCDC_DISTYPE	(3 << 0)
> +#define		ATMEL_LCDC_DISTYPE_STNMONO	(0 << 0)
> +#define		ATMEL_LCDC_DISTYPE_STNCOLOR	(1 << 0)
> +#define		ATMEL_LCDC_DISTYPE_TFT		(2 << 0)
> +#define	ATMEL_LCDC_SCANMOD	(1 << 2)
> +#define		ATMEL_LCDC_SCANMOD_SINGLE	(0 << 2)
> +#define		ATMEL_LCDC_SCANMOD_DUAL		(1 << 2)
> +#define	ATMEL_LCDC_IFWIDTH	(3 << 3)
> +#define		ATMEL_LCDC_IFWIDTH_4		(0 << 3)
> +#define		ATMEL_LCDC_IFWIDTH_8		(1 << 3)
> +#define		ATMEL_LCDC_IFWIDTH_16		(2 << 3)
> +#define	ATMEL_LCDC_PIXELSIZE	(7 << 5)
> +#define		ATMEL_LCDC_PIXELSIZE_1		(0 << 5)
> +#define		ATMEL_LCDC_PIXELSIZE_2		(1 << 5)
> +#define		ATMEL_LCDC_PIXELSIZE_4		(2 << 5)
> +#define		ATMEL_LCDC_PIXELSIZE_8		(3 << 5)
> +#define		ATMEL_LCDC_PIXELSIZE_16		(4 << 5)
> +#define		ATMEL_LCDC_PIXELSIZE_24		(5 << 5)
> +#define		ATMEL_LCDC_PIXELSIZE_32		(6 << 5)
> +#define	ATMEL_LCDC_INVVD	(1 << 8)
> +#define		ATMEL_LCDC_INVVD_NORMAL		(0 << 8)
> +#define		ATMEL_LCDC_INVVD_INVERTED	(1 << 8)
> +#define	ATMEL_LCDC_INVFRAME	(1 << 9)
> +#define		ATMEL_LCDC_INVFRAME_NORMAL	(0 << 9)
> +#define		ATMEL_LCDC_INVFRAME_INVERTED	(1 << 9)
> +#define	ATMEL_LCDC_INVLINE	(1 << 10)
> +#define		ATMEL_LCDC_INVLINE_NORMAL	(0 << 10)
> +#define		ATMEL_LCDC_INVLINE_INVERTED	(1 << 10)
> +#define	ATMEL_LCDC_INVCLK	(1 << 11)
> +#define		ATMEL_LCDC_INVCLK_NORMAL	(0 << 11)
> +#define		ATMEL_LCDC_INVCLK_INVERTED	(1 << 11)
> +#define	ATMEL_LCDC_INVDVAL	(1 << 12)
> +#define		ATMEL_LCDC_INVDVAL_NORMAL	(0 << 12)
> +#define		ATMEL_LCDC_INVDVAL_INVERTED	(1 << 12)
> +#define	ATMEL_LCDC_CLKMOD	(1 << 15)
> +#define		ATMEL_LCDC_CLKMOD_ACTIVEDISPLAY	(0 << 15)
> +#define		ATMEL_LCDC_CLKMOD_ALWAYSACTIVE	(1 << 15)
> +#define	ATMEL_LCDC_MEMOR	(1 << 31)
> +#define		ATMEL_LCDC_MEMOR_BIG		(0 << 31)
> +#define		ATMEL_LCDC_MEMOR_LITTLE		(1 << 31)
> +
> +#define ATMEL_LCDC_TIM1		0x0808
> +#define	ATMEL_LCDC_VFP_OFFSET		0
> +#define	ATMEL_LCDC_VFP		(0xffU <<  0)
> +#define	ATMEL_LCDC_VBP_OFFSET		8
> +#define	ATMEL_LCDC_VBP		(0xffU <<  ATMEL_LCDC_VBP_OFFSET)
> +#define	ATMEL_LCDC_VPW_OFFSET		16
> +#define	ATMEL_LCDC_VPW		(0x3fU << ATMEL_LCDC_VPW_OFFSET)
> +#define	ATMEL_LCDC_VHDLY_OFFSET		24
> +#define	ATMEL_LCDC_VHDLY	(0xfU  << ATMEL_LCDC_VHDLY_OFFSET)
> +
> +#define ATMEL_LCDC_TIM2		0x080c
> +#define	ATMEL_LCDC_HBP_OFFSET		0
> +#define	ATMEL_LCDC_HBP		(0xffU  <<  0)
> +#define	ATMEL_LCDC_HPW_OFFSET		8
> +#define	ATMEL_LCDC_HPW		(0x3fU  <<  ATMEL_LCDC_HPW_OFFSET)
> +#define	ATMEL_LCDC_HFP_OFFSET		21
> +#define	ATMEL_LCDC_HFP		(0x7ffU << ATMEL_LCDC_HFP_OFFSET)
> +
> +#define ATMEL_LCDC_LCDFRMCFG	0x0810
> +#define	ATMEL_LCDC_LINEVAL	(0x7ff <<  0)
> +#define	ATMEL_LCDC_HOZVAL_OFFSET	21
> +#define	ATMEL_LCDC_HOZVAL	(0x7ff << ATMEL_LCDC_HOZVAL_OFFSET)
> +
> +#define ATMEL_LCDC_FIFO		0x0814
> +#define	ATMEL_LCDC_FIFOTH	(0xffff)
> +
> +#define ATMEL_LCDC_MVAL		0x0818
> +
> +#define ATMEL_LCDC_DP1_2	0x081c
> +#define ATMEL_LCDC_DP4_7	0x0820
> +#define ATMEL_LCDC_DP3_5	0x0824
> +#define ATMEL_LCDC_DP2_3	0x0828
> +#define ATMEL_LCDC_DP5_7	0x082c
> +#define ATMEL_LCDC_DP3_4	0x0830
> +#define ATMEL_LCDC_DP4_5	0x0834
> +#define ATMEL_LCDC_DP6_7	0x0838
> +#define	ATMEL_LCDC_DP1_2_VAL	(0xff)
> +#define	ATMEL_LCDC_DP4_7_VAL	(0xfffffff)
> +#define	ATMEL_LCDC_DP3_5_VAL	(0xfffff)
> +#define	ATMEL_LCDC_DP2_3_VAL	(0xfff)
> +#define	ATMEL_LCDC_DP5_7_VAL	(0xfffffff)
> +#define	ATMEL_LCDC_DP3_4_VAL	(0xffff)
> +#define	ATMEL_LCDC_DP4_5_VAL	(0xfffff)
> +#define	ATMEL_LCDC_DP6_7_VAL	(0xfffffff)
> +
> +#define ATMEL_LCDC_PWRCON	0x083c
> +#define	ATMEL_LCDC_PWR		(1    <<  0)
> +#define	ATMEL_LCDC_GUARDT_OFFSET	1
> +#define	ATMEL_LCDC_GUARDT	(0x7f <<  ATMEL_LCDC_GUARDT_OFFSET)
> +#define	ATMEL_LCDC_BUSY		(1    << 31)
> +
> +#define ATMEL_LCDC_CONTRAST_CTR	0x0840
> +#define	ATMEL_LCDC_PS		(3 << 0)
> +#define		ATMEL_LCDC_PS_DIV1		(0 << 0)
> +#define		ATMEL_LCDC_PS_DIV2		(1 << 0)
> +#define		ATMEL_LCDC_PS_DIV4		(2 << 0)
> +#define		ATMEL_LCDC_PS_DIV8		(3 << 0)
> +#define	ATMEL_LCDC_POL		(1 << 2)
> +#define		ATMEL_LCDC_POL_NEGATIVE		(0 << 2)
> +#define		ATMEL_LCDC_POL_POSITIVE		(1 << 2)
> +#define	ATMEL_LCDC_ENA		(1 << 3)
> +#define		ATMEL_LCDC_ENA_PWMDISABLE	(0 << 3)
> +#define		ATMEL_LCDC_ENA_PWMENABLE	(1 << 3)
> +
> +#define ATMEL_LCDC_CONTRAST_VAL	0x0844
> +#define	ATMEL_LCDC_CVAL	(0xff)
> +
> +#define ATMEL_LCDC_IER		0x0848
> +#define ATMEL_LCDC_IDR		0x084c
> +#define ATMEL_LCDC_IMR		0x0850
> +#define ATMEL_LCDC_ISR		0x0854
> +#define ATMEL_LCDC_ICR		0x0858
> +#define	ATMEL_LCDC_LNI		(1 << 0)
> +#define	ATMEL_LCDC_LSTLNI	(1 << 1)
> +#define	ATMEL_LCDC_EOFI		(1 << 2)
> +#define	ATMEL_LCDC_UFLWI	(1 << 4)
> +#define	ATMEL_LCDC_OWRI		(1 << 5)
> +#define	ATMEL_LCDC_MERI		(1 << 6)
> +
> +#define ATMEL_LCDC_LUT(n)	(0x0c00 + ((n)*4))
> +#define ATMEL_LCDC_LUT_SIZE	256
> +
> +#endif /* __LINUX_MFD_LCDC_H */
Lee Jones Aug. 24, 2018, 10:58 a.m. UTC | #10
On Fri, 24 Aug 2018, Boris Brezillon wrote:

> Hi Lee,
> 
> On Wed, 15 Aug 2018 06:24:35 +0100
> Lee Jones <lee.jones@linaro.org> wrote:
> 
> > > +static const struct mfd_cell lcdc_cells[] = {
> > > +	{
> > > +		.name = "atmel-lcdc-pwm",
> > > +		.of_compatible = "atmel,lcdc-pwm",
> > > +	},
> > > +	{
> > > +		.name = "atmel-lcdc-dc",
> > > +		.of_compatible = "atmel,lcdc-display-controller",
> > > +	},
> > > +};  
> > 
> > Will you be adding any more devices, or is this the entirety of the
> > device?  If the latter, I suggest that this doesn't warrant being an
> > MFD.
> > 
> 
> Is there a lower limit to define when an MFD is recommended, or is it
> that you find a PWM (driving a backlight) and a display controller
> close enough to be implemented in a single driver?

I was erring towards that latter.

> I personally prefer the separation we have today, because I can then
> place the drivers where they belong (PWM subsystem and DRM subsystem)
> and the respective maintainers know about these drivers.

Yes separation is good a good thing.  Not placing them in MFD and
having the individual parts reside in the appropriate subsystems are
not mutually exclusive though.  I assume the Display Controller is
higher ranking than the PWM device right?  Seeing as they are so
closely bound i.e. the DC can't operated with the PWM, it would be
justifiable to register the PWM from the DC.

Of course if there is complicated set-up to be done, lots of devices
are involved or there are shared functions between them, then that is
where MFD usually steps in.

However, since there is a very similar device already in MFD, I
suggest you simply extend it to add support for this new device and
have done.
diff mbox series

Patch

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index b860eb5aa194..f4851f0f033f 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -121,6 +121,16 @@  config MFD_ATMEL_HLCDC
 	  additional drivers must be enabled in order to use the
 	  functionality of the device.
 
+config MFD_ATMEL_LCDC
+	tristate "Atmel LCDC (LCD Controller)"
+	select MFD_CORE
+	depends on OF
+	help
+	  If you say yes here you get support for the LCDC block.
+	  This driver provides common support for accessing the device,
+	  additional drivers must be enabled in order to use the
+	  functionality of the device.
+
 config MFD_ATMEL_SMC
 	bool
 	select MFD_SYSCON
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e9fd20dba18d..dba8465e0d96 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -186,6 +186,7 @@  obj-$(CONFIG_MFD_TPS65090)	+= tps65090.o
 obj-$(CONFIG_MFD_AAT2870_CORE)	+= aat2870-core.o
 obj-$(CONFIG_MFD_ATMEL_FLEXCOM)	+= atmel-flexcom.o
 obj-$(CONFIG_MFD_ATMEL_HLCDC)	+= atmel-hlcdc.o
+obj-$(CONFIG_MFD_ATMEL_LCDC)	+= atmel-lcdc.o
 obj-$(CONFIG_MFD_ATMEL_SMC)	+= atmel-smc.o
 obj-$(CONFIG_MFD_INTEL_LPSS)	+= intel-lpss.o
 obj-$(CONFIG_MFD_INTEL_LPSS_PCI)	+= intel-lpss-pci.o
diff --git a/drivers/mfd/atmel-lcdc.c b/drivers/mfd/atmel-lcdc.c
new file mode 100644
index 000000000000..8928976bafca
--- /dev/null
+++ b/drivers/mfd/atmel-lcdc.c
@@ -0,0 +1,158 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Sam Ravnborg
+ *
+ * Author: Sam Ravnborg <sam@ravnborg.org>
+ *
+ * Based on atmel-hlcdc.c wich is:
+ * Copyright (C) 2014 Free Electrons
+ * Copyright (C) 2014 Atmel
+ * Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
+ */
+
+#include <linux/mfd/atmel-lcdc.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#define ATMEL_LCDC_REG_MAX		(0x1000 - 0x4)
+
+struct lcdc_regmap {
+	void __iomem *regs;
+};
+
+static const struct mfd_cell lcdc_cells[] = {
+	{
+		.name = "atmel-lcdc-pwm",
+		.of_compatible = "atmel,lcdc-pwm",
+	},
+	{
+		.name = "atmel-lcdc-dc",
+		.of_compatible = "atmel,lcdc-display-controller",
+	},
+};
+
+static int regmap_lcdc_reg_write(void *context, unsigned int reg,
+				 unsigned int val)
+{
+	struct lcdc_regmap *regmap = context;
+
+	writel(val, regmap->regs + reg);
+
+	return 0;
+}
+
+static int regmap_lcdc_reg_read(void *context, unsigned int reg,
+				unsigned int *val)
+{
+	struct lcdc_regmap *regmap = context;
+
+	*val = readl(regmap->regs + reg);
+
+	return 0;
+}
+
+static const struct regmap_config lcdc_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.max_register = ATMEL_LCDC_REG_MAX,
+	.reg_write = regmap_lcdc_reg_write,
+	.reg_read = regmap_lcdc_reg_read,
+	.fast_io = true,
+};
+
+static int lcdc_probe(struct platform_device *pdev)
+{
+	struct atmel_mfd_lcdc *lcdc;
+	struct lcdc_regmap *regmap;
+	struct resource *res;
+	struct device *dev;
+	int ret;
+
+	dev = &pdev->dev;
+
+	regmap = devm_kzalloc(dev, sizeof(*regmap), GFP_KERNEL);
+	if (!regmap)
+		return -ENOMEM;
+
+	lcdc = devm_kzalloc(dev, sizeof(*lcdc), GFP_KERNEL);
+	if (!lcdc)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	regmap->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(regmap->regs)) {
+		dev_err(dev, "Failed to allocate IO mem (%ld)\n",
+			PTR_ERR(regmap->regs));
+		return PTR_ERR(regmap->regs);
+	}
+
+	lcdc->irq = platform_get_irq(pdev, 0);
+	if (lcdc->irq < 0) {
+		dev_err(dev, "Failed to get irq (%d)\n", lcdc->irq);
+		return lcdc->irq;
+	}
+
+	lcdc->lcdc_clk = devm_clk_get(dev, "lcdc_clk");
+	if (IS_ERR(lcdc->lcdc_clk)) {
+		dev_err(dev, "failed to get lcdc clock (%ld)\n",
+			PTR_ERR(lcdc->lcdc_clk));
+		return PTR_ERR(lcdc->lcdc_clk);
+	}
+
+	lcdc->bus_clk = devm_clk_get(dev, "hclk");
+	if (IS_ERR(lcdc->bus_clk)) {
+		dev_err(dev, "failed to get bus clock (%ld)\n",
+			PTR_ERR(lcdc->bus_clk));
+		return PTR_ERR(lcdc->bus_clk);
+	}
+
+	lcdc->regmap = devm_regmap_init(dev, NULL, regmap,
+					 &lcdc_regmap_config);
+	if (IS_ERR(lcdc->regmap)) {
+		dev_err(dev, "Failed to init regmap (%ld)\n",
+			PTR_ERR(lcdc->regmap));
+		return PTR_ERR(lcdc->regmap);
+	}
+
+	dev_set_drvdata(dev, lcdc);
+
+	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
+				   lcdc_cells, ARRAY_SIZE(lcdc_cells),
+				   NULL, 0, NULL);
+	if (ret < 0)
+		dev_err(dev, "Failed to add %d mfd devices (%d)\n",
+			ARRAY_SIZE(lcdc_cells), ret);
+
+	return ret;
+}
+
+static const struct of_device_id lcdc_match[] = {
+	{ .compatible = "atmel,at91sam9261-lcdc-mfd" },
+	{ .compatible = "atmel,at91sam9263-lcdc-mfd" },
+	{ .compatible = "atmel,at91sam9g10-lcdc-mfd" },
+	{ .compatible = "atmel,at91sam9g45-lcdc-mfd" },
+	{ .compatible = "atmel,at91sam9g46-lcdc-mfd" },
+	{ .compatible = "atmel,at91sam9m10-lcdc-mfd" },
+	{ .compatible = "atmel,at91sam9m11-lcdc-mfd" },
+	{ .compatible = "atmel,at91sam9rl-lcdc-mfd" },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, lcdc_match);
+
+static struct platform_driver lcdc_driver = {
+	.probe = lcdc_probe,
+	.driver = {
+		.name = "atmel-lcdc",
+		.of_match_table = lcdc_match,
+	},
+};
+module_platform_driver(lcdc_driver);
+
+MODULE_ALIAS("platform:atmel-lcdc");
+MODULE_AUTHOR("Sam Ravnborg <sam@ravnborg.org>");
+MODULE_DESCRIPTION("Atmel LCDC mfd driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/atmel-lcdc.h b/include/linux/mfd/atmel-lcdc.h
new file mode 100644
index 000000000000..fdab269baa8e
--- /dev/null
+++ b/include/linux/mfd/atmel-lcdc.h
@@ -0,0 +1,184 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Sam Ravnborg
+ *
+ * Author: Sam Ravnborg <sam@ravnborg.org>
+ */
+
+#ifndef __LINUX_MFD_LCDC_H
+#define __LINUX_MFD_LCDC_H
+
+#include <linux/regmap.h>
+#include <linux/clk.h>
+
+/**
+ * Structure shared by the Atmel LCD Controller device and its sub-devices.
+ *
+ * @regmap: register map used to access LCDC IP registers
+ * @lcdc_clk: the lcd controller peripheral clock
+ * @bus_clk: the bus clock clock (often the same as lcdc_clk)
+ * @irq: the lcdc irq
+ */
+struct atmel_mfd_lcdc {
+	struct regmap *regmap;
+	struct clk *lcdc_clk;
+	struct clk *bus_clk;
+	int irq;
+};
+
+#define ATMEL_LCDC_DMABADDR1	0x00
+#define ATMEL_LCDC_DMABADDR2	0x04
+#define ATMEL_LCDC_DMAFRMPT1	0x08
+#define ATMEL_LCDC_DMAFRMPT2	0x0c
+#define ATMEL_LCDC_DMAFRMADD1	0x10
+#define ATMEL_LCDC_DMAFRMADD2	0x14
+
+#define ATMEL_LCDC_DMAFRMCFG	0x18
+#define	ATMEL_LCDC_FRSIZE	(0x7fffff <<  0)
+#define	ATMEL_LCDC_BLENGTH_OFFSET	24
+#define	ATMEL_LCDC_BLENGTH	(0x7f     << ATMEL_LCDC_BLENGTH_OFFSET)
+
+#define ATMEL_LCDC_DMACON	0x1c
+#define	ATMEL_LCDC_DMAEN	(0x1 << 0)
+#define	ATMEL_LCDC_DMARST	(0x1 << 1)
+#define	ATMEL_LCDC_DMABUSY	(0x1 << 2)
+#define		ATMEL_LCDC_DMAUPDT	(0x1 << 3)
+#define		ATMEL_LCDC_DMA2DEN	(0x1 << 4)
+
+#define ATMEL_LCDC_DMA2DCFG	0x20
+#define		ATMEL_LCDC_ADDRINC_OFFSET	0
+#define		ATMEL_LCDC_ADDRINC		(0xffff)
+#define		ATMEL_LCDC_PIXELOFF_OFFSET	24
+#define		ATMEL_LCDC_PIXELOFF		(0x1f << 24)
+
+#define ATMEL_LCDC_LCDCON1	0x0800
+#define	ATMEL_LCDC_BYPASS	(1     <<  0)
+#define	ATMEL_LCDC_CLKVAL_OFFSET	12
+#define	ATMEL_LCDC_CLKVAL	(0x1ff << ATMEL_LCDC_CLKVAL_OFFSET)
+#define	ATMEL_LCDC_LINCNT	(0x7ff << 21)
+
+#define ATMEL_LCDC_LCDCON2	0x0804
+#define	ATMEL_LCDC_DISTYPE	(3 << 0)
+#define		ATMEL_LCDC_DISTYPE_STNMONO	(0 << 0)
+#define		ATMEL_LCDC_DISTYPE_STNCOLOR	(1 << 0)
+#define		ATMEL_LCDC_DISTYPE_TFT		(2 << 0)
+#define	ATMEL_LCDC_SCANMOD	(1 << 2)
+#define		ATMEL_LCDC_SCANMOD_SINGLE	(0 << 2)
+#define		ATMEL_LCDC_SCANMOD_DUAL		(1 << 2)
+#define	ATMEL_LCDC_IFWIDTH	(3 << 3)
+#define		ATMEL_LCDC_IFWIDTH_4		(0 << 3)
+#define		ATMEL_LCDC_IFWIDTH_8		(1 << 3)
+#define		ATMEL_LCDC_IFWIDTH_16		(2 << 3)
+#define	ATMEL_LCDC_PIXELSIZE	(7 << 5)
+#define		ATMEL_LCDC_PIXELSIZE_1		(0 << 5)
+#define		ATMEL_LCDC_PIXELSIZE_2		(1 << 5)
+#define		ATMEL_LCDC_PIXELSIZE_4		(2 << 5)
+#define		ATMEL_LCDC_PIXELSIZE_8		(3 << 5)
+#define		ATMEL_LCDC_PIXELSIZE_16		(4 << 5)
+#define		ATMEL_LCDC_PIXELSIZE_24		(5 << 5)
+#define		ATMEL_LCDC_PIXELSIZE_32		(6 << 5)
+#define	ATMEL_LCDC_INVVD	(1 << 8)
+#define		ATMEL_LCDC_INVVD_NORMAL		(0 << 8)
+#define		ATMEL_LCDC_INVVD_INVERTED	(1 << 8)
+#define	ATMEL_LCDC_INVFRAME	(1 << 9)
+#define		ATMEL_LCDC_INVFRAME_NORMAL	(0 << 9)
+#define		ATMEL_LCDC_INVFRAME_INVERTED	(1 << 9)
+#define	ATMEL_LCDC_INVLINE	(1 << 10)
+#define		ATMEL_LCDC_INVLINE_NORMAL	(0 << 10)
+#define		ATMEL_LCDC_INVLINE_INVERTED	(1 << 10)
+#define	ATMEL_LCDC_INVCLK	(1 << 11)
+#define		ATMEL_LCDC_INVCLK_NORMAL	(0 << 11)
+#define		ATMEL_LCDC_INVCLK_INVERTED	(1 << 11)
+#define	ATMEL_LCDC_INVDVAL	(1 << 12)
+#define		ATMEL_LCDC_INVDVAL_NORMAL	(0 << 12)
+#define		ATMEL_LCDC_INVDVAL_INVERTED	(1 << 12)
+#define	ATMEL_LCDC_CLKMOD	(1 << 15)
+#define		ATMEL_LCDC_CLKMOD_ACTIVEDISPLAY	(0 << 15)
+#define		ATMEL_LCDC_CLKMOD_ALWAYSACTIVE	(1 << 15)
+#define	ATMEL_LCDC_MEMOR	(1 << 31)
+#define		ATMEL_LCDC_MEMOR_BIG		(0 << 31)
+#define		ATMEL_LCDC_MEMOR_LITTLE		(1 << 31)
+
+#define ATMEL_LCDC_TIM1		0x0808
+#define	ATMEL_LCDC_VFP_OFFSET		0
+#define	ATMEL_LCDC_VFP		(0xffU <<  0)
+#define	ATMEL_LCDC_VBP_OFFSET		8
+#define	ATMEL_LCDC_VBP		(0xffU <<  ATMEL_LCDC_VBP_OFFSET)
+#define	ATMEL_LCDC_VPW_OFFSET		16
+#define	ATMEL_LCDC_VPW		(0x3fU << ATMEL_LCDC_VPW_OFFSET)
+#define	ATMEL_LCDC_VHDLY_OFFSET		24
+#define	ATMEL_LCDC_VHDLY	(0xfU  << ATMEL_LCDC_VHDLY_OFFSET)
+
+#define ATMEL_LCDC_TIM2		0x080c
+#define	ATMEL_LCDC_HBP_OFFSET		0
+#define	ATMEL_LCDC_HBP		(0xffU  <<  0)
+#define	ATMEL_LCDC_HPW_OFFSET		8
+#define	ATMEL_LCDC_HPW		(0x3fU  <<  ATMEL_LCDC_HPW_OFFSET)
+#define	ATMEL_LCDC_HFP_OFFSET		21
+#define	ATMEL_LCDC_HFP		(0x7ffU << ATMEL_LCDC_HFP_OFFSET)
+
+#define ATMEL_LCDC_LCDFRMCFG	0x0810
+#define	ATMEL_LCDC_LINEVAL	(0x7ff <<  0)
+#define	ATMEL_LCDC_HOZVAL_OFFSET	21
+#define	ATMEL_LCDC_HOZVAL	(0x7ff << ATMEL_LCDC_HOZVAL_OFFSET)
+
+#define ATMEL_LCDC_FIFO		0x0814
+#define	ATMEL_LCDC_FIFOTH	(0xffff)
+
+#define ATMEL_LCDC_MVAL		0x0818
+
+#define ATMEL_LCDC_DP1_2	0x081c
+#define ATMEL_LCDC_DP4_7	0x0820
+#define ATMEL_LCDC_DP3_5	0x0824
+#define ATMEL_LCDC_DP2_3	0x0828
+#define ATMEL_LCDC_DP5_7	0x082c
+#define ATMEL_LCDC_DP3_4	0x0830
+#define ATMEL_LCDC_DP4_5	0x0834
+#define ATMEL_LCDC_DP6_7	0x0838
+#define	ATMEL_LCDC_DP1_2_VAL	(0xff)
+#define	ATMEL_LCDC_DP4_7_VAL	(0xfffffff)
+#define	ATMEL_LCDC_DP3_5_VAL	(0xfffff)
+#define	ATMEL_LCDC_DP2_3_VAL	(0xfff)
+#define	ATMEL_LCDC_DP5_7_VAL	(0xfffffff)
+#define	ATMEL_LCDC_DP3_4_VAL	(0xffff)
+#define	ATMEL_LCDC_DP4_5_VAL	(0xfffff)
+#define	ATMEL_LCDC_DP6_7_VAL	(0xfffffff)
+
+#define ATMEL_LCDC_PWRCON	0x083c
+#define	ATMEL_LCDC_PWR		(1    <<  0)
+#define	ATMEL_LCDC_GUARDT_OFFSET	1
+#define	ATMEL_LCDC_GUARDT	(0x7f <<  ATMEL_LCDC_GUARDT_OFFSET)
+#define	ATMEL_LCDC_BUSY		(1    << 31)
+
+#define ATMEL_LCDC_CONTRAST_CTR	0x0840
+#define	ATMEL_LCDC_PS		(3 << 0)
+#define		ATMEL_LCDC_PS_DIV1		(0 << 0)
+#define		ATMEL_LCDC_PS_DIV2		(1 << 0)
+#define		ATMEL_LCDC_PS_DIV4		(2 << 0)
+#define		ATMEL_LCDC_PS_DIV8		(3 << 0)
+#define	ATMEL_LCDC_POL		(1 << 2)
+#define		ATMEL_LCDC_POL_NEGATIVE		(0 << 2)
+#define		ATMEL_LCDC_POL_POSITIVE		(1 << 2)
+#define	ATMEL_LCDC_ENA		(1 << 3)
+#define		ATMEL_LCDC_ENA_PWMDISABLE	(0 << 3)
+#define		ATMEL_LCDC_ENA_PWMENABLE	(1 << 3)
+
+#define ATMEL_LCDC_CONTRAST_VAL	0x0844
+#define	ATMEL_LCDC_CVAL	(0xff)
+
+#define ATMEL_LCDC_IER		0x0848
+#define ATMEL_LCDC_IDR		0x084c
+#define ATMEL_LCDC_IMR		0x0850
+#define ATMEL_LCDC_ISR		0x0854
+#define ATMEL_LCDC_ICR		0x0858
+#define	ATMEL_LCDC_LNI		(1 << 0)
+#define	ATMEL_LCDC_LSTLNI	(1 << 1)
+#define	ATMEL_LCDC_EOFI		(1 << 2)
+#define	ATMEL_LCDC_UFLWI	(1 << 4)
+#define	ATMEL_LCDC_OWRI		(1 << 5)
+#define	ATMEL_LCDC_MERI		(1 << 6)
+
+#define ATMEL_LCDC_LUT(n)	(0x0c00 + ((n)*4))
+#define ATMEL_LCDC_LUT_SIZE	256
+
+#endif /* __LINUX_MFD_LCDC_H */