[RFC PATCH 03/10] i2c: mmc: add nexell driver (gpio, i2c, mmc, pwm)
Heiko Schocher
hs at denx.de
Sat Feb 22 13:34:29 CET 2020
Hello Stefan,
Am 20.02.2020 um 18:49 schrieb Stefan B.:
> Hello Heiko,
>
> see below my feedback, please give me further advise where indicated.
>
> Unfortunately there have been some Bugs in the i2c-driver and I learned that this driver has not
> been used at all ("i2c-gpio" has been used instead). So I have done several Bugfixes and
> improvements appart from your proposals.
>
>
> Regards
> Stefan
>
>
> Am 04.02.20 um 07:58 schrieb Heiko Schocher:
>> 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 at 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!
>>
> Ok, I will split this patch.
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.
>
> The list is not sorted (at least in no alphabetical order), but I can e.g. move "... += nx_gpio.o"
> one line up?
Find for me.
>>> 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 at 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(®s->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(®s->data, 1 << pin);
>>> + else
>>> + setbits_le32(®s->pad_reset, 1 << pin);
>>> +
>>> + setbits_le32(®s->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(®s->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(®s->data, 1 << pin);
>>> + else
>>> + clrbits_le32(®s->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(®s->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(®s->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(®s->data, 1 << pin);
>>> + else
>>> + clrbits_le32(®s->data, 1 << pin);
>>> +
>>> + setbits_le32(®s->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(®s->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(®s->data, 1 << pin);
>>> + else
>>> + clrbits_le32(®s->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(®s->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.
>
> No, it is not configurable. I have changed it to MAX_I2C_NUM.
Ok.
>>> +
>>> +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?
>
> According to the above FIXME comment from Nexell it probably should. But there is no pincontrol
> driver implemented. But is the change to a driver necessary for now?
Hmm.. it would be better to have a pinctrl driver, but I can accept it
for now.
>>> +
>>> +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 ?
>
> DEV_NAME_I2C is defined in arch/arm/mach-nexell/include/mach/nexell.h
Ah, ok.
>
>>
>>> + 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
>>
>
> Ok, I have changed this as proposed.
Thanks!
>>> +}
>>> +
>>> +/* 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
>>
>
> I'd like to allow all values of speed. In my opinion allowing only standard speeds does complicate
> things (e.g. how to do error handling?). Furthermore I think sometimes it could be handy to be able
> to set speed to an arbitrary value (e.g. to a lower value than 100000) when trying a new i2c-device
> on the bus.
ok.
>
>>> +
>>> + 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,"
>>
>
> I have changed "nexell,i2c-max-bus-freq" to the already defined "clock-frequency". Furthermore I
> have changed "nexell,i2c-sda-delay" to "i2c-sda-delay-ns".
> Furthermore, I have added "nx_i2c.txt" in doc/device-tree-bindings/i2c.
Thanks!
>
>> 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!
>>
>
> No, I do not plan to post a linux driver.
Ok.
>
>>> +}
>>> +
>>> +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
>>
>
> Ok
>
>>> + }
>>> +
>>> + /* 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
>>
>
> Ok
>
>> Hmm.. if you return here if target_speed is not set, it is not optional!
>>
>
> You are right, I have removed 'optional' in the comment.
Thanks.
>
>>> +
>>> + /* 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 at 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 at nexell.co.kr>
>>> + *
>>> + * (C) Copyright 2019 Stefan Bosch <stefan_b at 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 at 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 at samsung.com>
>>> + * Minkyu Kang <mk7.kang at 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
>>>
>>
>
bye,
Heiko
--
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
More information about the U-Boot
mailing list