From mboxrd@z Thu Jan 1 00:00:00 1970 From: Heiko Schocher Date: Tue, 4 Feb 2020 07:58:16 +0100 Subject: [RFC PATCH 03/10] i2c: mmc: add nexell driver (gpio, i2c, mmc, pwm) In-Reply-To: <1580762412-6260-1-git-send-email-stefan_b@posteo.net> References: <1580758773-5287-1-git-send-email-stefan_b@posteo.net> <1580762412-6260-1-git-send-email-stefan_b@posteo.net> Message-ID: <1b85e42f-e504-ffed-eebf-1caabe648cfd@denx.de> List-Id: MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit To: u-boot@lists.denx.de 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 > -- 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