All of lore.kernel.org
 help / color / mirror / Atom feed
From: Heiko Schocher <hs@denx.de>
To: u-boot@lists.denx.de
Subject: [RFC PATCH 03/10] i2c: mmc: add nexell driver (gpio, i2c, mmc, pwm)
Date: Tue, 4 Feb 2020 07:58:16 +0100	[thread overview]
Message-ID: <1b85e42f-e504-ffed-eebf-1caabe648cfd@denx.de> (raw)
In-Reply-To: <1580762412-6260-1-git-send-email-stefan_b@posteo.net>

Hello Stefan,

Am 03.02.2020 um 21:40 schrieb Stefan Bosch:
> Changes in relation to FriendlyARM's U-Boot nanopi2-v2016.01:
> - i2c/nx_i2c.c: Some adaptions mainly because of changes in
>    "struct udevice".
> - mmc: nexell_dw_mmc.c changed to nexell_dw_mmc_dm.c (switched to DM).
> 
> Signed-off-by: Stefan Bosch <stefan_b@posteo.net>
> ---
> 
>   drivers/gpio/Kconfig           |   9 +
>   drivers/gpio/Makefile          |   1 +
>   drivers/gpio/nx_gpio.c         | 252 +++++++++++++++++++
>   drivers/i2c/Kconfig            |   9 +
>   drivers/i2c/Makefile           |   1 +
>   drivers/i2c/nx_i2c.c           | 537 +++++++++++++++++++++++++++++++++++++++++
>   drivers/mmc/Kconfig            |   6 +
>   drivers/mmc/Makefile           |   1 +
>   drivers/mmc/nexell_dw_mmc_dm.c | 350 +++++++++++++++++++++++++++
>   drivers/pwm/Makefile           |   1 +
>   drivers/pwm/pwm-nexell.c       | 252 +++++++++++++++++++
>   drivers/pwm/pwm-nexell.h       |  54 +++++

Could you please split this patch into 4 parts (i2c, gpio, mmc and
pwm) ?

Thanks!

>   12 files changed, 1473 insertions(+)
>   create mode 100644 drivers/gpio/nx_gpio.c
>   create mode 100644 drivers/i2c/nx_i2c.c
>   create mode 100644 drivers/mmc/nexell_dw_mmc_dm.c
>   create mode 100644 drivers/pwm/pwm-nexell.c
>   create mode 100644 drivers/pwm/pwm-nexell.h
> 
[...]
> diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
> index 449046b..e3340de 100644
> --- a/drivers/gpio/Makefile
> +++ b/drivers/gpio/Makefile
> @@ -65,3 +65,4 @@ obj-$(CONFIG_PM8916_GPIO)	+= pm8916_gpio.o
>   obj-$(CONFIG_MT7621_GPIO)	+= mt7621_gpio.o
>   obj-$(CONFIG_MSCC_SGPIO)	+= mscc_sgpio.o
>   obj-$(CONFIG_SIFIVE_GPIO)	+= sifive-gpio.o
> +obj-$(CONFIG_NX_GPIO)		+= nx_gpio.o

Please keep lists sorted.

> diff --git a/drivers/gpio/nx_gpio.c b/drivers/gpio/nx_gpio.c
> new file mode 100644
> index 0000000..86472f6
> --- /dev/null
> +++ b/drivers/gpio/nx_gpio.c
> @@ -0,0 +1,252 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * (C) Copyright 2016 Nexell
> + * DeokJin, Lee <truevirtue@nexell.co.kr>
> + */
> +
> +#include <common.h>
> +#include <dm.h>
> +#include <errno.h>
> +#include <malloc.h>
> +#include <fdtdec.h>
> +#include <asm/io.h>
> +#include <asm/gpio.h>
> +
> +DECLARE_GLOBAL_DATA_PTR;
> +
> +struct nx_gpio_regs {
> +	u32	data;		/* Data register */
> +	u32	outputenb;	/* Output Enable register */
> +	u32	detmode[2];	/* Detect Mode Register */
> +	u32	intenb;		/* Interrupt Enable Register */
> +	u32	det;		/* Event Detect Register */
> +	u32	pad;		/* Pad Status Register */
> +};
> +
> +struct nx_alive_gpio_regs {
> +	u32	pwrgate;	/* Power Gating Register */
> +	u32	reserved0[28];	/* Reserved0 */
> +	u32	outputenb_reset;/* Alive GPIO Output Enable Reset Register */
> +	u32	outputenb;	/* Alive GPIO Output Enable Register */
> +	u32	outputenb_read; /* Alive GPIO Output Read Register */
> +	u32	reserved1[3];	/* Reserved1 */
> +	u32	pad_reset;	/* Alive GPIO Output Reset Register */
> +	u32	data;		/* Alive GPIO Output Register */
> +	u32	pad_read;	/* Alive GPIO Pad Read Register */
> +	u32	reserved2[33];	/* Reserved2 */
> +	u32	pad;		/* Alive GPIO Input Value Register */
> +};
> +
> +struct nx_gpio_platdata {
> +	void *regs;
> +	int gpio_count;
> +	const char *bank_name;
> +};
> +
> +static int nx_alive_gpio_is_check(struct udevice *dev)
> +{
> +	struct nx_gpio_platdata *plat = dev_get_platdata(dev);
> +	const char *bank_name = plat->bank_name;
> +
> +	if (!strcmp(bank_name, "gpio_alv"))
> +		return 1;
> +
> +	return 0;
> +}
> +
> +static int nx_alive_gpio_direction_input(struct udevice *dev, unsigned int pin)
> +{
> +	struct nx_gpio_platdata *plat = dev_get_platdata(dev);
> +	struct nx_alive_gpio_regs *const regs = plat->regs;
> +
> +	setbits_le32(&regs->outputenb_reset, 1 << pin);
> +
> +	return 0;
> +}
> +
> +static int nx_alive_gpio_direction_output(struct udevice *dev, unsigned int pin,
> +					  int val)
> +{
> +	struct nx_gpio_platdata *plat = dev_get_platdata(dev);
> +	struct nx_alive_gpio_regs *const regs = plat->regs;
> +
> +	if (val)
> +		setbits_le32(&regs->data, 1 << pin);
> +	else
> +		setbits_le32(&regs->pad_reset, 1 << pin);
> +
> +	setbits_le32(&regs->outputenb, 1 << pin);
> +
> +	return 0;
> +}
> +
> +static int nx_alive_gpio_get_value(struct udevice *dev, unsigned int pin)
> +{
> +	struct nx_gpio_platdata *plat = dev_get_platdata(dev);
> +	struct nx_alive_gpio_regs *const regs = plat->regs;
> +	unsigned int mask = 1UL << pin;
> +	unsigned int value;
> +
> +	value = (readl(&regs->pad_read) & mask) >> pin;
> +
> +	return value;
> +}
> +
> +static int nx_alive_gpio_set_value(struct udevice *dev, unsigned int pin,
> +				   int val)
> +{
> +	struct nx_gpio_platdata *plat = dev_get_platdata(dev);
> +	struct nx_alive_gpio_regs *const regs = plat->regs;
> +
> +	if (val)
> +		setbits_le32(&regs->data, 1 << pin);
> +	else
> +		clrbits_le32(&regs->pad_reset, 1 << pin);
> +
> +	return 0;
> +}
> +
> +static int nx_alive_gpio_get_function(struct udevice *dev, unsigned int pin)
> +{
> +	struct nx_gpio_platdata *plat = dev_get_platdata(dev);
> +	struct nx_alive_gpio_regs *const regs = plat->regs;
> +	unsigned int mask = (1UL << pin);
> +	unsigned int output;
> +
> +	output = readl(&regs->outputenb_read) & mask;
> +
> +	if (output)
> +		return GPIOF_OUTPUT;
> +	else
> +		return GPIOF_INPUT;
> +}
> +
> +static int nx_gpio_direction_input(struct udevice *dev, unsigned int pin)
> +{
> +	struct nx_gpio_platdata *plat = dev_get_platdata(dev);
> +	struct nx_gpio_regs *const regs = plat->regs;
> +
> +	if (nx_alive_gpio_is_check(dev))
> +		return nx_alive_gpio_direction_input(dev, pin);
> +
> +	clrbits_le32(&regs->outputenb, 1 << pin);
> +
> +	return 0;
> +}
> +
> +static int nx_gpio_direction_output(struct udevice *dev, unsigned int pin,
> +				    int val)
> +{
> +	struct nx_gpio_platdata *plat = dev_get_platdata(dev);
> +	struct nx_gpio_regs *const regs = plat->regs;
> +
> +	if (nx_alive_gpio_is_check(dev))
> +		return nx_alive_gpio_direction_output(dev, pin, val);
> +
> +	if (val)
> +		setbits_le32(&regs->data, 1 << pin);
> +	else
> +		clrbits_le32(&regs->data, 1 << pin);
> +
> +	setbits_le32(&regs->outputenb, 1 << pin);
> +
> +	return 0;
> +}
> +
> +static int nx_gpio_get_value(struct udevice *dev, unsigned int pin)
> +{
> +	struct nx_gpio_platdata *plat = dev_get_platdata(dev);
> +	struct nx_gpio_regs *const regs = plat->regs;
> +	unsigned int mask = 1UL << pin;
> +	unsigned int value;
> +
> +	if (nx_alive_gpio_is_check(dev))
> +		return nx_alive_gpio_get_value(dev, pin);
> +
> +	value = (readl(&regs->pad) & mask) >> pin;
> +
> +	return value;
> +}
> +
> +static int nx_gpio_set_value(struct udevice *dev, unsigned int pin, int val)
> +{
> +	struct nx_gpio_platdata *plat = dev_get_platdata(dev);
> +	struct nx_gpio_regs *const regs = plat->regs;
> +
> +	if (nx_alive_gpio_is_check(dev))
> +		return nx_alive_gpio_set_value(dev, pin, val);
> +
> +	if (val)
> +		setbits_le32(&regs->data, 1 << pin);
> +	else
> +		clrbits_le32(&regs->data, 1 << pin);
> +
> +	return 0;
> +}
> +
> +static int nx_gpio_get_function(struct udevice *dev, unsigned int pin)
> +{
> +	struct nx_gpio_platdata *plat = dev_get_platdata(dev);
> +	struct nx_gpio_regs *const regs = plat->regs;
> +	unsigned int mask = (1UL << pin);
> +	unsigned int output;
> +
> +	if (nx_alive_gpio_is_check(dev))
> +		return nx_alive_gpio_get_function(dev, pin);
> +
> +	output = readl(&regs->outputenb) & mask;
> +
> +	if (output)
> +		return GPIOF_OUTPUT;
> +	else
> +		return GPIOF_INPUT;
> +}
> +
> +static int nx_gpio_probe(struct udevice *dev)
> +{
> +	struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
> +	struct nx_gpio_platdata *plat = dev_get_platdata(dev);
> +
> +	uc_priv->gpio_count = plat->gpio_count;
> +	uc_priv->bank_name = plat->bank_name;
> +
> +	return 0;
> +}
> +
> +static int nx_gpio_ofdata_to_platdata(struct udevice *dev)
> +{
> +	struct nx_gpio_platdata *plat = dev_get_platdata(dev);
> +
> +	plat->regs = map_physmem(devfdt_get_addr(dev),
> +				 sizeof(struct nx_gpio_regs),
> +				 MAP_NOCACHE);
> +	plat->gpio_count = fdtdec_get_int(gd->fdt_blob, dev->node.of_offset,
> +					  "nexell,gpio-bank-width", 32);
> +	plat->bank_name = fdt_getprop(gd->fdt_blob, dev->node.of_offset,
> +				      "gpio-bank-name", NULL);
> +
> +	return 0;
> +}
> +
> +static const struct dm_gpio_ops nx_gpio_ops = {
> +	.direction_input	= nx_gpio_direction_input,
> +	.direction_output	= nx_gpio_direction_output,
> +	.get_value		= nx_gpio_get_value,
> +	.set_value		= nx_gpio_set_value,
> +	.get_function		= nx_gpio_get_function,
> +};
> +
> +static const struct udevice_id nx_gpio_ids[] = {
> +	{ .compatible = "nexell,nexell-gpio" },
> +	{ }
> +};
> +
> +U_BOOT_DRIVER(nx_gpio) = {
> +	.name		= "nx_gpio",
> +	.id		= UCLASS_GPIO,
> +	.of_match	= nx_gpio_ids,
> +	.ops		= &nx_gpio_ops,
> +	.ofdata_to_platdata = nx_gpio_ofdata_to_platdata,
> +	.platdata_auto_alloc_size = sizeof(struct nx_gpio_platdata),
> +	.probe		= nx_gpio_probe,
> +};
> diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
> index 03d2fed..2cd0ed3 100644
> --- a/drivers/i2c/Kconfig
> +++ b/drivers/i2c/Kconfig
> @@ -317,6 +317,15 @@ config SYS_MXC_I2C8_SLAVE
>   	 MXC I2C8 Slave
>   endif
>   
> +config SYS_I2C_NEXELL
> +	bool "Nexell I2C driver"
> +	depends on DM_I2C
> +	help
> +	  Add support for the Nexell I2C driver. This is used with various
> +	  Nexell parts such as S5Pxx18 series SoCs. All chips
> +	  have several I2C ports and all are provided, controlled by the
> +	  device tree.
> +
>   config SYS_I2C_OMAP24XX
>   	bool "TI OMAP2+ I2C driver"
>   	depends on ARCH_OMAP2PLUS || ARCH_K3
> diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
> index f5a471f..64b8ead 100644
> --- a/drivers/i2c/Makefile
> +++ b/drivers/i2c/Makefile
> @@ -26,6 +26,7 @@ obj-$(CONFIG_SYS_I2C_LPC32XX) += lpc32xx_i2c.o
>   obj-$(CONFIG_SYS_I2C_MESON) += meson_i2c.o
>   obj-$(CONFIG_SYS_I2C_MVTWSI) += mvtwsi.o
>   obj-$(CONFIG_SYS_I2C_MXC) += mxc_i2c.o
> +obj-$(CONFIG_SYS_I2C_NEXELL) += nx_i2c.o
>   obj-$(CONFIG_SYS_I2C_OMAP24XX) += omap24xx_i2c.o
>   obj-$(CONFIG_SYS_I2C_RCAR_I2C) += rcar_i2c.o
>   obj-$(CONFIG_SYS_I2C_RCAR_IIC) += rcar_iic.o
> diff --git a/drivers/i2c/nx_i2c.c b/drivers/i2c/nx_i2c.c
> new file mode 100644
> index 0000000..a3eec6c
> --- /dev/null
> +++ b/drivers/i2c/nx_i2c.c
> @@ -0,0 +1,537 @@
> +#include <common.h>
> +#include <errno.h>
> +#include <dm.h>
> +#include <i2c.h>
> +#include <asm/arch/nexell.h>
> +#include <asm/arch/reset.h>
> +#include <asm/arch/clk.h>
> +#include <asm/arch/nx_gpio.h>
> +
> +#define I2C_WRITE       0
> +#define I2C_READ        1
> +
> +#define I2C_OK          0
> +#define I2C_NOK         1
> +#define I2C_NACK        2
> +#define I2C_NOK_LA      3       /* Lost arbitration */
> +#define I2C_NOK_TOUT    4       /* time out */
> +
> +#define I2CLC_FILTER	0x04	/* SDA filter on*/
> +#define I2CSTAT_BSY     0x20    /* Busy bit */
> +#define I2CSTAT_NACK    0x01    /* Nack bit */
> +#define I2CSTAT_ABT	0x08	/* Arbitration bit */
> +#define I2CCON_ACKGEN   0x80    /* Acknowledge generation */
> +#define I2CCON_IRENB	0x20	/* Interrupt Enable bit  */
> +#define I2CCON_IRPND    0x10    /* Interrupt pending bit */
> +#define I2C_MODE_MT     0xC0    /* Master Transmit Mode */
> +#define I2C_MODE_MR     0x80    /* Master Receive Mode */
> +#define I2C_START_STOP  0x20    /* START / STOP */
> +#define I2C_TXRX_ENA    0x10    /* I2C Tx/Rx enable */
> +
> +#define I2C_TIMEOUT_MS	10      /* 10 ms */
> +
> +#define I2C_M_NOSTOP	0x100
> +
> +#ifndef CONFIG_MAX_I2C_NUM
> +#define CONFIG_MAX_I2C_NUM 3
> +#endif

Is this really configurable? If so, I do not find the Kconfig
description.

> +
> +DECLARE_GLOBAL_DATA_PTR;
> +
> +struct nx_i2c_regs {
> +	uint     iiccon;
> +	uint     iicstat;
> +	uint     iicadd;
> +	uint     iicds;
> +	uint     iiclc;
> +};
> +
> +struct nx_i2c_bus {
> +	uint bus_num;
> +	struct nx_i2c_regs *regs;
> +	uint speed;
> +	uint target_speed;
> +	uint sda_delay;
> +};
> +
> +/* s5pxx18 i2c must be reset before enabled */
> +static void i2c_reset(int ch)
> +{
> +	int rst_id = RESET_ID_I2C0 + ch;
> +
> +	nx_rstcon_setrst(rst_id, 0);
> +	nx_rstcon_setrst(rst_id, 1);
> +}
> +
> +/* FIXME : this func will be removed after reset dm driver ported.
> + * set mmc pad alternative func.
> + */
> +static void set_i2c_pad_func(struct nx_i2c_bus *i2c)
> +{
> +	switch (i2c->bus_num) {
> +	case 0:
> +		nx_gpio_set_pad_function(3, 2, 1);
> +		nx_gpio_set_pad_function(3, 3, 1);
> +		break;
> +	case 1:
> +		nx_gpio_set_pad_function(3, 4, 1);
> +		nx_gpio_set_pad_function(3, 5, 1);
> +		break;
> +	case 2:
> +		nx_gpio_set_pad_function(3, 6, 1);
> +		nx_gpio_set_pad_function(3, 7, 1);
> +		break;
> +	}
> +}

Hmm... may this should be moved into a seperate pincontrol driver?

> +
> +static uint i2c_get_clkrate(struct nx_i2c_bus *bus)
> +{
> +	struct clk *clk;
> +	int index = bus->bus_num;
> +	char name[50] = {0, };

?

> +
> +	sprintf(name, "%s.%d", DEV_NAME_I2C, index);

Where is DEV_NAME_I2C defined ?

> +	clk = clk_get((const char *)name);
> +	if (!clk)
> +		return -1;
> +
> +	return clk_get_rate(clk);
> +}
> +
> +static uint i2c_set_clk(struct nx_i2c_bus *bus, uint enb)
> +{
> +	struct clk *clk;
> +	char name[50];
> +
> +	sprintf(name, "%s.%d", DEV_NAME_I2C, bus->bus_num);
> +	clk = clk_get((const char *)name);
> +	if (!clk)
> +		return -1;
> +
> +	if (enb) {
> +		clk_disable(clk);
> +		clk_enable(clk);
> +	} else {
> +		clk_disable(clk);
> +	}
> +
> +	return 0;
> +}
> +
> +/* get i2c module number from base address */
> +static uint i2c_get_busnum(struct nx_i2c_bus *bus)
> +{
> +	void *base_addr = (void *)PHY_BASEADDR_I2C0;
> +	int i;
> +
> +	for (i = 0; i < CONFIG_MAX_I2C_NUM; i++) {
> +		if (base_addr == ((void *)bus->regs)) {
> +			bus->bus_num = i;
> +			return i;
> +		}
> +		base_addr += 0x1000;
> +	}
> +
> +	return -1;

return -ENODEV;

Hmm... is there no chance to use seq from struct udevice

https://gitlab.denx.de/u-boot/u-boot/blob/master/include/dm/device.h#L152

?

For example like:
https://gitlab.denx.de/u-boot/u-boot/blob/master/drivers/i2c/mxc_i2c.c#L895

> +}
> +
> +/* Set SDA line delay */
> +static int nx_i2c_set_sda_delay(struct nx_i2c_bus *bus, ulong clkin)
> +{
> +	struct nx_i2c_regs *i2c = bus->regs;
> +	uint sda_delay = 0;
> +
> +	if (bus->sda_delay) {
> +		sda_delay = clkin * bus->sda_delay;
> +		sda_delay = DIV_ROUND_UP(sda_delay, 1000000);
> +		sda_delay = DIV_ROUND_UP(sda_delay, 5);
> +		if (sda_delay > 3)
> +			sda_delay = 3;
> +		sda_delay |= I2CLC_FILTER;
> +	} else {
> +		sda_delay = 0;
> +	}
> +
> +	sda_delay &= 0x7;
> +	writel(sda_delay, &i2c->iiclc);
> +
> +	return 0;
> +}
> +
> +/* Calculate the value of the divider and prescaler, set the bus speed. */
> +static int nx_i2c_set_bus_speed(struct udevice *dev, uint speed)
> +{
> +	struct nx_i2c_bus *bus = dev_get_priv(dev);
> +	struct nx_i2c_regs *i2c = bus->regs;
> +	unsigned long freq, pres = 16, div;
> +
> +	freq = i2c_get_clkrate(bus);
> +	/* calculate prescaler and divisor values */
> +	if ((freq / pres / (16 + 1)) > speed)
> +		/* set prescaler to 512 */
> +		pres = 512;
> +
> +	div = 0;
> +	while ((freq / pres / (div + 1)) > speed)
> +		div++;
> +
> +	/* set prescaler, divisor according to freq, also set ACKGEN, IRQ */
> +	writel((div & 0x0F) | ((pres == 512) ? 0x40 : 0), &i2c->iiccon);
> +
> +	/* init to SLAVE REVEIVE and set slaveaddr */
> +	writel(0, &i2c->iicstat);
> +	writel(0x00, &i2c->iicadd);
> +	/* program Master Transmit (and implicit STOP) */
> +	writel(I2C_MODE_MT | I2C_TXRX_ENA, &i2c->iicstat);
> +
> +	bus->speed = bus->target_speed / (div * pres);

Do you want to allow all values of speeds or may you want to use
standard speeds, see:

https://gitlab.denx.de/u-boot/u-boot/blob/master/include/i2c.h#L33

> +
> +	return 0;
> +}
> +
> +static void nx_i2c_set_clockrate(struct udevice *dev, uint speed)
> +{
> +	struct nx_i2c_bus *bus = dev_get_priv(dev);
> +	ulong clkin;
> +
> +	nx_i2c_set_bus_speed(dev, speed);
> +	clkin = bus->speed;			/* the actual i2c speed */
> +	clkin /= 1000;				/* clkin now in Khz */
> +	nx_i2c_set_sda_delay(bus, clkin);
> +}
> +
> +static void i2c_process_node(struct udevice *dev)
> +{
> +	struct nx_i2c_bus *bus = dev_get_priv(dev);
> +	const void *blob = gd->fdt_blob;
> +
> +	int node;
> +
> +	node = dev->node.of_offset;
> +
> +	bus->target_speed = fdtdec_get_int(blob, node,
> +					   "nexell,i2c-max-bus-freq", 0);
> +	bus->sda_delay = fdtdec_get_int(blob, node,
> +					"nexell,i2c-sda-delay", 0);

You introdue here new properties, please document them in
u-boot:/doc/device-tree-bindings/i2c

Please without "nexell,"

Do you plan to post also a linux i2c driver?

If so, the devicetree bindings should be discussed there to avoid
different properties between U-Boot and linux!

> +}
> +
> +static int nx_i2c_probe(struct udevice *dev)
> +{
> +	struct nx_i2c_bus *bus = dev_get_priv(dev);
> +
> +	/* get regs */
> +	bus->regs = (struct nx_i2c_regs *)devfdt_get_addr(dev);
> +	/* calc index */
> +	if (!i2c_get_busnum(bus)) {
> +		debug("not found i2c number!\n");
> +		return -1;

please return -ENODEV

> +	}
> +
> +	/* i2c optional node parsing */
> +	i2c_process_node(dev);
> +	if (!bus->target_speed)
> +		return -1;

please return here also an errorcode from include/linux/errno.h

Hmm.. if you return here if target_speed is not set, it is not optional!

> +
> +	/* reset */
> +	i2c_reset(bus->bus_num);
> +	/* gpio pad */
> +	set_i2c_pad_func(bus);
> +
> +	/* clock rate */
> +	i2c_set_clk(bus, 1);
> +	nx_i2c_set_clockrate(dev, bus->target_speed);
> +	i2c_set_clk(bus, 0);
> +
> +	return 0;
> +}
> +
> +/* i2c bus busy check */
> +static int i2c_is_busy(struct nx_i2c_regs *i2c)
> +{
> +	ulong start_time;
> +
> +	start_time = get_timer(0);
> +	while (readl(&i2c->iicstat) & I2CSTAT_BSY) {
> +		if (get_timer(start_time) > I2C_TIMEOUT_MS) {
> +			debug("Timeout\n");
> +			return -I2C_NOK_TOUT;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/* irq enable/disable functions */
> +static void i2c_enable_irq(struct nx_i2c_regs *i2c)
> +{
> +	unsigned int reg;
> +
> +	reg = readl(&i2c->iiccon);
> +	reg |= I2CCON_IRENB;
> +	writel(reg, &i2c->iiccon);
> +}
> +
> +/* irq clear function */
> +static void i2c_clear_irq(struct nx_i2c_regs *i2c)
> +{
> +	clrbits_le32(&i2c->iiccon, I2CCON_IRPND);
> +}
> +
> +/* ack enable functions */
> +static void i2c_enable_ack(struct nx_i2c_regs *i2c)
> +{
> +	unsigned int reg;
> +
> +	reg = readl(&i2c->iiccon);
> +	reg |= I2CCON_ACKGEN;
> +	writel(reg, &i2c->iiccon);
> +}
> +
> +static void i2c_send_stop(struct nx_i2c_regs *i2c)
> +{
> +	unsigned int reg;
> +
> +	/* Send STOP. */
> +	reg = readl(&i2c->iicstat);
> +	reg |= I2C_MODE_MR | I2C_TXRX_ENA;
> +	reg &= (~I2C_START_STOP);
> +	writel(reg, &i2c->iicstat);
> +	i2c_clear_irq(i2c);
> +}
> +
> +static int wait_for_xfer(struct nx_i2c_regs *i2c)
> +{
> +	unsigned long start_time = get_timer(0);
> +
> +	do {
> +		if (readl(&i2c->iiccon) & I2CCON_IRPND)
> +			return (readl(&i2c->iicstat) & I2CSTAT_NACK) ?
> +				I2C_NACK : I2C_OK;
> +	} while (get_timer(start_time) < I2C_TIMEOUT_MS);
> +
> +	return I2C_NOK_TOUT;
> +}
> +
> +static int i2c_transfer(struct nx_i2c_regs *i2c,
> +			uchar cmd_type,
> +			uchar chip,
> +			uchar addr[],
> +			uchar addr_len,
> +			uchar data[],
> +			unsigned short data_len,
> +			uint seq)
> +{
> +	uint status;
> +	int i = 0, result;
> +
> +	if (data == 0 || data_len == 0) {
> +		/*Don't support data transfer of no length or to address 0 */
> +		debug("%s: bad call\n", __func__);
> +		return I2C_NOK;
> +	}
> +
> +	i2c_enable_irq(i2c);
> +	i2c_enable_ack(i2c);
> +
> +	/* Get the slave chip address going */
> +	writel(chip, &i2c->iicds);
> +	status = I2C_TXRX_ENA | I2C_START_STOP;
> +	if (cmd_type == I2C_WRITE || (addr && addr_len))
> +		status |= I2C_MODE_MT;
> +	else
> +		status |= I2C_MODE_MR;
> +	writel(status, &i2c->iicstat);
> +	if (seq)
> +		i2c_clear_irq(i2c);
> +
> +	/* Wait for chip address to transmit. */
> +	result = wait_for_xfer(i2c);
> +	if (result != I2C_OK)
> +		goto bailout;
> +
> +	/* If register address needs to be transmitted - do it now. */
> +	if (addr && addr_len) {  /* register addr */
> +		while ((i < addr_len) && (result == I2C_OK)) {
> +			writel(addr[i++], &i2c->iicds);
> +			i2c_clear_irq(i2c);
> +			result = wait_for_xfer(i2c);
> +		}
> +
> +		i = 0;
> +		if (result != I2C_OK)
> +			goto bailout;
> +	}
> +
> +	switch (cmd_type) {
> +	case I2C_WRITE:
> +		while ((i < data_len) && (result == I2C_OK)) {
> +			writel(data[i++], &i2c->iicds);
> +			i2c_clear_irq(i2c);
> +			result = wait_for_xfer(i2c);
> +		}
> +		break;
> +	case I2C_READ:
> +		if (addr && addr_len) {
> +			/*
> +			 * Register address has been sent, now send slave chip
> +			 * address again to start the actual read transaction.
> +			 */
> +			writel(chip, &i2c->iicds);
> +
> +			/* Generate a re-START. */
> +			writel(I2C_MODE_MR | I2C_TXRX_ENA
> +					| I2C_START_STOP, &i2c->iicstat);
> +			i2c_clear_irq(i2c);
> +			result = wait_for_xfer(i2c);
> +			if (result != I2C_OK)
> +				goto bailout;
> +		}
> +
> +		while ((i < data_len) && (result == I2C_OK)) {
> +			/* disable ACK for final READ */
> +			if (i == data_len - 1)
> +				clrbits_le32(&i2c->iiccon
> +						, I2CCON_ACKGEN);
> +
> +			i2c_clear_irq(i2c);
> +			result = wait_for_xfer(i2c);
> +			data[i++] = readb(&i2c->iicds);
> +		}
> +
> +		if (result == I2C_NACK)
> +			result = I2C_OK; /* Normal terminated read. */
> +		break;
> +
> +	default:
> +		debug("%s: bad call\n", __func__);
> +		result = I2C_NOK;
> +		break;
> +	}
> +
> +bailout:
> +	return result;
> +}
> +
> +static int nx_i2c_read(struct udevice *dev, uchar chip, uint addr,
> +		       uint alen, uchar *buffer, uint len, uint seq)
> +{
> +	struct nx_i2c_bus *i2c;
> +	uchar xaddr[4];
> +	int ret;
> +
> +	i2c = dev_get_priv(dev);
> +	if (!i2c)
> +		return -EFAULT;
> +
> +	if (alen > 4) {
> +		debug("I2C read: addr len %d not supported\n", alen);
> +		return -EADDRNOTAVAIL;
> +	}
> +
> +	if (alen > 0)
> +		xaddr[0] = (addr >> 24) & 0xFF;
> +
> +	if (alen > 0) {
> +		xaddr[0] = (addr >> 24) & 0xFF;
> +		xaddr[1] = (addr >> 16) & 0xFF;
> +		xaddr[2] = (addr >> 8) & 0xFF;
> +		xaddr[3] = addr & 0xFF;
> +	}
> +
> +	ret = i2c_transfer(i2c->regs, I2C_READ, chip << 1,
> +			   &xaddr[4 - alen], alen, buffer, len, seq);
> +
> +	if (ret) {
> +		debug("I2C read failed %d\n", ret);
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +static int nx_i2c_write(struct udevice *dev, uchar chip, uint addr,
> +			uint alen, uchar *buffer, uint len, uint seq)
> +{
> +	struct nx_i2c_bus *i2c;
> +	uchar xaddr[4];
> +	int ret;
> +
> +	i2c = dev_get_priv(dev);
> +	if (!i2c)
> +		return -EFAULT;
> +
> +	if (alen > 4) {
> +		debug("I2C write: addr len %d not supported\n", alen);
> +		return -EINVAL;
> +	}
> +
> +	if (alen > 0) {
> +		xaddr[0] = (addr >> 24) & 0xFF;
> +		xaddr[1] = (addr >> 16) & 0xFF;
> +		xaddr[2] = (addr >> 8) & 0xFF;
> +		xaddr[3] = addr & 0xFF;
> +	}
> +
> +	ret = i2c_transfer(i2c->regs, I2C_WRITE, chip << 1,
> +			   &xaddr[4 - alen], alen, buffer, len, seq);
> +	if (ret) {
> +		debug("I2C write failed %d\n", ret);
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +static int nx_i2c_xfer(struct udevice *dev, struct i2c_msg *msg, int nmsgs)
> +{
> +	struct nx_i2c_bus *bus = dev_get_priv(dev);
> +	struct nx_i2c_regs *i2c = bus->regs;
> +	int ret;
> +	int i;
> +
> +	/* The power loss by the clock, only during on/off. */
> +	i2c_set_clk(bus, 1);
> +
> +	/* Bus State(Busy) check  */
> +	ret = i2c_is_busy(i2c);
> +	if (ret < 0)
> +		return ret;
> +
> +	for (i = 0; i < nmsgs; msg++, i++) {
> +		if (msg->flags & I2C_M_RD) {
> +			ret = nx_i2c_read(dev, msg->addr, 0, 0, msg->buf,
> +					  msg->len, i);
> +		} else {
> +			ret = nx_i2c_write(dev, msg->addr, 0, 0, msg->buf,
> +					   msg->len, i);
> +		}
> +
> +		if (ret) {
> +			debug("i2c_xfer: error sending\n");
> +			return -EREMOTEIO;
> +		}
> +	}
> +	/* Send Stop */
> +	i2c_send_stop(i2c);
> +	i2c_set_clk(bus, 0);
> +
> +	return ret ? -EREMOTEIO : 0;
> +};
> +
> +static const struct dm_i2c_ops nx_i2c_ops = {
> +	.xfer		= nx_i2c_xfer,
> +	.set_bus_speed	= nx_i2c_set_bus_speed,
> +};
> +
> +static const struct udevice_id nx_i2c_ids[] = {
> +	{ .compatible = "nexell,s5pxx18-i2c" },

Same here as for the new properties. Please discuss the names on

devicetree at vger.kernel.org <devicetree@vger.kernel.org>

first!

> +	{ }
> +};
> +
> +U_BOOT_DRIVER(i2c_nexell) = {
> +	.name		= "i2c_nexell",
> +	.id		= UCLASS_I2C,
> +	.of_match	= nx_i2c_ids,
> +	.probe		= nx_i2c_probe,
> +	.priv_auto_alloc_size	= sizeof(struct nx_i2c_bus),
> +	.ops		= &nx_i2c_ops,
> +};

bye,
Heiko
> diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig
> index 2f0eedc..bb8e7c0 100644
> --- a/drivers/mmc/Kconfig
> +++ b/drivers/mmc/Kconfig
> @@ -253,6 +253,12 @@ config MMC_DW_SNPS
>   	  This selects support for Synopsys DesignWare Memory Card Interface driver
>   	  extensions used in various Synopsys ARC devboards.
>   
> +config NEXELL_DWMMC
> +	bool "Nexell SD/MMC controller support"
> +	depends on ARCH_NEXELL
> +	depends on MMC_DW
> +	default y
> +
>   config MMC_MESON_GX
>   	bool "Meson GX EMMC controller support"
>   	depends on DM_MMC && BLK && ARCH_MESON
> diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile
> index 9c1f8e5..a7b5a7b 100644
> --- a/drivers/mmc/Makefile
> +++ b/drivers/mmc/Makefile
> @@ -43,6 +43,7 @@ obj-$(CONFIG_SH_MMCIF) += sh_mmcif.o
>   obj-$(CONFIG_SH_SDHI) += sh_sdhi.o
>   obj-$(CONFIG_STM32_SDMMC2) += stm32_sdmmc2.o
>   obj-$(CONFIG_JZ47XX_MMC) += jz_mmc.o
> +obj-$(CONFIG_NEXELL_DWMMC) += nexell_dw_mmc_dm.o
>   
>   # SDHCI
>   obj-$(CONFIG_MMC_SDHCI)			+= sdhci.o
> diff --git a/drivers/mmc/nexell_dw_mmc_dm.c b/drivers/mmc/nexell_dw_mmc_dm.c
> new file mode 100644
> index 0000000..b06b60d
> --- /dev/null
> +++ b/drivers/mmc/nexell_dw_mmc_dm.c
> @@ -0,0 +1,350 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * (C) Copyright 2016 Nexell
> + * Youngbok, Park <park@nexell.co.kr>
> + *
> + * (C) Copyright 2019 Stefan Bosch <stefan_b@posteo.net>
> + */
> +
> +#include <common.h>
> +#include <clk.h>
> +#include <dm.h>
> +#include <dt-structs.h>
> +#include <dwmmc.h>
> +#include <syscon.h>
> +#include <asm/gpio.h>
> +#include <asm/arch/nx_gpio.h>
> +#include <asm/arch/reset.h>
> +
> +#define DWMCI_CLKSEL			0x09C
> +#define DWMCI_SHIFT_0			0x0
> +#define DWMCI_SHIFT_1			0x1
> +#define DWMCI_SHIFT_2			0x2
> +#define DWMCI_SHIFT_3			0x3
> +#define DWMCI_SET_SAMPLE_CLK(x)	(x)
> +#define DWMCI_SET_DRV_CLK(x)	((x) << 16)
> +#define DWMCI_SET_DIV_RATIO(x)	((x) << 24)
> +#define DWMCI_CLKCTRL			0x114
> +#define NX_MMC_CLK_DELAY(x, y, a, b)	((((x) & 0xFF) << 0) |\
> +					(((y) & 0x03) << 16) |\
> +					(((a) & 0xFF) << 8)  |\
> +					(((b) & 0x03) << 24))
> +
> +struct nexell_mmc_plat {
> +	struct mmc_config cfg;
> +	struct mmc mmc;
> +};
> +
> +struct nexell_dwmmc_priv {
> +	struct clk *clk;
> +	struct dwmci_host host;
> +	int fifo_size;
> +	bool fifo_mode;
> +	int frequency;
> +	u32 min_freq;
> +	u32 max_freq;
> +	int d_delay;
> +	int d_shift;
> +	int s_delay;
> +	int s_shift;
> +
> +};
> +
> +struct clk *clk_get(const char *id);
> +
> +static void set_pin_stat(int index, int bit, int value)
> +{
> +#if !defined(CONFIG_SPL_BUILD)
> +	nx_gpio_set_pad_function(index, bit, value);
> +#else
> +#if defined(CONFIG_ARCH_S5P4418) ||	\
> +	defined(CONFIG_ARCH_S5P6818)
> +
> +	unsigned long base[5] = {
> +		PHY_BASEADDR_GPIOA, PHY_BASEADDR_GPIOB,
> +		PHY_BASEADDR_GPIOC, PHY_BASEADDR_GPIOD,
> +		PHY_BASEADDR_GPIOE,
> +	};
> +
> +	dw_mmc_set_pin(base[index], bit, value);
> +#endif
> +#endif
> +}
> +
> +static void nx_dw_mmc_set_pin(struct dwmci_host *host)
> +{
> +	debug("  %s(): dev_index == %d", __func__, host->dev_index);
> +
> +	switch (host->dev_index) {
> +	case 0:
> +		set_pin_stat(0, 29, 1);
> +		set_pin_stat(0, 31, 1);
> +		set_pin_stat(1, 1, 1);
> +		set_pin_stat(1, 3, 1);
> +		set_pin_stat(1, 5, 1);
> +		set_pin_stat(1, 7, 1);
> +		break;
> +	case 1:
> +		set_pin_stat(3, 22, 1);
> +		set_pin_stat(3, 23, 1);
> +		set_pin_stat(3, 24, 1);
> +		set_pin_stat(3, 25, 1);
> +		set_pin_stat(3, 26, 1);
> +		set_pin_stat(3, 27, 1);
> +		break;
> +	case 2:
> +		set_pin_stat(2, 18, 2);
> +		set_pin_stat(2, 19, 2);
> +		set_pin_stat(2, 20, 2);
> +		set_pin_stat(2, 21, 2);
> +		set_pin_stat(2, 22, 2);
> +		set_pin_stat(2, 23, 2);
> +		if (host->buswidth == 8) {
> +			set_pin_stat(4, 21, 2);
> +			set_pin_stat(4, 22, 2);
> +			set_pin_stat(4, 23, 2);
> +			set_pin_stat(4, 24, 2);
> +		}
> +		break;
> +	default:
> +		debug(" is invalid!");
> +	}
> +	debug("\n");
> +}
> +
> +static void nx_dw_mmc_clksel(struct dwmci_host *host)
> +{
> +	u32 val;
> +
> +#ifdef CONFIG_BOOST_MMC
> +	val = DWMCI_SET_SAMPLE_CLK(DWMCI_SHIFT_0) |
> +	    DWMCI_SET_DRV_CLK(DWMCI_SHIFT_0) | DWMCI_SET_DIV_RATIO(1);
> +#else
> +	val = DWMCI_SET_SAMPLE_CLK(DWMCI_SHIFT_0) |
> +	    DWMCI_SET_DRV_CLK(DWMCI_SHIFT_0) | DWMCI_SET_DIV_RATIO(3);
> +#endif
> +
> +	dwmci_writel(host, DWMCI_CLKSEL, val);
> +}
> +
> +static void nx_dw_mmc_reset(int ch)
> +{
> +	int rst_id = RESET_ID_SDMMC0 + ch;
> +
> +	nx_rstcon_setrst(rst_id, 0);
> +	nx_rstcon_setrst(rst_id, 1);
> +}
> +
> +static void nx_dw_mmc_clk_delay(struct udevice *dev)
> +{
> +	unsigned int delay;
> +	struct nexell_dwmmc_priv *priv = dev_get_priv(dev);
> +	struct dwmci_host *host = &priv->host;
> +
> +	delay = NX_MMC_CLK_DELAY(priv->d_delay,
> +				 priv->d_shift, priv->s_delay, priv->s_shift);
> +
> +	writel(delay, (host->ioaddr + DWMCI_CLKCTRL));
> +	debug("%s(): Values set: d_delay==%d, d_shift==%d, s_delay==%d, "
> +	      "s_shift==%d\n", __func__, priv->d_delay, priv->d_shift,
> +	      priv->s_delay, priv->s_shift);
> +}
> +
> +static unsigned int nx_dw_mmc_get_clk(struct dwmci_host *host, uint freq)
> +{
> +	struct clk *clk;
> +	struct udevice *dev = host->priv;
> +	struct nexell_dwmmc_priv *priv = dev_get_priv(dev);
> +
> +	int index = host->dev_index;
> +	char name[50] = { 0, };
> +
> +	clk = priv->clk;
> +	if (!clk) {
> +		sprintf(name, "%s.%d", DEV_NAME_SDHC, index);
> +		clk = clk_get((const char *)name);
> +		if (!clk)
> +			return 0;
> +		priv->clk = clk;
> +	}
> +
> +	return clk_get_rate(clk) / 2;
> +}
> +
> +static unsigned long nx_dw_mmc_set_clk(struct dwmci_host *host,
> +				       unsigned int rate)
> +{
> +	struct clk *clk;
> +	char name[50] = { 0, };
> +	struct udevice *dev = host->priv;
> +	struct nexell_dwmmc_priv *priv = dev_get_priv(dev);
> +
> +	int index = host->dev_index;
> +
> +	clk = priv->clk;
> +	if (!clk) {
> +		sprintf(name, "%s.%d", DEV_NAME_SDHC, index);
> +		clk = clk_get((const char *)name);
> +		if (!clk)
> +			return 0;
> +		priv->clk = clk;
> +	}
> +
> +	clk_disable(clk);
> +	rate = clk_set_rate(clk, rate);
> +	clk_enable(clk);
> +
> +	return rate;
> +}
> +
> +static int nexell_dwmmc_ofdata_to_platdata(struct udevice *dev)
> +{
> +	/* if (dev): *priv = dev->priv, else: *priv = NULL */
> +	struct nexell_dwmmc_priv *priv = dev_get_priv(dev);
> +	struct dwmci_host *host = &priv->host;
> +	int val = -1;
> +
> +	debug("%s()\n", __func__);
> +
> +	host->name = dev->name;
> +	host->ioaddr = dev_read_addr_ptr(dev);
> +
> +	val = dev_read_u32_default(dev, "nexell,bus-width", -1);
> +	if (val < 0) {
> +		debug("  'nexell,bus-width' missing/invalid!\n");
> +		return -EINVAL;
> +	}
> +	host->buswidth = val;
> +	host->get_mmc_clk = nx_dw_mmc_get_clk;
> +	host->clksel = nx_dw_mmc_clksel;
> +	host->priv = dev;
> +
> +	val = dev_read_u32_default(dev, "index", -1);
> +	if (val < 0) {
> +		debug("  'index' missing/invalid!\n");
> +		return -EINVAL;
> +	}
> +	host->dev_index = val;
> +
> +	val = dev_read_u32_default(dev, "fifo-size", 0x20);
> +	if (val <= 0) {
> +		debug("  'fifo-size' missing/invalid!\n");
> +		return -EINVAL;
> +	}
> +	priv->fifo_size = val;
> +
> +	priv->fifo_mode = dev_read_bool(dev, "fifo-mode");
> +
> +	val = dev_read_u32_default(dev, "frequency", -1);
> +	if (val < 0) {
> +		debug("  'frequency' missing/invalid!\n");
> +		return -EINVAL;
> +	}
> +	priv->frequency = val;
> +
> +	val = dev_read_u32_default(dev, "max-frequency", -1);
> +	if (val < 0) {
> +		debug("  'max-frequency' missing/invalid!\n");
> +		return -EINVAL;
> +	}
> +	priv->max_freq = val;
> +	priv->min_freq = 400000;  /* 400 kHz */
> +
> +	val = dev_read_u32_default(dev, "nexell,drive_dly", -1);
> +	if (val < 0) {
> +		debug("  'nexell,drive_dly' missing/invalid!\n");
> +		return -EINVAL;
> +	}
> +	priv->d_delay = val;
> +
> +	val = dev_read_u32_default(dev, "nexell,drive_shift", -1);
> +	if (val < 0) {
> +		debug("  'nexell,drive_shift' missing/invalid!\n");
> +		return -EINVAL;
> +	}
> +	priv->d_shift = val;
> +
> +	val = dev_read_u32_default(dev, "nexell,sample_dly", -1);
> +	if (val < 0) {
> +		debug("  'nexell,sample_dly' missing/invalid!\n");
> +		return -EINVAL;
> +	}
> +	priv->s_delay = val;
> +
> +	val = dev_read_u32_default(dev, "nexell,sample_shift", -1);
> +	if (val < 0) {
> +		debug("  'nexell,sample_shift' missing/invalid!\n");
> +		return -EINVAL;
> +	}
> +	priv->s_shift = val;
> +
> +	debug("  index==%d, name==%s, ioaddr==0x%08x, buswidth==%d, "
> +		  "fifo_size==%d, fifo_mode==%d, frequency==%d\n",
> +		  host->dev_index, host->name, (u32)host->ioaddr,
> +		  host->buswidth, priv->fifo_size, priv->fifo_mode,
> +		  priv->frequency);
> +	debug("  min_freq==%d, max_freq==%d, delay: "
> +		  "0x%02x:0x%02x:0x%02x:0x%02x\n",
> +		  priv->min_freq, priv->max_freq, priv->d_delay,
> +		  priv->d_shift, priv->s_delay, priv->s_shift);
> +
> +	return 0;
> +}
> +
> +static int nexell_dwmmc_probe(struct udevice *dev)
> +{
> +	struct nexell_mmc_plat *plat = dev_get_platdata(dev);
> +	struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
> +	struct nexell_dwmmc_priv *priv = dev_get_priv(dev);
> +	struct dwmci_host *host = &priv->host;
> +	struct udevice *pwr_dev __maybe_unused;
> +
> +	debug("%s():\n", __func__);
> +
> +	host->fifoth_val = MSIZE(0x2) |
> +		RX_WMARK(priv->fifo_size / 2 - 1) |
> +		TX_WMARK(priv->fifo_size / 2);
> +
> +	host->fifo_mode = priv->fifo_mode;
> +
> +	dwmci_setup_cfg(&plat->cfg, host, priv->max_freq, priv->min_freq);
> +	host->mmc = &plat->mmc;
> +	host->mmc->priv = &priv->host;
> +	host->mmc->dev = dev;
> +	upriv->mmc = host->mmc;
> +
> +	nx_dw_mmc_set_pin(host);
> +
> +	debug("  nx_dw_mmc_set_clk(host, frequency * 4 == %d)\n",
> +	      priv->frequency * 4);
> +	nx_dw_mmc_set_clk(host, priv->frequency * 4);
> +
> +	nx_dw_mmc_reset(host->dev_index);
> +	nx_dw_mmc_clk_delay(dev);
> +
> +	return dwmci_probe(dev);
> +}
> +
> +static int nexell_dwmmc_bind(struct udevice *dev)
> +{
> +	struct nexell_mmc_plat *plat = dev_get_platdata(dev);
> +
> +	return dwmci_bind(dev, &plat->mmc, &plat->cfg);
> +}
> +
> +static const struct udevice_id nexell_dwmmc_ids[] = {
> +	{ .compatible = "nexell,nexell-dwmmc" },
> +	{ }
> +};
> +
> +U_BOOT_DRIVER(nexell_dwmmc_drv) = {
> +	.name		= "nexell_dwmmc",
> +	.id		= UCLASS_MMC,
> +	.of_match	= nexell_dwmmc_ids,
> +	.ofdata_to_platdata = nexell_dwmmc_ofdata_to_platdata,
> +	.ops		= &dm_dwmci_ops,
> +	.bind		= nexell_dwmmc_bind,
> +	.probe		= nexell_dwmmc_probe,
> +	.priv_auto_alloc_size = sizeof(struct nexell_dwmmc_priv),
> +	.platdata_auto_alloc_size = sizeof(struct nexell_mmc_plat),
> +};
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index a837c35..b45aada 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -16,3 +16,4 @@ obj-$(CONFIG_PWM_ROCKCHIP)	+= rk_pwm.o
>   obj-$(CONFIG_PWM_SANDBOX)	+= sandbox_pwm.o
>   obj-$(CONFIG_PWM_TEGRA)		+= tegra_pwm.o
>   obj-$(CONFIG_PWM_SUNXI)		+= sunxi_pwm.o
> +obj-$(CONFIG_PWM_NX)		+= pwm-nexell.o
> diff --git a/drivers/pwm/pwm-nexell.c b/drivers/pwm/pwm-nexell.c
> new file mode 100644
> index 0000000..6c0f8f4
> --- /dev/null
> +++ b/drivers/pwm/pwm-nexell.c
> @@ -0,0 +1,252 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2011 Samsung Electronics
> + *
> + * Donghwa Lee <dh09.lee@samsung.com>
> + */
> +
> +/* This codes are copied from arch/arm/cpu/armv7/s5p-common/pwm.c */
> +
> +#include <common.h>
> +#include <errno.h>
> +#include <pwm.h>
> +#include <asm/io.h>
> +#include <asm/arch/clk.h>
> +#include "pwm-nexell.h"
> +
> +#if defined(CONFIG_ARCH_NEXELL)
> +#include <asm/arch/nexell.h>
> +#include <asm/arch/reset.h>
> +#include <asm/arch/nx_gpio.h>
> +#include <asm/arch/tieoff.h>
> +
> +struct pwm_device {
> +	int ch;
> +	int grp;
> +	int bit;
> +	int pwm_fn;
> +};
> +
> +static struct pwm_device pwm_dev[] = {
> +	[0] = { .ch = 0, .grp = 3, .bit = 1,  .pwm_fn = 1 },
> +	[1] = { .ch = 1, .grp = 2, .bit = 13, .pwm_fn = 2 },
> +	[2] = { .ch = 2, .grp = 2, .bit = 14, .pwm_fn =	2 },
> +	[3] = { .ch = 3, .grp = 3, .bit = 0,  .pwm_fn = 2 },
> +};
> +#endif
> +
> +int pwm_enable(int pwm_id)
> +{
> +	const struct s5p_timer *pwm =
> +#if defined(CONFIG_ARCH_NEXELL)
> +			(struct s5p_timer *)PHY_BASEADDR_PWM;
> +#else
> +			(struct s5p_timer *)samsung_get_base_timer();
> +#endif
> +	unsigned long tcon;
> +
> +	tcon = readl(&pwm->tcon);
> +	tcon |= TCON_START(pwm_id);
> +
> +	writel(tcon, &pwm->tcon);
> +
> +	return 0;
> +}
> +
> +void pwm_disable(int pwm_id)
> +{
> +	const struct s5p_timer *pwm =
> +#if defined(CONFIG_ARCH_NEXELL)
> +			(struct s5p_timer *)PHY_BASEADDR_PWM;
> +#else
> +			(struct s5p_timer *)samsung_get_base_timer();
> +#endif
> +	unsigned long tcon;
> +
> +	tcon = readl(&pwm->tcon);
> +	tcon &= ~TCON_START(pwm_id);
> +
> +	writel(tcon, &pwm->tcon);
> +}
> +
> +static unsigned long pwm_calc_tin(int pwm_id, unsigned long freq)
> +{
> +	unsigned long tin_parent_rate;
> +	unsigned int div;
> +#if defined(CONFIG_ARCH_NEXELL)
> +	unsigned int pre_div;
> +	const struct s5p_timer *pwm =
> +		(struct s5p_timer *)PHY_BASEADDR_PWM;
> +	unsigned int val;
> +#endif
> +
> +#if defined(CONFIG_ARCH_NEXELL)
> +	struct clk *clk = clk_get(CORECLK_NAME_PCLK);
> +
> +	tin_parent_rate = clk_get_rate(clk);
> +#else
> +	tin_parent_rate = get_pwm_clk();
> +#endif
> +
> +#if defined(CONFIG_ARCH_NEXELL)
> +	writel(0, &pwm->tcfg0);
> +	val = readl(&pwm->tcfg0);
> +
> +	if (pwm_id < 2)
> +		div = ((val >> 0) & 0xff) + 1;
> +	else
> +		div = ((val >> 8) & 0xff) + 1;
> +
> +	writel(0, &pwm->tcfg1);
> +	val = readl(&pwm->tcfg1);
> +	val = (val >> MUX_DIV_SHIFT(pwm_id)) & 0xF;
> +	pre_div = (1UL << val);
> +
> +	freq = tin_parent_rate / div / pre_div;
> +
> +	return freq;
> +#else
> +	for (div = 2; div <= 16; div *= 2) {
> +		if ((tin_parent_rate / (div << 16)) < freq)
> +			return tin_parent_rate / div;
> +	}
> +
> +	return tin_parent_rate / 16;
> +#endif
> +}
> +
> +#define NS_IN_SEC 1000000000UL
> +
> +int pwm_config(int pwm_id, int duty_ns, int period_ns)
> +{
> +	const struct s5p_timer *pwm =
> +#if defined(CONFIG_ARCH_NEXELL)
> +		(struct s5p_timer *)PHY_BASEADDR_PWM;
> +#else
> +		(struct s5p_timer *)samsung_get_base_timer();
> +#endif
> +	unsigned int offset;
> +	unsigned long tin_rate;
> +	unsigned long tin_ns;
> +	unsigned long frequency;
> +	unsigned long tcon;
> +	unsigned long tcnt;
> +	unsigned long tcmp;
> +
> +	/*
> +	 * We currently avoid using 64bit arithmetic by using the
> +	 * fact that anything faster than 1GHz is easily representable
> +	 * by 32bits.
> +	 */
> +	if (period_ns > NS_IN_SEC || duty_ns > NS_IN_SEC || period_ns == 0)
> +		return -ERANGE;
> +
> +	if (duty_ns > period_ns)
> +		return -EINVAL;
> +
> +	frequency = NS_IN_SEC / period_ns;
> +
> +	/* Check to see if we are changing the clock rate of the PWM */
> +	tin_rate = pwm_calc_tin(pwm_id, frequency);
> +
> +	tin_ns = NS_IN_SEC / tin_rate;
> +#if defined(CONFIG_ARCH_NEXELL)
> +	/* The counter starts at zero. */
> +	tcnt = (period_ns / tin_ns) - 1;
> +#else
> +	tcnt = period_ns / tin_ns;
> +#endif
> +
> +	/* Note, counters count down */
> +	tcmp = duty_ns / tin_ns;
> +	tcmp = tcnt - tcmp;
> +
> +	/* Update the PWM register block. */
> +	offset = pwm_id * 3;
> +	if (pwm_id < 4) {
> +		writel(tcnt, &pwm->tcntb0 + offset);
> +		writel(tcmp, &pwm->tcmpb0 + offset);
> +	}
> +
> +	tcon = readl(&pwm->tcon);
> +	tcon |= TCON_UPDATE(pwm_id);
> +	if (pwm_id < 4)
> +		tcon |= TCON_AUTO_RELOAD(pwm_id);
> +	else
> +		tcon |= TCON4_AUTO_RELOAD;
> +	writel(tcon, &pwm->tcon);
> +
> +	tcon &= ~TCON_UPDATE(pwm_id);
> +	writel(tcon, &pwm->tcon);
> +
> +	return 0;
> +}
> +
> +int pwm_init(int pwm_id, int div, int invert)
> +{
> +	u32 val;
> +	const struct s5p_timer *pwm =
> +#if defined(CONFIG_ARCH_NEXELL)
> +			(struct s5p_timer *)PHY_BASEADDR_PWM;
> +#else
> +			(struct s5p_timer *)samsung_get_base_timer();
> +#endif
> +	unsigned long ticks_per_period;
> +	unsigned int offset, prescaler;
> +
> +	/*
> +	 * Timer Freq(HZ) =
> +	 *	PWM_CLK / { (prescaler_value + 1) * (divider_value) }
> +	 */
> +
> +	val = readl(&pwm->tcfg0);
> +	if (pwm_id < 2) {
> +		prescaler = PRESCALER_0;
> +		val &= ~0xff;
> +		val |= (prescaler & 0xff);
> +	} else {
> +		prescaler = PRESCALER_1;
> +		val &= ~(0xff << 8);
> +		val |= (prescaler & 0xff) << 8;
> +	}
> +	writel(val, &pwm->tcfg0);
> +	val = readl(&pwm->tcfg1);
> +	val &= ~(0xf << MUX_DIV_SHIFT(pwm_id));
> +	val |= (div & 0xf) << MUX_DIV_SHIFT(pwm_id);
> +	writel(val, &pwm->tcfg1);
> +
> +	if (pwm_id == 4) {
> +		/*
> +		 * TODO(sjg): Use this as a countdown timer for now. We count
> +		 * down from the maximum value to 0, then reset.
> +		 */
> +		ticks_per_period = -1UL;
> +	} else {
> +		const unsigned long pwm_hz = 1000;
> +#if defined(CONFIG_ARCH_NEXELL)
> +		struct clk *clk = clk_get(CORECLK_NAME_PCLK);
> +		unsigned long timer_rate_hz = clk_get_rate(clk) /
> +#else
> +		unsigned long timer_rate_hz = get_pwm_clk() /
> +#endif
> +			((prescaler + 1) * (1 << div));
> +
> +		ticks_per_period = timer_rate_hz / pwm_hz;
> +	}
> +
> +	/* set count value */
> +	offset = pwm_id * 3;
> +
> +	writel(ticks_per_period, &pwm->tcntb0 + offset);
> +
> +	val = readl(&pwm->tcon) & ~(0xf << TCON_OFFSET(pwm_id));
> +	if (invert && pwm_id < 4)
> +		val |= TCON_INVERTER(pwm_id);
> +	writel(val, &pwm->tcon);
> +
> +	nx_gpio_set_pad_function(pwm_dev[pwm_id].grp, pwm_dev[pwm_id].bit,
> +				 pwm_dev[pwm_id].pwm_fn);
> +	pwm_enable(pwm_id);
> +
> +	return 0;
> +}
> diff --git a/drivers/pwm/pwm-nexell.h b/drivers/pwm/pwm-nexell.h
> new file mode 100644
> index 0000000..92dc707
> --- /dev/null
> +++ b/drivers/pwm/pwm-nexell.h
> @@ -0,0 +1,54 @@
> +/* SPDX-License-Identifier: GPL-2.0+
> + *
> + * Copyright (C) 2009 Samsung Electronics
> + * Kyungmin Park <kyungmin.park@samsung.com>
> + * Minkyu Kang <mk7.kang@samsung.com>
> + */
> +
> +#ifndef __ASM_ARM_ARCH_PWM_H_
> +#define __ASM_ARM_ARCH_PWM_H_
> +
> +#define PRESCALER_0		(8 - 1)		/* prescaler of timer 0, 1 */
> +#define PRESCALER_1		(16 - 1)	/* prescaler of timer 2, 3, 4 */
> +
> +/* Divider MUX */
> +#define MUX_DIV_1		0		/* 1/1 period */
> +#define MUX_DIV_2		1		/* 1/2 period */
> +#define MUX_DIV_4		2		/* 1/4 period */
> +#define MUX_DIV_8		3		/* 1/8 period */
> +#define MUX_DIV_16		4		/* 1/16 period */
> +
> +#define MUX_DIV_SHIFT(x)	((x) * 4)
> +
> +#define TCON_OFFSET(x)		(((x) + 1) * (!!x) << 2)
> +
> +#define TCON_START(x)		(1 << TCON_OFFSET(x))
> +#define TCON_UPDATE(x)		(1 << (TCON_OFFSET(x) + 1))
> +#define TCON_INVERTER(x)	(1 << (TCON_OFFSET(x) + 2))
> +#define TCON_AUTO_RELOAD(x)	(1 << (TCON_OFFSET(x) + 3))
> +#define TCON4_AUTO_RELOAD	(1 << 22)
> +
> +#ifndef __ASSEMBLY__
> +struct s5p_timer {
> +	unsigned int	tcfg0;
> +	unsigned int	tcfg1;
> +	unsigned int	tcon;
> +	unsigned int	tcntb0;
> +	unsigned int	tcmpb0;
> +	unsigned int	tcnto0;
> +	unsigned int	tcntb1;
> +	unsigned int	tcmpb1;
> +	unsigned int	tcnto1;
> +	unsigned int	tcntb2;
> +	unsigned int	tcmpb2;
> +	unsigned int	tcnto2;
> +	unsigned int	tcntb3;
> +	unsigned int	res1;
> +	unsigned int	tcnto3;
> +	unsigned int	tcntb4;
> +	unsigned int	tcnto4;
> +	unsigned int	tintcstat;
> +};
> +#endif	/* __ASSEMBLY__ */
> +
> +#endif
> 

-- 
DENX Software Engineering GmbH,      Managing Director: Wolfgang Denk
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: +49-8142-66989-52   Fax: +49-8142-66989-80   Email: hs at denx.de

  reply	other threads:[~2020-02-04  6:58 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-02-03 19:39 [RFC PATCH 00/10] arm: add support for SoC S5P4418 Stefan Bosch
2020-02-03 20:32 ` [RFC PATCH 01/10] arm: add mach-nexell (header files) Stefan Bosch
2020-02-03 20:38 ` [RFC PATCH 02/10] arm: add mach-nexell (all files except header files) Stefan Bosch
2020-02-03 20:40 ` [RFC PATCH 03/10] i2c: mmc: add nexell driver (gpio, i2c, mmc, pwm) Stefan Bosch
2020-02-04  6:58   ` Heiko Schocher [this message]
2020-02-04 18:29     ` Stefan B.
2020-02-20 17:49     ` Stefan B.
2020-02-22 12:34       ` Heiko Schocher
2020-02-03 20:41 ` [RFC PATCH 04/10] video: add nexell video driver (soc: displaytop) Stefan Bosch
2020-02-03 20:43 ` [RFC PATCH 05/10] video: add nexell video driver (soc: mlc, mipi) Stefan Bosch
2020-02-03 20:44 ` [RFC PATCH 06/10] video: add nexell video driver (soc: lvds, hdmi) Stefan Bosch
2020-02-03 20:45 ` [RFC PATCH 07/10] video: add nexell video driver (soc: dpc, makefile) Stefan Bosch
2020-02-03 20:45 ` [RFC PATCH 08/10] video: add nexell video driver (display/video driver) Stefan Bosch
2020-02-03 20:46 ` [RFC PATCH 09/10] arm: add support for SoC s5p4418 (cpu) / nanopi2 board Stefan Bosch
2020-02-07 16:10   ` Tom Rini
2020-02-03 20:47 ` [RFC PATCH 10/10] arm: add (default) config for " Stefan Bosch
2020-02-07 16:11 ` [RFC PATCH 00/10] arm: add support for SoC S5P4418 Tom Rini
2020-02-22 13:06 ` Amit Tomer
2020-02-25 19:13   ` Stefan B.

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1b85e42f-e504-ffed-eebf-1caabe648cfd@denx.de \
    --to=hs@denx.de \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.