From mboxrd@z Thu Jan 1 00:00:00 1970 From: Stefan B. Date: Tue, 4 Feb 2020 19:29:43 +0100 Subject: [RFC PATCH 03/10] i2c: mmc: add nexell driver (gpio, i2c, mmc, pwm) In-Reply-To: <1b85e42f-e504-ffed-eebf-1caabe648cfd@denx.de> References: <1580758773-5287-1-git-send-email-stefan_b@posteo.net> <1580762412-6260-1-git-send-email-stefan_b@posteo.net> <1b85e42f-e504-ffed-eebf-1caabe648cfd@denx.de> Message-ID: List-Id: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit To: u-boot@lists.denx.de Hello Heiko, thanks a lot for your annotations and suggestions. I will have a look at them and give you feedback ASAP. 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 >> --- >> >>   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 >> + */ >> + >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> + >> +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 >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> + >> +#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 > > 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 >> + * >> + * (C) Copyright 2019 Stefan Bosch >> + */ >> + >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> + >> +#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 >> + */ >> + >> +/* This codes are copied from arch/arm/cpu/armv7/s5p-common/pwm.c */ >> + >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include "pwm-nexell.h" >> + >> +#if defined(CONFIG_ARCH_NEXELL) >> +#include >> +#include >> +#include >> +#include >> + >> +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 >> + * Minkyu Kang >> + */ >> + >> +#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 >> >