From 79ccd6c7dc7055d5bbe375fddef925d7986c204d Mon Sep 17 00:00:00 2001 From: Greg Malysa Date: Wed, 26 Feb 2025 12:30:23 -0500 Subject: [PATCH 01/12] pinctrl: Add support for ADI SC5XX-family pinctrl This adds support for pin configuration on the Analog Devices SC5XX SoC family. This commit is largely a port of the Linux driver, which has not yet been submitted upstream. Co-developed-by: Nathan Barrett-Morrison Signed-off-by: Nathan Barrett-Morrison Co-developed-by: Ian Roberts Signed-off-by: Ian Roberts Signed-off-by: Vasileios Bimpikas Signed-off-by: Utsav Agarwal Signed-off-by: Arturs Artamonovs Signed-off-by: Oliver Gaskell Signed-off-by: Greg Malysa --- MAINTAINERS | 1 + drivers/pinctrl/Kconfig | 8 ++ drivers/pinctrl/Makefile | 1 + drivers/pinctrl/pinctrl-adi-adsp.c | 161 +++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 drivers/pinctrl/pinctrl-adi-adsp.c diff --git a/MAINTAINERS b/MAINTAINERS index c1851280e6e..af8d0b7d5e9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -631,6 +631,7 @@ F: doc/device-tree-bindings/arm/adi/adi,sc5xx.yaml F: doc/device-tree-bindings/clock/adi,sc5xx-clocks.yaml F: doc/device-tree-bindings/timer/adi,sc5xx-gptimer.yaml F: drivers/clk/adi/ +F: drivers/pinctrl/pinctrl-adi-adsp.c F: drivers/serial/serial_adi_uart4.c F: drivers/timer/adi_sc5xx_timer.c F: include/configs/sc5* diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 6ee7dc1cce8..687fb339ea0 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -178,6 +178,14 @@ config PINCTRL_APPLE both the GPIO definitions and pin control functions for each available multiplex function. +config PINCTRL_ADI + bool "ADI pinctrl driver" + depends on DM && ARCH_SC5XX + help + This driver enables pinctrl support on SC5xx processors. This + driver covers only the pin configuration functionality, and + GPIO functionality is contained in the separate GPIO driver. + config PINCTRL_AR933X bool "QCA/Athores ar933x pin control driver" depends on DM && SOC_AR933X diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index 634047a91f4..e3a82b12c78 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -3,6 +3,7 @@ obj-y += pinctrl-uclass.o obj-$(CONFIG_$(XPL_)PINCTRL_GENERIC) += pinctrl-generic.o +obj-$(CONFIG_PINCTRL_ADI) += pinctrl-adi-adsp.o obj-$(CONFIG_PINCTRL_APPLE) += pinctrl-apple.o obj-$(CONFIG_PINCTRL_AT91) += pinctrl-at91.o obj-$(CONFIG_PINCTRL_AT91PIO4) += pinctrl-at91-pio4.o diff --git a/drivers/pinctrl/pinctrl-adi-adsp.c b/drivers/pinctrl/pinctrl-adi-adsp.c new file mode 100644 index 00000000000..debf434212d --- /dev/null +++ b/drivers/pinctrl/pinctrl-adi-adsp.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright 2022 - Analog Devices, Inc. + * + * Written and/or maintained by Timesys Corporation + * + * Author: Greg Malysa + * Additional Contact: Nathan Barrett-Morrison + * + * dm pinctrl implementation for ADI ADSP SoCs + * + */ + +#include +#include +#include +#include +#include + +#define ADSP_PORT_MMIO_SIZE 0x80 +#define ADSP_PORT_PIN_SIZE 16 + +#define ADSP_PORT_PORT_MUX_BITS 2 +#define ADSP_PORT_PORT_MUX_MASK 0x03 +#define ADSP_PINCTRL_FUNCTION_COUNT 4 + +#define ADSP_PORT_REG_FER 0x00 +#define ADSP_PORT_REG_FER_SET 0x04 +#define ADSP_PORT_REG_FER_CLEAR 0x08 +#define ADSP_PORT_REG_DATA 0x0c +#define ADSP_PORT_REG_DATA_SET 0x10 +#define ADSP_PORT_REG_DATA_CLEAR 0x14 +#define ADSP_PORT_REG_DIR 0x18 +#define ADSP_PORT_REG_DIR_SET 0x1c +#define ADSP_PORT_REG_DIR_CLEAR 0x20 +#define ADSP_PORT_REG_INEN 0x24 +#define ADSP_PORT_REG_INEN_SET 0x28 +#define ADSP_PORT_REG_INEN_CLEAR 0x2c +#define ADSP_PORT_REG_PORT_MUX 0x30 +#define ADSP_PORT_REG_DATA_TGL 0x34 +#define ADSP_PORT_REG_POLAR 0x38 +#define ADSP_PORT_REG_POLAR_SET 0x3c +#define ADSP_PORT_REG_POLAR_CLEAR 0x40 +#define ADSP_PORT_REG_LOCK 0x44 +#define ADSP_PORT_REG_TRIG_TGL 0x48 + +struct adsp_pinctrl_priv { + void __iomem *base; + int npins; + char pinbuf[16]; +}; + +static u32 get_port(unsigned int pin) +{ + return pin / ADSP_PORT_PIN_SIZE; +} + +static u32 get_offset(unsigned int pin) +{ + return pin % ADSP_PORT_PIN_SIZE; +} + +static int adsp_pinctrl_pinmux_set(struct udevice *udev, unsigned int pin, unsigned int func) +{ + struct adsp_pinctrl_priv *priv = dev_get_priv(udev); + void __iomem *portbase; + u32 port, offset; + u32 val; + + if (pin >= priv->npins) + return -ENODEV; + + if (func >= ADSP_PINCTRL_FUNCTION_COUNT) + return -EINVAL; + + port = get_port(pin); + offset = get_offset(pin); + portbase = priv->base + port * ADSP_PORT_MMIO_SIZE; + + val = ioread32(portbase + ADSP_PORT_REG_PORT_MUX); + val &= ~(ADSP_PORT_PORT_MUX_MASK << (ADSP_PORT_PORT_MUX_BITS * offset)); + val |= func << (ADSP_PORT_PORT_MUX_BITS * offset); + iowrite32(val, portbase + ADSP_PORT_REG_PORT_MUX); + + iowrite32(BIT(offset), portbase + ADSP_PORT_REG_FER_SET); + return 0; +} + +static int adsp_pinctrl_set_state(struct udevice *udev, struct udevice *config) +{ + const struct fdt_property *pinlist; + int length = 0; + int ret, i; + u32 pin, function; + + pinlist = dev_read_prop(config, "adi,pins", &length); + if (!pinlist) { + dev_err(udev, "missing adi,pins property in pinctrl config node\n"); + return -EINVAL; + } + + if (length % (sizeof(uint32_t) * 2)) { + dev_err(udev, "adi,pins property must be a multiple of two uint32_ts\n"); + return -EINVAL; + } + + for (i = 0; i < length / sizeof(uint32_t); i += 2) { + ret = dev_read_u32_index(config, "adi,pins", i, &pin); + if (ret) + return ret; + + ret = dev_read_u32_index(config, "adi,pins", i + 1, &function); + if (ret) + return ret; + + ret = adsp_pinctrl_pinmux_set(udev, pin, function); + if (ret) + return ret; + } + + return 0; +} + +const struct pinctrl_ops adsp_pinctrl_ops = { + .set_state = adsp_pinctrl_set_state, +}; + +static int adsp_pinctrl_probe(struct udevice *udev) +{ + struct adsp_pinctrl_priv *priv = dev_get_priv(udev); + + priv->base = dev_read_addr_ptr(udev); + priv->npins = dev_read_u32_default(udev, "adi,npins", 0); + + if (!priv->base) { + dev_err(udev, "Missing or invalid pinctrl base address\n"); + return -ENOENT; + } + + if (!priv->npins) { + dev_err(udev, "Missing adi,npins property!\n"); + return -ENOENT; + } + + return 0; +} + +static const struct udevice_id adsp_pinctrl_match[] = { + { .compatible = "adi,adsp-pinctrl" }, + { }, +}; + +U_BOOT_DRIVER(adi_adsp_pinctrl) = { + .name = "adi_adsp_pinctrl", + .id = UCLASS_PINCTRL, + .of_match = adsp_pinctrl_match, + .probe = adsp_pinctrl_probe, + .priv_auto = sizeof(struct adsp_pinctrl_priv), + .ops = &adsp_pinctrl_ops, + .flags = DM_FLAG_PRE_RELOC, +}; From a0ffd8d7cd8efb8b348c07fa18a610a4534f9bea Mon Sep 17 00:00:00 2001 From: Greg Malysa Date: Wed, 26 Feb 2025 12:30:24 -0500 Subject: [PATCH 02/12] doc: Add dt-bindings and descriptions for ADI SC5xx-family pinctrl This adds the necessary dt-bindings and documentation to use the ADI SC5xx pinctrl driver in a device tree. It is not yet available upstream in the Linux kernel. Eventually, it will be moved there. Signed-off-by: Vasileios Bimpikas Signed-off-by: Utsav Agarwal Signed-off-by: Arturs Artamonovs Signed-off-by: Greg Malysa --- MAINTAINERS | 2 + .../pinctrl/adi,adsp-pinctrl.yaml | 73 +++++++++++++++++++ include/dt-bindings/pinctrl/adi-adsp.h | 21 ++++++ 3 files changed, 96 insertions(+) create mode 100644 doc/device-tree-bindings/pinctrl/adi,adsp-pinctrl.yaml create mode 100644 include/dt-bindings/pinctrl/adi-adsp.h diff --git a/MAINTAINERS b/MAINTAINERS index af8d0b7d5e9..abdb418b599 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -629,12 +629,14 @@ F: arch/arm/mach-sc5xx/ F: board/adi/ F: doc/device-tree-bindings/arm/adi/adi,sc5xx.yaml F: doc/device-tree-bindings/clock/adi,sc5xx-clocks.yaml +F: doc/device-tree-bindings/pinctrl/adi,adsp-pinctrl.yaml F: doc/device-tree-bindings/timer/adi,sc5xx-gptimer.yaml F: drivers/clk/adi/ F: drivers/pinctrl/pinctrl-adi-adsp.c F: drivers/serial/serial_adi_uart4.c F: drivers/timer/adi_sc5xx_timer.c F: include/configs/sc5* +F: include/dt-bindings/pinctrl/adi-adsp.h F: include/env/adi/ ARM SNAPDRAGON diff --git a/doc/device-tree-bindings/pinctrl/adi,adsp-pinctrl.yaml b/doc/device-tree-bindings/pinctrl/adi,adsp-pinctrl.yaml new file mode 100644 index 00000000000..418ebd5ce41 --- /dev/null +++ b/doc/device-tree-bindings/pinctrl/adi,adsp-pinctrl.yaml @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: (GPL-2.0+ OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pinctrl/adi,adsp-pinctrl.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Pinctrl Driver for Analog Devices SC5xx Processors + +maintainers: + - Vasileios Bimpikas + - Utsav Agarwal + - Arturs Artamonovs + +description: | + This driver provides an interface for performing pin configuration + Analog Devices SoCs using the ADSP PORT hardware for pin + configuration according to the HRM. Currently this is only the + SC5xx series. + +properties: + compatible: + const: adi,adsp-pinctrl + + reg: + maxItems: 1 + + adi,npins: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Total number of pins available to this SoC's pin controller, + found in the HRM. + +patternProperties: + '_pins$': + type: object + + properties: + adi,pins: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + For n pins, 2n values must be provided as a sequence of pin + name as identified with the ADI_ADSP_PIN() macro and a pin + function constant, both defined in + include/dt-bindings/pinctrl/adi-adsp.h. + + required: + - adi,pins + + additionalProperties: false + +required: + - compatible + - reg + - adi,npins + +additionalProperties: false + +examples: + - | + #include + + soc { + pinctrl0: pinctrl@0x31004000 { + compatible = "adi,adsp-pinctrl"; + reg = <0x31004000 0x500>; + adi,npins = <135>; + uart0_default: uart0_pins { + adi,pins = , + ; + }; + }; + + }; diff --git a/include/dt-bindings/pinctrl/adi-adsp.h b/include/dt-bindings/pinctrl/adi-adsp.h new file mode 100644 index 00000000000..7dc8a1ef5c4 --- /dev/null +++ b/include/dt-bindings/pinctrl/adi-adsp.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * (C) Copyright 2022 - Analog Devices, Inc. + * + * Written and/or maintained by Timesys Corporation + * + * Contact: Nathan Barrett-Morrison + * Contact: Greg Malysa + * + */ + +#ifndef DT_BINDINGS_PINCTRL_ADI_ADSP +#define DT_BINDINGS_PINCTRL_ADI_ADSP + +#define ADI_ADSP_PIN(port, pin) (16 * ((port) - 'A') + (pin)) +#define ADI_ADSP_PINFUNC_ALT0 0 +#define ADI_ADSP_PINFUNC_ALT1 1 +#define ADI_ADSP_PINFUNC_ALT2 2 +#define ADI_ADSP_PINFUNC_ALT3 3 + +#endif From 1e87f0ed79100d915540658eda76e4e125713323 Mon Sep 17 00:00:00 2001 From: Greg Malysa Date: Wed, 26 Feb 2025 12:30:25 -0500 Subject: [PATCH 03/12] gpio: Add support for SC5XX-family processor GPIO driver This adds support for using the GPIO pins on the SC5XX family of SoCs from Analog Devices. Co-developed-by: Nathan Barrett-Morrison Signed-off-by: Nathan Barrett-Morrison Co-developed-by: Ian Roberts Signed-off-by: Ian Roberts Signed-off-by: Vasileios Bimpikas Signed-off-by: Utsav Agarwal Signed-off-by: Arturs Artamonovs Signed-off-by: Oliver Gaskell Signed-off-by: Greg Malysa --- MAINTAINERS | 1 + drivers/gpio/Kconfig | 9 ++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-adi-adsp.c | 179 +++++++++++++++++++++++++++++++++++ 4 files changed, 190 insertions(+) create mode 100644 drivers/gpio/gpio-adi-adsp.c diff --git a/MAINTAINERS b/MAINTAINERS index abdb418b599..5c16c8f6646 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -632,6 +632,7 @@ F: doc/device-tree-bindings/clock/adi,sc5xx-clocks.yaml F: doc/device-tree-bindings/pinctrl/adi,adsp-pinctrl.yaml F: doc/device-tree-bindings/timer/adi,sc5xx-gptimer.yaml F: drivers/clk/adi/ +F: drivers/gpio/gpio-adi-adsp.c F: drivers/pinctrl/pinctrl-adi-adsp.c F: drivers/serial/serial_adi_uart4.c F: drivers/timer/adi_sc5xx_timer.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index f4a453e1cdd..00b01f5f09e 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -97,6 +97,15 @@ config SPL_DM_GPIO_LOOKUP_LABEL different gpios on different hardware versions for the same functionality in board code. +config ADI_GPIO + bool "ADI GPIO driver" + depends on DM_GPIO && ARCH_SC5XX + help + This driver supports GPIO banks on SC5xx processors. It + supports inputs and outputs but does not support pin + interrupt functionality (PINT) or other features in the + Linux version of the driver. + config ALTERA_PIO bool "Altera PIO driver" depends on DM_GPIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 3f882c065d8..e25ede1813c 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_$(PHASE_)DM_GPIO) += gpio-uclass.o obj-$(CONFIG_$(XPL_)DM_PCA953X) += pca953x_gpio.o +obj-$(CONFIG_ADI_GPIO) += gpio-adi-adsp.o obj-$(CONFIG_ASPEED_GPIO) += gpio-aspeed.o obj-$(CONFIG_ASPEED_G7_GPIO) += gpio-aspeed-g7.o obj-$(CONFIG_ASPEED_SGPIO) += gpio-aspeed-sgpio.o diff --git a/drivers/gpio/gpio-adi-adsp.c b/drivers/gpio/gpio-adi-adsp.c new file mode 100644 index 00000000000..0ce00572e08 --- /dev/null +++ b/drivers/gpio/gpio-adi-adsp.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright 2022 - Analog Devices, Inc. + * + * Written and/or maintained by Timesys Corporation + * + * Author: Greg Malysa + * Additional Contact: Nathan Barrett-Morrison + */ + +#include +#include +#include +#include +#include + +#define ADSP_PORT_MMIO_SIZE 0x80 +#define ADSP_PORT_PIN_SIZE 16 + +#define ADSP_PORT_REG_FER 0x00 +#define ADSP_PORT_REG_FER_SET 0x04 +#define ADSP_PORT_REG_FER_CLEAR 0x08 +#define ADSP_PORT_REG_DATA 0x0c +#define ADSP_PORT_REG_DATA_SET 0x10 +#define ADSP_PORT_REG_DATA_CLEAR 0x14 +#define ADSP_PORT_REG_DIR 0x18 +#define ADSP_PORT_REG_DIR_SET 0x1c +#define ADSP_PORT_REG_DIR_CLEAR 0x20 +#define ADSP_PORT_REG_INEN 0x24 +#define ADSP_PORT_REG_INEN_SET 0x28 +#define ADSP_PORT_REG_INEN_CLEAR 0x2c +#define ADSP_PORT_REG_PORT_MUX 0x30 +#define ADSP_PORT_REG_DATA_TGL 0x34 +#define ADSP_PORT_REG_POLAR 0x38 +#define ADSP_PORT_REG_POLAR_SET 0x3c +#define ADSP_PORT_REG_POLAR_CLEAR 0x40 +#define ADSP_PORT_REG_LOCK 0x44 +#define ADSP_PORT_REG_TRIG_TGL 0x48 + +struct adsp_gpio_priv { + void __iomem *base; + int ngpio; +}; + +static u32 get_port(unsigned int pin) +{ + return pin / ADSP_PORT_PIN_SIZE; +} + +static u32 get_offset(unsigned int pin) +{ + return pin % ADSP_PORT_PIN_SIZE; +} + +static int adsp_gpio_input(struct udevice *udev, unsigned int pin) +{ + struct adsp_gpio_priv *priv = dev_get_priv(udev); + u32 port, offset; + void __iomem *portbase; + + if (pin < priv->ngpio) { + port = get_port(pin); + offset = get_offset(pin); + portbase = priv->base + port * ADSP_PORT_MMIO_SIZE; + + iowrite16(BIT(offset), portbase + ADSP_PORT_REG_FER_CLEAR); + iowrite16(BIT(offset), portbase + ADSP_PORT_REG_DIR_CLEAR); + iowrite16(BIT(offset), portbase + ADSP_PORT_REG_INEN_SET); + return 0; + } + + return -EINVAL; +} + +static int adsp_gpio_output(struct udevice *udev, unsigned int pin, int value) +{ + struct adsp_gpio_priv *priv = dev_get_priv(udev); + u32 port, offset; + void __iomem *portbase; + + if (pin < priv->ngpio) { + port = get_port(pin); + offset = get_offset(pin); + portbase = priv->base + port * ADSP_PORT_MMIO_SIZE; + + iowrite16(BIT(offset), portbase + ADSP_PORT_REG_FER_CLEAR); + + if (value) + iowrite16(BIT(offset), portbase + ADSP_PORT_REG_DATA_SET); + else + iowrite16(BIT(offset), portbase + ADSP_PORT_REG_DATA_CLEAR); + + iowrite16(BIT(offset), portbase + ADSP_PORT_REG_DIR_SET); + iowrite16(BIT(offset), portbase + ADSP_PORT_REG_INEN_CLEAR); + return 0; + } + + return -EINVAL; +} + +static int adsp_gpio_get_value(struct udevice *udev, unsigned int pin) +{ + struct adsp_gpio_priv *priv = dev_get_priv(udev); + u32 port, offset; + u16 val; + void __iomem *portbase; + + if (pin < priv->ngpio) { + port = get_port(pin); + offset = get_offset(pin); + portbase = priv->base + port * ADSP_PORT_MMIO_SIZE; + + val = ioread16(portbase + ADSP_PORT_REG_DATA); + return !!(val & BIT(offset)); + } + + return 0; +} + +static int adsp_gpio_set_value(struct udevice *udev, unsigned int pin, int value) +{ + struct adsp_gpio_priv *priv = dev_get_priv(udev); + u32 port, offset; + void __iomem *portbase; + + if (pin < priv->ngpio) { + port = get_port(pin); + offset = get_offset(pin); + portbase = priv->base + port * ADSP_PORT_MMIO_SIZE; + + if (value) + iowrite16(BIT(offset), portbase + ADSP_PORT_REG_DATA_SET); + else + iowrite16(BIT(offset), portbase + ADSP_PORT_REG_DATA_CLEAR); + } + + return 0; +} + +static const struct dm_gpio_ops adsp_gpio_ops = { + .direction_input = adsp_gpio_input, + .direction_output = adsp_gpio_output, + .get_value = adsp_gpio_get_value, + .set_value = adsp_gpio_set_value, +}; + +static int adsp_gpio_probe(struct udevice *udev) +{ + struct adsp_gpio_priv *priv = dev_get_priv(udev); + struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(udev); + + uc_priv->bank_name = "adsp gpio"; + uc_priv->gpio_count = dev_read_u32_default(udev, "adi,ngpios", 0); + + if (!uc_priv->gpio_count) { + dev_err(udev, "Missing adi,ngpios property!\n"); + return -ENOENT; + } + + priv->base = dev_read_addr_ptr(udev); + priv->ngpio = uc_priv->gpio_count; + + return 0; +} + +static const struct udevice_id adsp_gpio_match[] = { + { .compatible = "adi,adsp-gpio" }, + { }, +}; + +U_BOOT_DRIVER(adi_adsp_gpio) = { + .name = "adi_adsp_gpio", + .id = UCLASS_GPIO, + .ops = &adsp_gpio_ops, + .probe = adsp_gpio_probe, + .priv_auto = sizeof(struct adsp_gpio_priv), + .of_match = adsp_gpio_match, + .flags = DM_FLAG_PRE_RELOC, +}; From 446179627fcac2e99aaa329a1d6cf49805482c6b Mon Sep 17 00:00:00 2001 From: Nathan Barrett-Morrison Date: Wed, 26 Feb 2025 12:30:26 -0500 Subject: [PATCH 04/12] gpio: Add support for ADI ADP5588 GPIO expander chips This adds support for the ADP588 GPIO expander from Analog Devices. It is accessed over I2C and provides up to 18 pins. It is largely a port of the Linux driver developed by Michael Hennerich Signed-off-by: Ian Roberts Signed-off-by: Greg Malysa Signed-off-by: Vasileios Bimpikas Signed-off-by: Utsav Agarwal Signed-off-by: Arturs Artamonovs Signed-off-by: Oliver Gaskell Signed-off-by: Nathan Barrett-Morrison --- MAINTAINERS | 1 + drivers/gpio/Kconfig | 8 ++ drivers/gpio/Makefile | 1 + drivers/gpio/adp5588_gpio.c | 208 ++++++++++++++++++++++++++++++++++++ 4 files changed, 218 insertions(+) create mode 100644 drivers/gpio/adp5588_gpio.c diff --git a/MAINTAINERS b/MAINTAINERS index 5c16c8f6646..bfb81bb9891 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -632,6 +632,7 @@ F: doc/device-tree-bindings/clock/adi,sc5xx-clocks.yaml F: doc/device-tree-bindings/pinctrl/adi,adsp-pinctrl.yaml F: doc/device-tree-bindings/timer/adi,sc5xx-gptimer.yaml F: drivers/clk/adi/ +F: drivers/gpio/adp5588_gpio.c F: drivers/gpio/gpio-adi-adsp.c F: drivers/pinctrl/pinctrl-adi-adsp.c F: drivers/serial/serial_adi_uart4.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 00b01f5f09e..8d7a990b04f 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -554,6 +554,14 @@ config DM_PCA953X Now, max 24 bits chips and PCA953X compatible chips are supported +config ADP5588_GPIO + bool "ADP5588 GPIO expander driver" + depends on DM_GPIO && DM_I2C + help + Say yes here to support GPIO functionality of ADI ADP5588 chips. + + The ADP5588 is an 18-port I2C GPIO expander and keypad controller. + config SPL_DM_PCA953X bool "PCA95[357]x, PCA9698, TCA64xx, and MAX7310 I/O ports in SPL" depends on SPL_DM_GPIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index e25ede1813c..d426d3a2d7b 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_NOMADIK_GPIO) += nmk_gpio.o obj-$(CONFIG_MAX7320_GPIO) += max7320_gpio.o obj-$(CONFIG_$(XPL_)MAX77663_GPIO) += max77663_gpio.o obj-$(CONFIG_SL28CPLD_GPIO) += sl28cpld-gpio.o +obj-$(CONFIG_ADP5588_GPIO) += adp5588_gpio.o obj-$(CONFIG_ZYNQMP_GPIO_MODEPIN) += zynqmp_gpio_modepin.o obj-$(CONFIG_SLG7XL45106_I2C_GPO) += gpio_slg7xl45106.o obj-$(CONFIG_FTGPIO010) += ftgpio010.o diff --git a/drivers/gpio/adp5588_gpio.c b/drivers/gpio/adp5588_gpio.c new file mode 100644 index 00000000000..d081e169897 --- /dev/null +++ b/drivers/gpio/adp5588_gpio.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * GPIO Chip driver for Analog Devices + * ADP5588/ADP5587 I/O Expander and QWERTY Keypad Controller + * + * (C) Copyright 2022 - Analog Devices, Inc. + * + * Written and/or maintained by Timesys Corporation + * + * Contact: Nathan Barrett-Morrison + * Contact: Greg Malysa + * + * Based on Michael Hennerich's Linux driver: + * Michael Hennerich + * + */ + +#include +#include +#include + +#define ADP5588_MAXGPIO 18 +#define ADP5588_BANK(offs) ((offs) >> 3) +#define ADP5588_BIT(offs) (1u << ((offs) & 0x7)) + +#define DEV_ID 0x00 /* Device ID */ +#define GPIO_DAT_STAT1 0x14 /* GPIO Data Status, Read twice to clear */ +#define GPIO_DAT_STAT2 0x15 /* GPIO Data Status, Read twice to clear */ +#define GPIO_DAT_STAT3 0x16 /* GPIO Data Status, Read twice to clear */ +#define GPIO_DAT_OUT1 0x17 /* GPIO DATA OUT */ +#define GPIO_DAT_OUT2 0x18 /* GPIO DATA OUT */ +#define GPIO_DAT_OUT3 0x19 /* GPIO DATA OUT */ +#define GPIO_INT_EN1 0x1A /* GPIO Interrupt Enable */ +#define GPIO_INT_EN2 0x1B /* GPIO Interrupt Enable */ +#define GPIO_INT_EN3 0x1C /* GPIO Interrupt Enable */ +#define KP_GPIO1 0x1D /* Keypad or GPIO Selection */ +#define KP_GPIO2 0x1E /* Keypad or GPIO Selection */ +#define KP_GPIO3 0x1F /* Keypad or GPIO Selection */ +#define GPIO_DIR1 0x23 /* GPIO Data Direction */ +#define GPIO_DIR2 0x24 /* GPIO Data Direction */ +#define GPIO_DIR3 0x25 /* GPIO Data Direction */ +#define GPIO_PULL1 0x2C /* GPIO Pull Disable */ +#define GPIO_PULL2 0x2D /* GPIO Pull Disable */ +#define GPIO_PULL3 0x2E /* GPIO Pull Disable */ +#define ID_MASK 0x0F + +struct adp5588_gpio { + u8 dat_out[3]; + u8 dir[3]; +}; + +static int adp5588_gpio_read(struct udevice *dev, u8 reg) +{ + int ret; + u8 val; + + ret = dm_i2c_read(dev, reg, &val, 1); + + if (ret < 0) { + pr_err("%s: read error\n", __func__); + return ret; + } + + return val; +} + +static int adp5588_gpio_write(struct udevice *dev, u8 reg, u8 val) +{ + int ret; + + ret = dm_i2c_write(dev, reg, &val, 1); + if (ret < 0) { + pr_err("%s: write error\n", __func__); + return ret; + } + + return 0; +} + +static int adp5588_get_value(struct udevice *dev, u32 offset) +{ + struct adp5588_gpio *plat = dev_get_plat(dev); + unsigned int bank = ADP5588_BANK(offset); + unsigned int bit = ADP5588_BIT(offset); + int val; + + if (plat->dir[bank] & bit) + val = plat->dat_out[bank]; + else + val = adp5588_gpio_read(dev, GPIO_DAT_STAT1 + bank); + + return !!(val & bit); +} + +static int adp5588_set_value(struct udevice *dev, u32 offset, + int32_t value) +{ + unsigned int bank, bit; + int ret; + struct adp5588_gpio *plat = dev_get_plat(dev); + + bank = ADP5588_BANK(offset); + bit = ADP5588_BIT(offset); + + if (value) + plat->dat_out[bank] |= bit; + else + plat->dat_out[bank] &= ~bit; + + ret = adp5588_gpio_write(dev, GPIO_DAT_OUT1 + bank, + plat->dat_out[bank]); + + return ret; +} + +static int adp5588_direction_input(struct udevice *dev, u32 offset) +{ + int ret; + unsigned int bank; + struct adp5588_gpio *plat = dev_get_plat(dev); + + bank = ADP5588_BANK(offset); + + plat->dir[bank] &= ~ADP5588_BIT(offset); + ret = adp5588_gpio_write(dev, GPIO_DIR1 + bank, plat->dir[bank]); + + return ret; +} + +static int adp5588_direction_output(struct udevice *dev, + u32 offset, int value) +{ + int ret; + unsigned int bank, bit; + struct adp5588_gpio *plat = dev_get_plat(dev); + + bank = ADP5588_BANK(offset); + bit = ADP5588_BIT(offset); + + plat->dir[bank] |= bit; + + if (value) + plat->dat_out[bank] |= bit; + else + plat->dat_out[bank] &= ~bit; + + ret = adp5588_gpio_write(dev, GPIO_DAT_OUT1 + bank, + plat->dat_out[bank]); + ret |= adp5588_gpio_write(dev, GPIO_DIR1 + bank, + plat->dir[bank]); + + return ret; +} + +static int adp5588_ofdata_platdata(struct udevice *dev) +{ + struct adp5588_gpio *plat = dev_get_plat(dev); + struct gpio_dev_priv *priv = dev_get_uclass_priv(dev); + int node = dev_of_offset(dev); + int ret, i, revid; + + priv->gpio_count = ADP5588_MAXGPIO; + priv->bank_name = fdt_get_name(gd->fdt_blob, node, NULL); + + ret = adp5588_gpio_read(dev, DEV_ID); + if (ret < 0) + return ret; + + revid = ret & ID_MASK; + + printf("ADP5588 Detected: Rev %x, Rev ID %x\n", ret, revid); + + for (i = 0, ret = 0; i <= ADP5588_BANK(ADP5588_MAXGPIO); i++) { + plat->dat_out[i] = adp5588_gpio_read(dev, GPIO_DAT_OUT1 + i); + plat->dir[i] = adp5588_gpio_read(dev, GPIO_DIR1 + i); + ret |= adp5588_gpio_write(dev, KP_GPIO1 + i, 0); + ret |= adp5588_gpio_write(dev, GPIO_PULL1 + i, 0); + ret |= adp5588_gpio_write(dev, GPIO_INT_EN1 + i, 0); + if (ret) { + pr_err("%s: Initialization error\n", __func__); + return ret; + } + } + + return 0; +} + +static const struct dm_gpio_ops adp5588_ops = { + .direction_input = adp5588_direction_input, + .direction_output = adp5588_direction_output, + .get_value = adp5588_get_value, + .set_value = adp5588_set_value, +}; + +static const struct udevice_id adp5588_of_match_list[] = { + { .compatible = "adi,adp5588"}, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(gpio_adp5588) = { + .name = "gpio_adp5588", + .id = UCLASS_GPIO, + .ops = &adp5588_ops, + .of_match = adp5588_of_match_list, + .of_to_plat = adp5588_ofdata_platdata, + .plat_auto = sizeof(struct adp5588_gpio), + .flags = DM_FLAG_PRE_RELOC, +}; From 2f6a86a6125645ef818dd0d4c0bfc1f77c73e31f Mon Sep 17 00:00:00 2001 From: Nathan Barrett-Morrison Date: Wed, 26 Feb 2025 12:30:27 -0500 Subject: [PATCH 05/12] usb: musb-new: Add support for Analog Devices SC5xx SoCs This adds support for the MUSB-based USB controller found in the Analog Devices SC57x and SC58x SoCs. Co-developed-by: Greg Malysa Signed-off-by: Greg Malysa Co-developed-by: Ian Roberts Signed-off-by: Ian Roberts Signed-off-by: Vasileios Bimpikas Signed-off-by: Utsav Agarwal Signed-off-by: Arturs Artamonovs Signed-off-by: Oliver Gaskell Signed-off-by: Nathan Barrett-Morrison --- MAINTAINERS | 1 + drivers/usb/musb-new/Kconfig | 7 ++ drivers/usb/musb-new/Makefile | 1 + drivers/usb/musb-new/sc5xx.c | 202 ++++++++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+) create mode 100644 drivers/usb/musb-new/sc5xx.c diff --git a/MAINTAINERS b/MAINTAINERS index bfb81bb9891..b57e2119679 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -637,6 +637,7 @@ F: drivers/gpio/gpio-adi-adsp.c F: drivers/pinctrl/pinctrl-adi-adsp.c F: drivers/serial/serial_adi_uart4.c F: drivers/timer/adi_sc5xx_timer.c +F: drivers/usb/musb-new/sc5xx.c F: include/configs/sc5* F: include/dt-bindings/pinctrl/adi-adsp.h F: include/env/adi/ diff --git a/drivers/usb/musb-new/Kconfig b/drivers/usb/musb-new/Kconfig index c52afd41a75..ad9072a5327 100644 --- a/drivers/usb/musb-new/Kconfig +++ b/drivers/usb/musb-new/Kconfig @@ -22,6 +22,13 @@ config USB_MUSB_GADGET Enables the MUSB USB dual-role controller in gadget mode. if USB_MUSB_HOST || USB_MUSB_GADGET +config USB_MUSB_SC5XX + bool "Analog Devices MUSB support" + depends on (SC57X || SC58X) + help + Say y here to enable support for the USB controller on + ADI SC57X/SC58X processors. + config USB_MUSB_DA8XX bool "Enable DA8xx MUSB Controller" depends on ARCH_DAVINCI diff --git a/drivers/usb/musb-new/Makefile b/drivers/usb/musb-new/Makefile index 396ff02654b..6638772daca 100644 --- a/drivers/usb/musb-new/Makefile +++ b/drivers/usb/musb-new/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_USB_MUSB_PIC32) += pic32.o obj-$(CONFIG_USB_MUSB_SUNXI) += sunxi.o obj-$(CONFIG_USB_MUSB_TI) += ti-musb.o obj-$(CONFIG_USB_MUSB_UX500) += ux500.o +obj-$(CONFIG_USB_MUSB_SC5XX) += sc5xx.o ccflags-y := $(call cc-option,-Wno-unused-variable) \ $(call cc-option,-Wno-unused-but-set-variable) \ diff --git a/drivers/usb/musb-new/sc5xx.c b/drivers/usb/musb-new/sc5xx.c new file mode 100644 index 00000000000..16201480b43 --- /dev/null +++ b/drivers/usb/musb-new/sc5xx.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright 2022 - Analog Devices, Inc. + * + * ADI SC5XX MUSB "glue layer" + * + * Written and/or maintained by Timesys Corporation + * + * Loosely ported from Linux driver: + * Author: Nathan Barrett-Morrison + * + */ + +#include +#include +#include +#include +#include +#include "linux-compat.h" +#include "musb_core.h" +#include "musb_uboot.h" + +#define MUSB_SOFTRST 0x7f +#define MUSB_SOFTRST_NRST BIT(0) +#define MUSB_SOFTRST_NRSTX BIT(1) + +#define REG_USB_VBUS_CTL 0x380 +#define REG_USB_ID_CTL 0x382 +#define REG_USB_PHY_CTL 0x394 +#define REG_USB_PLL_OSC 0x398 +#define REG_USB_UTMI_CTL 0x39c + +/* controller data */ +struct sc5xx_musb_data { + struct musb_host_data mdata; + struct device dev; +}; + +#define to_sc5xx_musb_data(d) \ + container_of(d, struct sc5xx_musb_data, dev) + +static void sc5xx_musb_disable(struct musb *musb) +{ + /* no way to shut the controller */ +} + +static int sc5xx_musb_enable(struct musb *musb) +{ + /* soft reset by NRSTx */ + musb_writeb(musb->mregs, MUSB_SOFTRST, MUSB_SOFTRST_NRSTX); + /* set mode */ + musb_platform_set_mode(musb, musb->board_mode); + + return 0; +} + +static irqreturn_t sc5xx_interrupt(int irq, void *hci) +{ + struct musb *musb = hci; + irqreturn_t ret = IRQ_NONE; + u8 devctl; + + musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); + musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX); + musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX); + + if (musb->int_usb & MUSB_INTR_VBUSERROR) { + musb->int_usb &= ~MUSB_INTR_VBUSERROR; + devctl = musb_readw(musb->mregs, MUSB_DEVCTL); + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + } + if (musb->int_usb || musb->int_tx || musb->int_rx) { + musb_writeb(musb->mregs, MUSB_INTRUSB, musb->int_usb); + musb_writew(musb->mregs, MUSB_INTRTX, musb->int_tx); + musb_writew(musb->mregs, MUSB_INTRRX, musb->int_rx); + ret = musb_interrupt(musb); + } + + if (musb->int_usb & MUSB_INTR_DISCONNECT && is_host_active(musb)) + musb_writeb(musb->mregs, REG_USB_VBUS_CTL, 0x0); + + return ret; +} + +static int sc5xx_musb_set_mode(struct musb *musb, u8 mode) +{ + struct device *dev = musb->controller; + struct sc5xx_musb_data *pdata = to_sc5xx_musb_data(dev); + + switch (mode) { + case MUSB_HOST: + musb_writeb(musb->mregs, REG_USB_ID_CTL, 0x1); + break; + case MUSB_PERIPHERAL: + musb_writeb(musb->mregs, REG_USB_ID_CTL, 0x3); + break; + case MUSB_OTG: + musb_writeb(musb->mregs, REG_USB_ID_CTL, 0x0); + break; + default: + dev_err(dev, "unsupported mode %d\n", mode); + return -EINVAL; + } + + return 0; +} + +static int sc5xx_musb_init(struct musb *musb) +{ + struct sc5xx_musb_data *pdata = to_sc5xx_musb_data(musb->controller); + + musb->isr = sc5xx_interrupt; + + musb_writel(musb->mregs, REG_USB_PLL_OSC, 20 << 1); + musb_writeb(musb->mregs, REG_USB_VBUS_CTL, 0x0); + musb_writeb(musb->mregs, REG_USB_PHY_CTL, 0x80); + musb_writel(musb->mregs, REG_USB_UTMI_CTL, + 0x40 | musb_readl(musb->mregs, REG_USB_UTMI_CTL)); + + return 0; +} + +const struct musb_platform_ops sc5xx_musb_ops = { + .init = sc5xx_musb_init, + .set_mode = sc5xx_musb_set_mode, + .disable = sc5xx_musb_disable, + .enable = sc5xx_musb_enable, +}; + +static struct musb_hdrc_config sc5xx_musb_config = { + .multipoint = 1, + .dyn_fifo = 1, + .num_eps = 16, + .ram_bits = 12, +}; + +/* has one MUSB controller which can be host or gadget */ +static struct musb_hdrc_platform_data sc5xx_musb_plat = { + .mode = MUSB_HOST, + .config = &sc5xx_musb_config, + .power = 100, + .platform_ops = &sc5xx_musb_ops, +}; + +static int musb_usb_probe(struct udevice *dev) +{ + struct usb_bus_priv *priv = dev_get_uclass_priv(dev); + struct sc5xx_musb_data *pdata = dev_get_priv(dev); + struct musb_host_data *mdata = &pdata->mdata; + void __iomem *mregs; + int ret; + + priv->desc_before_addr = true; + + mregs = dev_remap_addr(dev); + if (!mregs) + return -EINVAL; + + /* init controller */ + if (IS_ENABLED(CONFIG_USB_MUSB_HOST)) { + mdata->host = musb_init_controller(&sc5xx_musb_plat, + &pdata->dev, mregs); + if (!mdata->host) + return -EIO; + + ret = musb_lowlevel_init(mdata); + } else { + sc5xx_musb_plat.mode = MUSB_PERIPHERAL; + mdata->host = musb_register(&sc5xx_musb_plat, &pdata->dev, mregs); + if (!mdata->host) + return -EIO; + } + return ret; +} + +static int musb_usb_remove(struct udevice *dev) +{ + struct sc5xx_musb_data *pdata = dev_get_priv(dev); + + musb_stop(pdata->mdata.host); + + return 0; +} + +static const struct udevice_id sc5xx_musb_ids[] = { + { .compatible = "adi,sc5xx-musb" }, + { } +}; + +U_BOOT_DRIVER(usb_musb) = { + .name = "sc5xx-musb", + .id = UCLASS_USB, + .of_match = sc5xx_musb_ids, + .probe = musb_usb_probe, + .remove = musb_usb_remove, +#ifdef CONFIG_USB_MUSB_HOST + .ops = &musb_usb_ops, +#endif + .plat_auto = sizeof(struct usb_plat), + .priv_auto = sizeof(struct sc5xx_musb_data), +}; From 7f99650bb8f592827fc687022f4238ffdd4fde3e Mon Sep 17 00:00:00 2001 From: Nathan Barrett-Morrison Date: Wed, 26 Feb 2025 12:30:28 -0500 Subject: [PATCH 06/12] i2c: Add support for ADI SC5XX-family I2C peripheral Co-developed-by: Greg Malysa Signed-off-by: Greg Malysa Co-developed-by: Ian Roberts Signed-off-by: Ian Roberts Co-developed-by: Angelo Dureghello Signed-off-by: Angelo Dureghello Signed-off-by: Vasileios Bimpikas Signed-off-by: Utsav Agarwal Signed-off-by: Arturs Artamonovs Signed-off-by: Oliver Gaskell Signed-off-by: Nathan Barrett-Morrison Reviewed-by: Heiko Schocher --- MAINTAINERS | 1 + drivers/i2c/Kconfig | 7 + drivers/i2c/Makefile | 1 + drivers/i2c/adi_i2c.c | 386 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 395 insertions(+) create mode 100644 drivers/i2c/adi_i2c.c diff --git a/MAINTAINERS b/MAINTAINERS index b57e2119679..f2638becc32 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -634,6 +634,7 @@ F: doc/device-tree-bindings/timer/adi,sc5xx-gptimer.yaml F: drivers/clk/adi/ F: drivers/gpio/adp5588_gpio.c F: drivers/gpio/gpio-adi-adsp.c +F: drivers/i2c/adi_i2c.c F: drivers/pinctrl/pinctrl-adi-adsp.c F: drivers/serial/serial_adi_uart4.c F: drivers/timer/adi_sc5xx_timer.c diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index cdae6825736..46e76385961 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -154,6 +154,13 @@ config SPL_DM_I2C_GPIO bindings are supported. Binding info: doc/device-tree-bindings/i2c/i2c-gpio.txt +config SYS_I2C_ADI + bool "ADI I2C driver" + depends on DM_I2C && ARCH_SC5XX + help + Add support for the ADI (Analog Devices) I2C driver as used + in SC57X, SC58X, SC59X, SC59X_64. + config SYS_I2C_AT91 bool "Atmel I2C driver" depends on DM_I2C && ARCH_AT91 diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index bebd728e7da..2713289f7db 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_$(XPL_)I2C_CROS_EC_TUNNEL) += cros_ec_tunnel.o obj-$(CONFIG_$(XPL_)I2C_CROS_EC_LDO) += cros_ec_ldo.o obj-$(CONFIG_$(XPL_)SYS_I2C_LEGACY) += i2c_core.o +obj-$(CONFIG_SYS_I2C_ADI) += adi_i2c.o obj-$(CONFIG_SYS_I2C_ASPEED) += ast_i2c.o obj-$(CONFIG_SYS_I2C_AST2600) += ast2600_i2c.o obj-$(CONFIG_SYS_I2C_AT91) += at91_i2c.o diff --git a/drivers/i2c/adi_i2c.c b/drivers/i2c/adi_i2c.c new file mode 100644 index 00000000000..4cddcfa6b7f --- /dev/null +++ b/drivers/i2c/adi_i2c.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright 2022 - Analog Devices, Inc. + * + * Written and/or maintained by Timesys Corporation + * + * Converted to driver model by Nathan Barrett-Morrison + * + * Contact: Nathan Barrett-Morrison + * Contact: Greg Malysa + */ + +#include +#include +#include +#include +#include + +#define CLKLOW(x) ((x) & 0xFF) // Periods Clock Is Held Low +#define CLKHI(y) (((y) & 0xFF) << 0x8) // Periods Clock Is High + +#define PRESCALE 0x007F // SCLKs Per Internal Time Reference (10MHz) +#define TWI_ENA 0x0080 // TWI Enable +#define SCCB 0x0200 // SCCB Compatibility Enable + +#define SEN 0x0001 // Slave Enable +#define SADD_LEN 0x0002 // Slave Address Length +#define STDVAL 0x0004 // Slave Transmit Data Valid +#define TSC_NAK 0x0008 // NAK Generated At Conclusion Of Transfer +#define GEN 0x0010 // General Call Adrress Matching Enabled + +#define SDIR 0x0001 // Slave Transfer Direction +#define GCALL 0x0002 // General Call Indicator + +#define MEN 0x0001 // Master Mode Enable +#define MADD_LEN 0x0002 // Master Address Length +#define MDIR 0x0004 // Master Transmit Direction (RX/TX*) +#define FAST 0x0008 // Use Fast Mode Timing Specs +#define STOP 0x0010 // Issue Stop Condition +#define RSTART 0x0020 // Repeat Start or Stop* At End Of Transfer +#define DCNT 0x3FC0 // Data Bytes To Transfer +#define SDAOVR 0x4000 // Serial Data Override +#define SCLOVR 0x8000 // Serial Clock Override + +#define MPROG 0x0001 // Master Transfer In Progress +#define LOSTARB 0x0002 // Lost Arbitration Indicator (Xfer Aborted) +#define ANAK 0x0004 // Address Not Acknowledged +#define DNAK 0x0008 // Data Not Acknowledged +#define BUFRDERR 0x0010 // Buffer Read Error +#define BUFWRERR 0x0020 // Buffer Write Error +#define SDASEN 0x0040 // Serial Data Sense +#define SCLSEN 0x0080 // Serial Clock Sense +#define BUSBUSY 0x0100 // Bus Busy Indicator + +#define SINIT 0x0001 // Slave Transfer Initiated +#define SCOMP 0x0002 // Slave Transfer Complete +#define SERR 0x0004 // Slave Transfer Error +#define SOVF 0x0008 // Slave Overflow +#define MCOMP 0x0010 // Master Transfer Complete +#define MERR 0x0020 // Master Transfer Error +#define XMTSERV 0x0040 // Transmit FIFO Service +#define RCVSERV 0x0080 // Receive FIFO Service + +#define XMTFLUSH 0x0001 // Transmit Buffer Flush +#define RCVFLUSH 0x0002 // Receive Buffer Flush +#define XMTINTLEN 0x0004 // Transmit Buffer Interrupt Length +#define RCVINTLEN 0x0008 // Receive Buffer Interrupt Length + +#define XMTSTAT 0x0003 // Transmit FIFO Status +#define XMT_EMPTY 0x0000 // Transmit FIFO Empty +#define XMT_HALF 0x0001 // Transmit FIFO Has 1 Byte To Write +#define XMT_FULL 0x0003 // Transmit FIFO Full (2 Bytes To Write) + +#define RCVSTAT 0x000C // Receive FIFO Status +#define RCV_EMPTY 0x0000 // Receive FIFO Empty +#define RCV_HALF 0x0004 // Receive FIFO Has 1 Byte To Read +#define RCV_FULL 0x000C // Receive FIFO Full (2 Bytes To Read) + +/* Every register is 32bit aligned, but only 16bits in size */ +#define ureg(name) u16 name; u16 __pad_##name + +struct twi_regs { + ureg(clkdiv); + ureg(control); + ureg(slave_ctl); + ureg(slave_stat); + ureg(slave_addr); + ureg(master_ctl); + ureg(master_stat); + ureg(master_addr); + ureg(int_stat); + ureg(int_mask); + ureg(fifo_ctl); + ureg(fifo_stat); + u8 __pad[0x50]; + + ureg(xmt_data8); + ureg(xmt_data16); + ureg(rcv_data8); + ureg(rcv_data16); +}; + +#undef ureg + +/* + * The way speed is changed into duty often results in integer truncation + * with 50% duty, so we'll force rounding up to the next duty by adding 1 + * to the max. In practice this will get us a speed of something like + * 385 KHz. The other limit is easy to handle as it is only 8 bits. + */ +#define I2C_SPEED_MAX 400000 +#define I2C_SPEED_TO_DUTY(speed) (5000000 / (speed)) +#define I2C_DUTY_MAX (I2C_SPEED_TO_DUTY(I2C_SPEED_MAX) + 1) +#define I2C_DUTY_MIN 0xff /* 8 bit limited */ + +#define I2C_M_COMBO 0x4 +#define I2C_M_STOP 0x2 +#define I2C_M_READ 0x1 + +/* + * All transfers are described by this data structure + */ +struct adi_i2c_msg { + u8 flags; + u32 len; /* msg length */ + u8 *buf; /* pointer to msg data */ + u32 olen; /* addr length */ + u8 *obuf; /* addr buffer */ +}; + +struct adi_i2c_dev { + struct twi_regs __iomem *base; + u32 i2c_clk; + uint speed; +}; + +/* Allow msec timeout per ~byte transfer */ +#define I2C_TIMEOUT 10 + +/** + * wait_for_completion - manage the actual i2c transfer + * @msg: the i2c msg + */ +static int wait_for_completion(struct twi_regs *twi, struct adi_i2c_msg *msg) +{ + u16 int_stat; + ulong timebase = get_timer(0); + + do { + int_stat = ioread16(&twi->int_stat); + + if (int_stat & XMTSERV) { + iowrite16(XMTSERV, &twi->int_stat); + if (msg->olen) { + iowrite16(*(msg->obuf++), &twi->xmt_data8); + --msg->olen; + } else if (!(msg->flags & I2C_M_COMBO) && msg->len) { + iowrite16(*(msg->buf++), &twi->xmt_data8); + --msg->len; + } else { + if (msg->flags & I2C_M_COMBO) + setbits_16(&twi->master_ctl, RSTART | MDIR); + else + setbits_16(&twi->master_ctl, STOP); + } + } + if (int_stat & RCVSERV) { + iowrite16(RCVSERV, &twi->int_stat); + if (msg->len) { + *(msg->buf++) = ioread16(&twi->rcv_data8); + --msg->len; + } else if (msg->flags & I2C_M_STOP) { + setbits_16(&twi->master_ctl, STOP); + } + } + if (int_stat & MERR) { + pr_err("%s: master transmit terror: %d\n", __func__, + ioread16(&twi->master_stat)); + iowrite16(MERR, &twi->int_stat); + return -EIO; + } + if (int_stat & MCOMP) { + iowrite16(MCOMP, &twi->int_stat); + if (msg->flags & I2C_M_COMBO && msg->len) { + u16 mlen = min(msg->len, 0xffu) << 6; + clrsetbits_16(&twi->master_ctl, RSTART, mlen | MEN | MDIR); + } else { + break; + } + } + + /* If we were able to do something, reset timeout */ + if (int_stat) + timebase = get_timer(0); + + } while (get_timer(timebase) < I2C_TIMEOUT); + + return 0; +} + +static int i2c_transfer(struct twi_regs *twi, u8 chip, u8 *offset, + int olen, u8 *buffer, int len, u8 flags) +{ + int ret; + u16 ctl; + + struct adi_i2c_msg msg = { + .flags = flags | (len >= 0xff ? I2C_M_STOP : 0), + .buf = buffer, + .len = len, + .obuf = offset, + .olen = olen, + }; + + /* wait for things to settle */ + while (ioread16(&twi->master_stat) & BUSBUSY) + if (!IS_ENABLED(CONFIG_SPL_BUILD) && ctrlc()) + return -EINTR; + + /* Set Transmit device address */ + iowrite16(chip, &twi->master_addr); + + /* Clear the FIFO before starting things */ + iowrite16(XMTFLUSH | RCVFLUSH, &twi->fifo_ctl); + iowrite16(0, &twi->fifo_ctl); + + /* Prime the pump */ + if (msg.olen) { + len = (msg.flags & I2C_M_COMBO) ? msg.olen : msg.olen + len; + iowrite16(*(msg.obuf++), &twi->xmt_data8); + --msg.olen; + } else if (!(msg.flags & I2C_M_READ) && msg.len) { + iowrite16(*(msg.buf++), &twi->xmt_data8); + --msg.len; + } + + /* clear int stat */ + iowrite16(-1, &twi->master_stat); + iowrite16(-1, &twi->int_stat); + iowrite16(0, &twi->int_mask); + + /* Master enable */ + ctl = ioread16(&twi->master_ctl); + ctl = (ctl & FAST) | (min(len, 0xff) << 6) | MEN | + ((msg.flags & I2C_M_READ) ? MDIR : 0); + iowrite16(ctl, &twi->master_ctl); + + /* Process the rest */ + ret = wait_for_completion(twi, &msg); + + clrbits_16(&twi->master_ctl, MEN); + clrbits_16(&twi->control, TWI_ENA); + setbits_16(&twi->control, TWI_ENA); + return ret; +} + +static int adi_i2c_read(struct twi_regs *twi, u8 chip, + u8 *offset, int olen, u8 *buffer, int len) +{ + return i2c_transfer(twi, chip, offset, olen, buffer, + len, olen ? I2C_M_COMBO : I2C_M_READ); +} + +static int adi_i2c_write(struct twi_regs *twi, u8 chip, + u8 *offset, int olen, u8 *buffer, int len) +{ + return i2c_transfer(twi, chip, offset, olen, buffer, len, 0); +} + +static int adi_i2c_set_bus_speed(struct udevice *bus, uint speed) +{ + struct adi_i2c_dev *dev = dev_get_priv(bus); + struct twi_regs *twi = dev->base; + u16 clkdiv = I2C_SPEED_TO_DUTY(speed); + + /* Set TWI interface clock */ + if (clkdiv < I2C_DUTY_MAX || clkdiv > I2C_DUTY_MIN) + return -1; + clkdiv = (clkdiv << 8) | (clkdiv & 0xff); + iowrite16(clkdiv, &twi->clkdiv); + + /* Don't turn it on */ + iowrite16(speed > 100000 ? FAST : 0, &twi->master_ctl); + + return 0; +} + +static int adi_i2c_of_to_plat(struct udevice *bus) +{ + struct adi_i2c_dev *dev = dev_get_priv(bus); + struct clk clock; + u32 ret; + + dev->base = map_sysmem(dev_read_addr(bus), sizeof(struct twi_regs)); + + if (!dev->base) + return -ENOMEM; + + dev->speed = dev_read_u32_default(bus, "clock-frequency", + I2C_SPEED_FAST_RATE); + + ret = clk_get_by_name(bus, "i2c", &clock); + if (ret < 0) + printf("%s: Can't get I2C clk: %d\n", __func__, ret); + else + dev->i2c_clk = clk_get_rate(&clock); + + return 0; +} + +static int adi_i2c_probe_chip(struct udevice *bus, u32 chip_addr, + u32 chip_flags) +{ + struct adi_i2c_dev *dev = dev_get_priv(bus); + u8 byte; + + return adi_i2c_read(dev->base, chip_addr, NULL, 0, &byte, 1); +} + +static int adi_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, int nmsgs) +{ + struct adi_i2c_dev *dev = dev_get_priv(bus); + struct i2c_msg *dmsg, *omsg, dummy; + + memset(&dummy, 0, sizeof(struct i2c_msg)); + + /* + * We expect either two messages (one with an offset and one with the + * actual data) or one message (just data) + */ + if (nmsgs > 2 || nmsgs == 0) { + debug("%s: Only one or two messages are supported.", __func__); + return -EINVAL; + } + + omsg = nmsgs == 1 ? &dummy : msg; + dmsg = nmsgs == 1 ? msg : msg + 1; + + if (dmsg->flags & I2C_M_RD) + return adi_i2c_read(dev->base, dmsg->addr, omsg->buf, omsg->len, + dmsg->buf, dmsg->len); + else + return adi_i2c_write(dev->base, dmsg->addr, omsg->buf, omsg->len, + dmsg->buf, dmsg->len); +} + +int adi_i2c_probe(struct udevice *bus) +{ + struct adi_i2c_dev *dev = dev_get_priv(bus); + struct twi_regs *twi = dev->base; + + u16 prescale = ((dev->i2c_clk / 1000 / 1000 + 5) / 10) & 0x7F; + + /* Set TWI internal clock as 10MHz */ + iowrite16(prescale, &twi->control); + + /* Set TWI interface clock as specified */ + adi_i2c_set_bus_speed(bus, dev->speed); + + /* Enable it */ + iowrite16(TWI_ENA | prescale, &twi->control); + + return 0; +} + +static const struct dm_i2c_ops adi_i2c_ops = { + .xfer = adi_i2c_xfer, + .probe_chip = adi_i2c_probe_chip, + .set_bus_speed = adi_i2c_set_bus_speed, +}; + +static const struct udevice_id adi_i2c_ids[] = { + { .compatible = "adi-i2c", }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(i2c_adi) = { + .name = "i2c_adi", + .id = UCLASS_I2C, + .of_match = adi_i2c_ids, + .probe = adi_i2c_probe, + .of_to_plat = adi_i2c_of_to_plat, + .priv_auto = sizeof(struct adi_i2c_dev), + .ops = &adi_i2c_ops, + .flags = DM_FLAG_PRE_RELOC, +}; From df831ebf6114c5f534f774c8b5b6998d328d0101 Mon Sep 17 00:00:00 2001 From: Greg Malysa Date: Wed, 26 Feb 2025 12:30:29 -0500 Subject: [PATCH 07/12] net: Add support for ADI SC5xx SoCs with DWC QoS ethernet The ADI SC598 includes a Designware QoS 5.20a IP block. This commit adds support for using the existing ethernet QoS driver with the SC598 SoC. Co-developed-by: Ian Roberts Signed-off-by: Ian Roberts Co-developed-by: Nathan Barrett-Morrison Signed-off-by: Nathan Barrett-Morrison Signed-off-by: Vasileios Bimpikas Signed-off-by: Utsav Agarwal Signed-off-by: Arturs Artamonovs Signed-off-by: Oliver Gaskell Signed-off-by: Greg Malysa --- MAINTAINERS | 1 + drivers/net/Kconfig | 7 +++ drivers/net/Makefile | 1 + drivers/net/dwc_eth_qos.c | 6 ++ drivers/net/dwc_eth_qos.h | 2 + drivers/net/dwc_eth_qos_adi.c | 103 ++++++++++++++++++++++++++++++++++ 6 files changed, 120 insertions(+) create mode 100644 drivers/net/dwc_eth_qos_adi.c diff --git a/MAINTAINERS b/MAINTAINERS index f2638becc32..2c4dc8b14c1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -635,6 +635,7 @@ F: drivers/clk/adi/ F: drivers/gpio/adp5588_gpio.c F: drivers/gpio/gpio-adi-adsp.c F: drivers/i2c/adi_i2c.c +F: drivers/net/dwc_eth_qos_adi.c F: drivers/pinctrl/pinctrl-adi-adsp.c F: drivers/serial/serial_adi_uart4.c F: drivers/timer/adi_sc5xx_timer.c diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 1563404ca17..8dc5f4810e9 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -237,6 +237,13 @@ config DWC_ETH_QOS Of Service) IP block. The IP supports many options for bus type, clocking/reset structure, and feature list. +config DWC_ETH_QOS_ADI + bool "Synopsys DWC Ethernet QOS device support for ADI SC59x-64 parts" + depends on DWC_ETH_QOS + help + The Synopsis Designware Ethernet QoS IP block with the specific + configuration used in the ADI ADSP-SC59X 64 bit SoCs + config DWC_ETH_QOS_IMX bool "Synopsys DWC Ethernet QOS device support for IMX" depends on DWC_ETH_QOS diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 80d70212971..d919d437c08 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_DM_ETH_PHY) += eth-phy-uclass.o obj-$(CONFIG_DRIVER_DM9000) += dm9000x.o obj-$(CONFIG_DSA_SANDBOX) += dsa_sandbox.o obj-$(CONFIG_DWC_ETH_QOS) += dwc_eth_qos.o +obj-$(CONFIG_DWC_ETH_QOS_ADI) += dwc_eth_qos_adi.o obj-$(CONFIG_DWC_ETH_QOS_IMX) += dwc_eth_qos_imx.o obj-$(CONFIG_DWC_ETH_QOS_INTEL) += dwc_eth_qos_intel.o obj-$(CONFIG_DWC_ETH_QOS_ROCKCHIP) += dwc_eth_qos_rockchip.o diff --git a/drivers/net/dwc_eth_qos.c b/drivers/net/dwc_eth_qos.c index 2279481d935..b4ec3614696 100644 --- a/drivers/net/dwc_eth_qos.c +++ b/drivers/net/dwc_eth_qos.c @@ -1631,6 +1631,12 @@ static const struct udevice_id eqos_ids[] = { .compatible = "starfive,jh7110-dwmac", .data = (ulong)&eqos_jh7110_config }, +#endif +#if IS_ENABLED(CONFIG_DWC_ETH_QOS_ADI) + { + .compatible = "adi,sc59x-dwmac-eqos", + .data = (ulong)&eqos_adi_config + }, #endif { } }; diff --git a/drivers/net/dwc_eth_qos.h b/drivers/net/dwc_eth_qos.h index 123f98d5d53..403e8203974 100644 --- a/drivers/net/dwc_eth_qos.h +++ b/drivers/net/dwc_eth_qos.h @@ -87,6 +87,7 @@ struct eqos_mac_regs { #define EQOS_MAC_MDIO_ADDRESS_CR_MASK GENMASK(11, 8) #define EQOS_MAC_MDIO_ADDRESS_CR_100_150 1 #define EQOS_MAC_MDIO_ADDRESS_CR_20_35 2 +#define EQOS_MAC_MDIO_ADDRESS_CR_150_250 4 #define EQOS_MAC_MDIO_ADDRESS_CR_250_300 5 #define EQOS_MAC_MDIO_ADDRESS_SKAP BIT(4) #define EQOS_MAC_MDIO_ADDRESS_GOC_MASK GENMASK(3, 2) @@ -301,3 +302,4 @@ extern struct eqos_config eqos_qcom_config; extern struct eqos_config eqos_stm32mp13_config; extern struct eqos_config eqos_stm32mp15_config; extern struct eqos_config eqos_jh7110_config; +extern struct eqos_config eqos_adi_config; diff --git a/drivers/net/dwc_eth_qos_adi.c b/drivers/net/dwc_eth_qos_adi.c new file mode 100644 index 00000000000..0e6a901e303 --- /dev/null +++ b/drivers/net/dwc_eth_qos_adi.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * (C) Copyright 2024 - Analog Devices, Inc. + * + * Written and/or maintained by Timesys Corporation + * + * Author: Greg Malysa + * Additional Contact: Nathan Barrett-Morrison + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "dwc_eth_qos.h" + +static int eqos_start_resets_adi(struct udevice *dev) +{ + struct eqos_priv *eqos = dev_get_priv(dev); + + /* + * Settings need to latch with the DMA reset below. Currently only + * rgmii is supported but other phy interfaces may be supported in + * the future + */ + sc5xx_enable_rgmii(); + setbits_32(&eqos->dma_regs->mode, EQOS_DMA_MODE_SWR); + + return 0; +} + +static int eqos_probe_resources_adi(struct udevice *dev) +{ + struct eqos_priv *eqos = dev_get_priv(dev); + phy_interface_t interface; + int ret; + + ret = eqos_get_base_addr_dt(dev); + if (ret) { + pr_err("eqos_get_base_addr_dt failed: %d\n", ret); + return ret; + } + + interface = eqos->config->interface(dev); + if (interface == PHY_INTERFACE_MODE_NA) { + pr_err("Invalid PHY interface\n"); + return -EINVAL; + } + + return 0; +} + +/** + * rgmii tx clock rate is set to 125 MHz regardless of phy mode, and + * by default the internal clock is always connected to 125 MHz. According + * to the HRM it is invalid for this clock to have any other speed, so + * the hardware won't work anyway if this is wrong. + */ +static ulong eqos_get_tick_clk_rate_adi(struct udevice *dev) +{ + return 125 * 1000000; +} + +static int eqos_get_enetaddr_adi(struct udevice *dev) +{ + struct eth_pdata *pdata = dev_get_plat(dev); + + return eth_env_get_enetaddr("ethaddr", pdata->enetaddr); +} + +static struct eqos_ops eqos_adi_ops = { + .eqos_inval_desc = eqos_inval_desc_generic, + .eqos_flush_desc = eqos_flush_desc_generic, + .eqos_inval_buffer = eqos_inval_buffer_generic, + .eqos_flush_buffer = eqos_flush_buffer_generic, + .eqos_probe_resources = eqos_probe_resources_adi, + .eqos_remove_resources = eqos_null_ops, + .eqos_start_resets = eqos_start_resets_adi, + .eqos_stop_resets = eqos_null_ops, + .eqos_start_clks = eqos_null_ops, + .eqos_stop_clks = eqos_null_ops, + .eqos_calibrate_pads = eqos_null_ops, + .eqos_disable_calibration = eqos_null_ops, + .eqos_set_tx_clk_speed = eqos_null_ops, + .eqos_get_enetaddr = eqos_get_enetaddr_adi, + .eqos_get_tick_clk_rate = eqos_get_tick_clk_rate_adi, +}; + +struct eqos_config __maybe_unused eqos_adi_config = { + .reg_access_always_ok = true, + .mdio_wait = 20, + .swr_wait = 50, + .config_mac = EQOS_MAC_RXQ_CTRL0_RXQ0EN_ENABLED_DCB, + .config_mac_mdio = EQOS_MAC_MDIO_ADDRESS_CR_150_250, + .axi_bus_width = EQOS_AXI_WIDTH_32, + .interface = dev_read_phy_mode, + .ops = &eqos_adi_ops, +}; From cbc0dfd424caabaa9f2a1fee911940c7f65666e2 Mon Sep 17 00:00:00 2001 From: Nathan Barrett-Morrison Date: Wed, 26 Feb 2025 12:30:30 -0500 Subject: [PATCH 08/12] watchdog: Add support for ADI SC5XX-family watchdog peripheral Co-developed-by: Greg Malysa Signed-off-by: Greg Malysa Co-developed-by: Ian Roberts Signed-off-by: Ian Roberts Signed-off-by: Vasileios Bimpikas Signed-off-by: Utsav Agarwal Signed-off-by: Arturs Artamonovs Signed-off-by: Oliver Gaskell Signed-off-by: Nathan Barrett-Morrison Reviewed-by: Stefan Roese --- MAINTAINERS | 1 + drivers/watchdog/Kconfig | 9 +++ drivers/watchdog/Makefile | 1 + drivers/watchdog/adi_wdt.c | 143 +++++++++++++++++++++++++++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 drivers/watchdog/adi_wdt.c diff --git a/MAINTAINERS b/MAINTAINERS index 2c4dc8b14c1..0cefd888885 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -640,6 +640,7 @@ F: drivers/pinctrl/pinctrl-adi-adsp.c F: drivers/serial/serial_adi_uart4.c F: drivers/timer/adi_sc5xx_timer.c F: drivers/usb/musb-new/sc5xx.c +F: drivers/watchdog/adi_wdt.c F: include/configs/sc5* F: include/dt-bindings/pinctrl/adi-adsp.h F: include/env/adi/ diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index b39b2546e5c..1bb67f50352 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -95,6 +95,15 @@ config WDT_APPLE The watchdog will perform a full SoC reset resulting in a reboot of the entire system. +config WDT_ADI + bool "Analog Devices watchdog timer support" + select WDT + select SPL_WDT if SPL + depends on ARCH_SC5XX + help + Enable this to support Watchdog Timer on ADI SC57X, SC58X, SC59X, + and SC59X_64 processors + config WDT_ARMADA_37XX bool "Marvell Armada 37xx watchdog timer support" depends on WDT && ARMADA_3700 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 9b6b1a8e8ad..e6bd4c587af 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -53,3 +53,4 @@ obj-$(CONFIG_WDT_STM32MP) += stm32mp_wdt.o obj-$(CONFIG_WDT_SUNXI) += sunxi_wdt.o obj-$(CONFIG_WDT_TANGIER) += tangier_wdt.o obj-$(CONFIG_WDT_XILINX) += xilinx_wwdt.o +obj-$(CONFIG_WDT_ADI) += adi_wdt.o diff --git a/drivers/watchdog/adi_wdt.c b/drivers/watchdog/adi_wdt.c new file mode 100644 index 00000000000..6f5b3d5d042 --- /dev/null +++ b/drivers/watchdog/adi_wdt.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright 2022 - Analog Devices, Inc. + * + * Written and/or maintained by Timesys Corporation + * + * Converted to driver model by Nathan Barrett-Morrison + * + * Contact: Nathan Barrett-Morrison + * Contact: Greg Malysa + * + * adi_wtd.c - driver for ADI on-chip watchdog + * + */ + +#include +#include +#include +#include +#include +#include + +#define WDOG_CTL 0x0 +#define WDOG_CNT 0x4 +#define WDOG_STAT 0x8 + +#define RCU_CTL 0x0 +#define RCU_STAT 0x4 + +#define SEC_GCTL 0x0 +#define SEC_FCTL 0x10 +#define SEC_SCTL0 0x800 + +#define WDEN 0x0010 +#define WDDIS 0x0AD0 + +struct adi_wdt_priv { + void __iomem *rcu_base; + void __iomem *sec_base; + void __iomem *wdt_base; + struct clk clock; +}; + +static int adi_wdt_reset(struct udevice *dev) +{ + struct adi_wdt_priv *priv = dev_get_priv(dev); + + iowrite32(0, priv->wdt_base + WDOG_STAT); + + return 0; +} + +static int adi_wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags) +{ + struct adi_wdt_priv *priv = dev_get_priv(dev); + + /* Disable SYSCD_RESETb input and clear the RCU0 reset status */ + iowrite32(0xf, priv->rcu_base + RCU_STAT); + iowrite32(0x0, priv->rcu_base + RCU_CTL); + + /* reset the SEC controller */ + iowrite32(0x2, priv->sec_base + SEC_GCTL); + iowrite32(0x2, priv->sec_base + SEC_FCTL); + + udelay(50); + + /* enable SEC fault event */ + iowrite32(0x1, priv->sec_base + SEC_GCTL); + + /* ANOMALY 36100004 Spurious External Fault event occurs when FCTL + * is re-programmed when currently active fault is not cleared + */ + iowrite32(0xc0, priv->sec_base + SEC_FCTL); + iowrite32(0xc1, priv->sec_base + SEC_FCTL); + + /* enable SEC fault source for watchdog0 */ + setbits_32(priv->sec_base + SEC_SCTL0 + (3*8), 0x6); + + /* Enable SYSCD_RESETb input */ + iowrite32(0x100, priv->rcu_base + RCU_CTL); + + /* enable watchdog0 */ + iowrite32(WDDIS, priv->wdt_base + WDOG_CTL); + + iowrite32(timeout_ms / 1000 * + (clk_get_rate(&priv->clock) / (IS_ENABLED(CONFIG_SC58X) ? 2 : 1)), + priv->wdt_base + WDOG_CNT); + + iowrite32(0, priv->wdt_base + WDOG_STAT); + iowrite32(WDEN, priv->wdt_base + WDOG_CTL); + + return 0; +} + +static int adi_wdt_probe(struct udevice *dev) +{ + struct adi_wdt_priv *priv = dev_get_priv(dev); + int ret; + struct resource res; + + ret = dev_read_resource_byname(dev, "rcu", &res); + if (ret) + return ret; + priv->rcu_base = devm_ioremap(dev, res.start, resource_size(&res)); + + ret = dev_read_resource_byname(dev, "sec", &res); + if (ret) + return ret; + priv->sec_base = devm_ioremap(dev, res.start, resource_size(&res)); + + ret = dev_read_resource_byname(dev, "wdt", &res); + if (ret) + return ret; + priv->wdt_base = devm_ioremap(dev, res.start, resource_size(&res)); + + ret = clk_get_by_name(dev, "sclk0", &priv->clock); + if (ret < 0) { + printf("Can't get WDT clk: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct wdt_ops adi_wdt_ops = { + .start = adi_wdt_start, + .reset = adi_wdt_reset, +}; + +static const struct udevice_id adi_wdt_ids[] = { + { .compatible = "adi,wdt" }, + {} +}; + +U_BOOT_DRIVER(adi_wdt) = { + .name = "adi_wdt", + .id = UCLASS_WDT, + .of_match = adi_wdt_ids, + .probe = adi_wdt_probe, + .ops = &adi_wdt_ops, + .priv_auto = sizeof(struct adi_wdt_priv), + .flags = DM_FLAG_PRE_RELOC, +}; From 072320d9215641dd30c5697a6b479ad46f05f2f9 Mon Sep 17 00:00:00 2001 From: Greg Malysa Date: Wed, 26 Feb 2025 12:30:31 -0500 Subject: [PATCH 09/12] dma: Add driver for ADI SC5xx-family SoC MDMA functionality Add a rudimentary MDMA driver for the Analog Devices SC5xx SoCs, primarily intended for use with and tested against the QSPI/OSPI IP included in the SoC. Co-developed-by: Ian Roberts Signed-off-by: Ian Roberts Co-developed-by: Nathan Barrett-Morrison Signed-off-by: Nathan Barrett-Morrison Signed-off-by: Vasileios Bimpikas Signed-off-by: Utsav Agarwal Signed-off-by: Arturs Artamonovs Signed-off-by: Greg Malysa Signed-off-by: Oliver Gaskell --- MAINTAINERS | 1 + drivers/dma/Kconfig | 7 ++ drivers/dma/Makefile | 1 + drivers/dma/adi_dma.c | 253 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 262 insertions(+) create mode 100644 drivers/dma/adi_dma.c diff --git a/MAINTAINERS b/MAINTAINERS index 0cefd888885..a9d05356763 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -632,6 +632,7 @@ F: doc/device-tree-bindings/clock/adi,sc5xx-clocks.yaml F: doc/device-tree-bindings/pinctrl/adi,adsp-pinctrl.yaml F: doc/device-tree-bindings/timer/adi,sc5xx-gptimer.yaml F: drivers/clk/adi/ +F: drivers/dma/adi_dma.c F: drivers/gpio/adp5588_gpio.c F: drivers/gpio/gpio-adi-adsp.c F: drivers/i2c/adi_i2c.c diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 3c64e894646..4b47be6b016 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -76,6 +76,13 @@ config XILINX_DPDMA this file is used as placeholder for driver. The main reason is to record compatible string and calling power domain driver. +config ADI_DMA + bool "ADI DMA driver" + depends on DMA && DMA_CHANNELS + help + Enable DMA support for Analog Devices SOCs, such as the SC5xx. + Currently this is a minimalistic driver tested against OSPI use only. + if APBH_DMA config APBH_DMA_BURST bool "Enable DMA BURST" diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 48811eaaeb3..00d765864cd 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -13,5 +13,6 @@ obj-$(CONFIG_TI_KSNAV) += keystone_nav.o keystone_nav_cfg.o obj-$(CONFIG_TI_EDMA3) += ti-edma3.o obj-$(CONFIG_DMA_LPC32XX) += lpc32xx_dma.o obj-$(CONFIG_XILINX_DPDMA) += xilinx_dpdma.o +obj-$(CONFIG_ADI_DMA) += adi_dma.o obj-y += ti/ diff --git a/drivers/dma/adi_dma.c b/drivers/dma/adi_dma.c new file mode 100644 index 00000000000..28afe488db0 --- /dev/null +++ b/drivers/dma/adi_dma.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Analog Devices DMA controller driver + * + * (C) Copyright 2024 - Analog Devices, Inc. + * + * Written and/or maintained by Timesys Corporation + * + * Contact: Nathan Barrett-Morrison + * Contact: Greg Malysa + * Contact: Ian Roberts + * + */ +#include +#include +#include +#include +#include +#include + +#define HAS_MDMA BIT(0) + +#define REG_ADDRSTART 0x04 +#define REG_CFG 0x08 +#define REG_XCNT 0x0C +#define REG_XMOD 0x10 +#define REG_STAT 0x30 + +#define BITP_DMA_CFG_MSIZE 8 +#define BITP_DMA_CFG_PSIZE 4 +#define BITM_DMA_CFG_WNR 0x00000002 +#define BITM_DMA_CFG_EN 0x00000001 +#define ENUM_DMA_CFG_XCNT_INT 0x00100000 + +#define BITP_DMA_STAT_PBWID 12 +#define BITP_DMA_STAT_ERRC 4 +#define BITM_DMA_STAT_PBWID 0x00003000 +#define BITM_DMA_STAT_ERRC 0x00000070 +#define BITM_DMA_STAT_PIRQ 0x00000004 +#define BITM_DMA_STAT_IRQERR 0x00000002 +#define BITM_DMA_STAT_IRQDONE 0x00000001 + +#define DMA_MDMA_SRC_DEFAULT_CONFIG(psize, msize) \ + (BITM_DMA_CFG_EN | ((psize) << BITP_DMA_CFG_PSIZE) | ((msize) << BITP_DMA_CFG_MSIZE)) +#define DMA_MDMA_DST_DEFAULT_CONFIG(psize, msize) \ + (BITM_DMA_CFG_EN | BITM_DMA_CFG_WNR | ENUM_DMA_CFG_XCNT_INT | \ + ((psize) << BITP_DMA_CFG_PSIZE) | ((msize) << BITP_DMA_CFG_MSIZE)) + +struct adi_dma_channel { + int id; + struct adi_dma *dma; + void __iomem *iosrc; + void __iomem *iodest; +}; + +struct adi_dma { + struct udevice *dev; + struct adi_dma_channel channels[1]; + void __iomem *ioaddr; + unsigned long hw_cfg; +}; + +static const struct udevice_id dma_dt_ids[] = { + { .compatible = "adi,mdma-controller", .data = HAS_MDMA }, + { } +}; + +static u8 adi_dma_get_msize(u32 n_bytecount, u32 n_address) +{ + /* Calculate MSIZE, PSIZE, XCNT and XMOD */ + u8 n_msize = 0; + u32 n_value = n_bytecount | n_address; + u32 n_mask = 0x1; + + for (n_msize = 0; n_msize < 5; n_msize++, n_mask <<= 1) { + if ((n_value & n_mask) == n_mask) + break; + } + + return n_msize; +} + +static int adi_dma_get_ch_error(void __iomem *ch) +{ + u32 cause = (ioread32(ch + REG_STAT) & BITM_DMA_STAT_ERRC) >> + BITP_DMA_STAT_ERRC; + switch (cause) { + case 0: + return -EINVAL; + case 1: + return -EBUSY; + case 2: + return -EFAULT; + case 3: + fallthrough; + case 5: + fallthrough; + case 6: + fallthrough; + default: + return -EIO; + } +} + +static int adi_mdma_transfer(struct udevice *dev, int direction, + dma_addr_t dst, dma_addr_t src, size_t len) +{ + struct adi_dma *priv = dev_get_priv(dev); + void __iomem *chsrc = priv->channels[0].iosrc; + void __iomem *chdst = priv->channels[0].iodest; + + int result = 0; + u32 reg; + u32 bytecount = len; + + u8 n_srcmsize; + u8 n_dstmsize; + u8 n_srcpsize; + u8 n_dstpsize; + u8 n_psize; + u32 srcconfig; + u32 dstconfig; + u8 srcpsizemax = (ioread32(chsrc + REG_STAT) & BITM_DMA_STAT_PBWID) >> + BITP_DMA_STAT_PBWID; + u8 dstpsizemax = (ioread32(chdst + REG_STAT) & BITM_DMA_STAT_PBWID) >> + BITP_DMA_STAT_PBWID; + + const u32 CLRSTAT = (BITM_DMA_STAT_IRQDONE | BITM_DMA_STAT_IRQERR | + BITM_DMA_STAT_PIRQ); + + if (len == 0) + return -EINVAL; + + /* Clear DMA status */ + iowrite32(CLRSTAT, chsrc + REG_STAT); + iowrite32(CLRSTAT, chdst + REG_STAT); + + /* Calculate MSIZE, PSIZE, XCNT and XMOD */ + n_srcmsize = adi_dma_get_msize(bytecount, src); + n_dstmsize = adi_dma_get_msize(bytecount, dst); + n_srcpsize = min(n_srcmsize, srcpsizemax); + n_dstpsize = min(n_dstmsize, dstpsizemax); + n_psize = min(n_srcpsize, n_dstpsize); + + srcconfig = DMA_MDMA_SRC_DEFAULT_CONFIG(n_psize, n_srcmsize); + dstconfig = DMA_MDMA_DST_DEFAULT_CONFIG(n_psize, n_dstmsize); + + /* Load the DMA descriptors */ + iowrite32(src, chsrc + REG_ADDRSTART); + iowrite32(bytecount >> n_srcmsize, chsrc + REG_XCNT); + iowrite32(1 << n_srcmsize, chsrc + REG_XMOD); + iowrite32(dst, chdst + REG_ADDRSTART); + iowrite32(bytecount >> n_dstmsize, chdst + REG_XCNT); + iowrite32(1 << n_dstmsize, chdst + REG_XMOD); + + iowrite32(dstconfig, chdst + REG_CFG); + iowrite32(srcconfig, chsrc + REG_CFG); + + /* Wait for DMA to complete while checking for a DMA error */ + do { + reg = ioread32(chsrc + REG_STAT); + if ((reg & BITM_DMA_STAT_IRQERR) == BITM_DMA_STAT_IRQERR) { + result = adi_dma_get_ch_error(chsrc); + break; + } + reg = ioread32(chdst + REG_STAT); + if ((reg & BITM_DMA_STAT_IRQERR) == BITM_DMA_STAT_IRQERR) { + result = adi_dma_get_ch_error(chdst); + break; + } + } while ((reg & BITM_DMA_STAT_IRQDONE) == 0); + + clrbits_32(chsrc + REG_CFG, 1); + clrbits_32(chdst + REG_CFG, 1); + + return result; +} + +static int adi_dma_init_channel(struct adi_dma *dma, + struct adi_dma_channel *channel, ofnode node) +{ + u32 offset; + + if (ofnode_read_u32(node, "adi,id", &channel->id)) { + dev_err(dma->dev, "Missing adi,id for channel %s\n", + ofnode_get_name(node)); + return -ENOENT; + } + + if (ofnode_read_u32(node, "adi,src-offset", &offset)) { + dev_err(dma->dev, "Missing adi,src-offset for channel %s\n", + ofnode_get_name(node)); + return -ENOENT; + } + + channel->iosrc = dma->ioaddr + offset; + channel->dma = dma; + + if (dma->hw_cfg & HAS_MDMA) { + if (ofnode_read_u32(node, "adi,dest-offset", &offset)) { + dev_err(dma->dev, + "Missing adi,dest-offset for channel %s\n", + ofnode_get_name(node)); + return -ENOENT; + } + channel->iodest = dma->ioaddr + offset; + } + + return 0; +} + +static int adi_dma_probe(struct udevice *dev) +{ + struct dma_dev_priv *uc_priv = dev_get_uclass_priv(dev); + struct adi_dma *priv = dev_get_priv(dev); + ofnode node, child; + + priv->hw_cfg = dev_get_driver_data(dev); + if (priv->hw_cfg & HAS_MDMA) + uc_priv->supported = DMA_SUPPORTS_MEM_TO_MEM; + + priv->ioaddr = dev_remap_addr(dev); + if (!priv->ioaddr) + return -EINVAL; + + node = dev_read_first_subnode(dev); + if (!ofnode_valid(node)) { + dev_err(dev, + "Error: device tree DMA channel config missing!\n"); + return -ENODEV; + } + + node = dev_ofnode(dev); + ofnode_for_each_subnode(child, node) { + adi_dma_init_channel(priv, priv->channels, child); + break; //Only 1 channel supported for now + } + + return 0; +} + +static const struct dma_ops adi_dma_ops = { + .transfer = adi_mdma_transfer, +}; + +U_BOOT_DRIVER(adi_dma) = { + .name = "adi_dma", + .id = UCLASS_DMA, + .of_match = dma_dt_ids, + .ops = &adi_dma_ops, + .probe = adi_dma_probe, + .priv_auto = sizeof(struct adi_dma), +}; From d3bfe577880d20a19f155cf2bd33c5545c72cf6f Mon Sep 17 00:00:00 2001 From: Nathan Barrett-Morrison Date: Wed, 26 Feb 2025 12:30:32 -0500 Subject: [PATCH 10/12] remoteproc: Add in SHARC loading for ADI SC5XX-family processors This adds the ability to load ldr-formatted files to the SHARC coprocessors using the rproc interface. Only a minimal subset of rproc functionality is supported: loading and starting the remote core. Secure boot and signed ldr verification are not available at this time through the U-Boot interface. Co-developed-by: Greg Malysa Signed-off-by: Greg Malysa Co-developed-by: Ian Roberts Signed-off-by: Ian Roberts Co-developed-by: Piotr Wojtaszczyk Signed-off-by: Piotr Wojtaszczyk Signed-off-by: Vasileios Bimpikas Signed-off-by: Utsav Agarwal Signed-off-by: Arturs Artamonovs Signed-off-by: Oliver Gaskell Signed-off-by: Nathan Barrett-Morrison --- MAINTAINERS | 1 + drivers/remoteproc/Kconfig | 11 ++ drivers/remoteproc/Makefile | 1 + drivers/remoteproc/adi_sc5xx_rproc.c | 277 +++++++++++++++++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 drivers/remoteproc/adi_sc5xx_rproc.c diff --git a/MAINTAINERS b/MAINTAINERS index a9d05356763..42ee02bf391 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -638,6 +638,7 @@ F: drivers/gpio/gpio-adi-adsp.c F: drivers/i2c/adi_i2c.c F: drivers/net/dwc_eth_qos_adi.c F: drivers/pinctrl/pinctrl-adi-adsp.c +F: drivers/remoteproc/adi_sc5xx_rproc.c F: drivers/serial/serial_adi_uart4.c F: drivers/timer/adi_sc5xx_timer.c F: drivers/usb/musb-new/sc5xx.c diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index 2790b168b19..8cb2fd006f5 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -13,6 +13,7 @@ config REMOTEPROC depends on DM # Please keep the configuration alphabetically sorted. + config K3_SYSTEM_CONTROLLER bool "Support for TI' K3 System Controller" select REMOTEPROC @@ -22,6 +23,16 @@ config K3_SYSTEM_CONTROLLER help Say 'y' here to add support for TI' K3 System Controller. +config REMOTEPROC_ADI_SC5XX + bool "Support for ADI SC5xx SHARC cores" + select REMOTEPROC + depends on DM + depends on ARCH_SC5XX + depends on SYSCON + help + Say 'y' here to add support for loading code onto SHARC cores in + an ADSP-SC5xx SoC from Analog Devices + config REMOTEPROC_RENESAS_APMU bool "Support for Renesas R-Car Gen4 APMU start of CR52 processor" select REMOTEPROC diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index 3a092b7660e..65ba3ebfe4e 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_$(XPL_)REMOTEPROC) += rproc-uclass.o rproc-elf-loader.o # Remote proc drivers - Please keep this list alphabetically sorted. obj-$(CONFIG_K3_SYSTEM_CONTROLLER) += k3_system_controller.o +obj-$(CONFIG_REMOTEPROC_ADI_SC5XX) += adi_sc5xx_rproc.o obj-$(CONFIG_REMOTEPROC_RENESAS_APMU) += renesas_apmu.o obj-$(CONFIG_REMOTEPROC_SANDBOX) += sandbox_testproc.o obj-$(CONFIG_REMOTEPROC_STM32_COPRO) += stm32_copro.o diff --git a/drivers/remoteproc/adi_sc5xx_rproc.c b/drivers/remoteproc/adi_sc5xx_rproc.c new file mode 100644 index 00000000000..86acd1b98c7 --- /dev/null +++ b/drivers/remoteproc/adi_sc5xx_rproc.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright 2022 - Analog Devices, Inc. + * + * Written and/or maintained by Timesys Corporation + * + * Contact: Nathan Barrett-Morrison + * Contact: Greg Malysa + * + * Analog Devices SC5xx remoteproc driver for loading code onto SHARC cores + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Register offsets */ +#ifdef CONFIG_SC58X +#define ADI_RCU_REG_CTL 0x00 +#define ADI_RCU_REG_STAT 0x04 +#define ADI_RCU_REG_CRCTL 0x08 +#define ADI_RCU_REG_CRSTAT 0x0c +#define ADI_RCU_REG_SIDIS 0x10 +#define ADI_RCU_REG_SISTAT 0x14 +#define ADI_RCU_REG_BCODE 0x1c +#define ADI_RCU_REG_SVECT0 0x20 +#define ADI_RCU_REG_SVECT1 0x24 +#define ADI_RCU_REG_SVECT2 0x28 +#define ADI_RCU_REG_MSG 0x60 +#define ADI_RCU_REG_MSG_SET 0x64 +#define ADI_RCU_REG_MSG_CLR 0x68 +#else +#define ADI_RCU_REG_CTL 0x00 +#define ADI_RCU_REG_STAT 0x04 +#define ADI_RCU_REG_CRCTL 0x08 +#define ADI_RCU_REG_CRSTAT 0x0c +#define ADI_RCU_REG_SRRQSTAT 0x18 +#define ADI_RCU_REG_SIDIS 0x1c +#define ADI_RCU_REG_SISTAT 0x20 +#define ADI_RCU_REG_SVECT_LCK 0x24 +#define ADI_RCU_REG_BCODE 0x28 +#define ADI_RCU_REG_SVECT0 0x2c +#define ADI_RCU_REG_SVECT1 0x30 +#define ADI_RCU_REG_SVECT2 0x34 +#define ADI_RCU_REG_MSG 0x6c +#define ADI_RCU_REG_MSG_SET 0x70 +#define ADI_RCU_REG_MSG_CLR 0x74 +#endif /* CONFIG_SC58X */ + +/* Register bit definitions */ +#define ADI_RCU_CTL_SYSRST BIT(0) + +/* Bit values for the RCU0_MSG register */ +#define RCU0_MSG_C0IDLE 0x00000100 /* Core 0 Idle */ +#define RCU0_MSG_C1IDLE 0x00000200 /* Core 1 Idle */ +#define RCU0_MSG_C2IDLE 0x00000400 /* Core 2 Idle */ +#define RCU0_MSG_CRR0 0x00001000 /* Core 0 reset request */ +#define RCU0_MSG_CRR1 0x00002000 /* Core 1 reset request */ +#define RCU0_MSG_CRR2 0x00004000 /* Core 2 reset request */ +#define RCU0_MSG_C1ACTIVATE 0x00080000 /* Core 1 Activated */ +#define RCU0_MSG_C2ACTIVATE 0x00100000 /* Core 2 Activated */ + +struct sc5xx_rproc_data { + /* Address to load to svect when rebooting core */ + u32 load_addr; + + /* RCU parameters */ + struct regmap *rcu; + u32 svect_offset; + u32 coreid; +}; + +struct block_code_flag { + u32 bcode:4, /* 0-3 */ + bflag_save:1, /* 4 */ + bflag_aux:1, /* 5 */ + breserved:1, /* 6 */ + bflag_forward:1, /* 7 */ + bflag_fill:1, /* 8 */ + bflag_quickboot:1, /* 9 */ + bflag_callback:1, /* 10 */ + bflag_init:1, /* 11 */ + bflag_ignore:1, /* 12 */ + bflag_indirect:1, /* 13 */ + bflag_first:1, /* 14 */ + bflag_final:1, /* 15 */ + bhdrchk:8, /* 16-23 */ + bhdrsign:8; /* 0xAD, 0xAC or 0xAB */ +}; + +struct ldr_hdr { + struct block_code_flag bcode_flag; + u32 target_addr; + u32 byte_count; + u32 argument; +}; + +static int is_final(struct ldr_hdr *hdr) +{ + return hdr->bcode_flag.bflag_final; +} + +static int is_empty(struct ldr_hdr *hdr) +{ + return hdr->bcode_flag.bflag_ignore || (hdr->byte_count == 0); +} + +static int adi_valid_firmware(struct ldr_hdr *adi_ldr_hdr) +{ + if (!adi_ldr_hdr->byte_count && + (adi_ldr_hdr->bcode_flag.bhdrsign == 0xAD || + adi_ldr_hdr->bcode_flag.bhdrsign == 0xAC || + adi_ldr_hdr->bcode_flag.bhdrsign == 0xAB)) + return 1; + + return 0; +} + +static int sharc_load(struct udevice *dev, ulong addr, ulong size) +{ + struct sc5xx_rproc_data *priv = dev_get_priv(dev); + size_t offset; + u8 *buf = (u8 *)addr; + struct ldr_hdr *ldr = (struct ldr_hdr *)addr; + struct ldr_hdr *block_hdr; + struct ldr_hdr *next_hdr; + + if (!adi_valid_firmware(ldr)) { + dev_err(dev, "Firmware at 0x%lx does not appear to be an LDR image\n", addr); + dev_err(dev, "Note: Signed firmware is not currently supported\n"); + return -EINVAL; + } + + do { + block_hdr = (struct ldr_hdr *)buf; + offset = sizeof(struct ldr_hdr) + (block_hdr->bcode_flag.bflag_fill ? + 0 : block_hdr->byte_count); + next_hdr = (struct ldr_hdr *)(buf + offset); + + if (block_hdr->bcode_flag.bflag_first) + priv->load_addr = (unsigned long)block_hdr->target_addr; + + if (!is_empty(block_hdr)) { + if (block_hdr->bcode_flag.bflag_fill) { + memset_io((void *)(phys_addr_t)block_hdr->target_addr, + block_hdr->argument, + block_hdr->byte_count); + } else { + memcpy_toio((void *)(phys_addr_t)block_hdr->target_addr, + buf + sizeof(struct ldr_hdr), + block_hdr->byte_count); + } + } + + if (is_final(block_hdr)) + break; + + buf += offset; + } while (1); + + return 0; +} + +static void sharc_reset(struct sc5xx_rproc_data *priv) +{ + u32 coreid = priv->coreid; + u32 val; + + /* First put core in reset. + * Clear CRSTAT bit for given coreid. + */ + regmap_write(priv->rcu, ADI_RCU_REG_CRSTAT, 1 << coreid); + + /* Set SIDIS to disable the system interface */ + regmap_read(priv->rcu, ADI_RCU_REG_SIDIS, &val); + regmap_write(priv->rcu, ADI_RCU_REG_SIDIS, val | (1 << (coreid - 1))); + + /* + * Wait for access to coreX have been disabled and all the pending + * transactions have completed + */ + udelay(50); + + /* Set CRCTL bit to put core in reset */ + regmap_read(priv->rcu, ADI_RCU_REG_CRCTL, &val); + regmap_write(priv->rcu, ADI_RCU_REG_CRCTL, val | (1 << coreid)); + + /* Poll until Core is in reset */ + while (!(regmap_read(priv->rcu, ADI_RCU_REG_CRSTAT, &val), val & (1 << coreid))) + ; + + /* Clear SIDIS to reenable the system interface */ + regmap_read(priv->rcu, ADI_RCU_REG_SIDIS, &val); + regmap_write(priv->rcu, ADI_RCU_REG_SIDIS, val & ~(1 << (coreid - 1))); + + udelay(50); + + /* Take Core out of reset */ + regmap_read(priv->rcu, ADI_RCU_REG_CRCTL, &val); + regmap_write(priv->rcu, ADI_RCU_REG_CRCTL, val & ~(1 << coreid)); + + /* Wait for done */ + udelay(50); +} + +static int sharc_start(struct udevice *dev) +{ + struct sc5xx_rproc_data *priv = dev_get_priv(dev); + + /* Write load address to appropriate SVECT for core */ + regmap_write(priv->rcu, priv->svect_offset, priv->load_addr); + + sharc_reset(priv); + + /* Clear the IDLE bit when start the SHARC core */ + regmap_write(priv->rcu, ADI_RCU_REG_MSG_CLR, RCU0_MSG_C0IDLE << priv->coreid); + + /* Notify CCES */ + regmap_write(priv->rcu, ADI_RCU_REG_MSG_SET, RCU0_MSG_C1ACTIVATE << (priv->coreid - 1)); + return 0; +} + +static const struct dm_rproc_ops sc5xx_ops = { + .load = sharc_load, + .start = sharc_start, +}; + +static int sc5xx_probe(struct udevice *dev) +{ + struct sc5xx_rproc_data *priv = dev_get_priv(dev); + u32 coreid; + + if (dev_read_u32(dev, "coreid", &coreid)) { + dev_err(dev, "Missing property coreid\n"); + return -ENOENT; + } + + priv->coreid = coreid; + switch (coreid) { + case 1: + priv->svect_offset = ADI_RCU_REG_SVECT1; + break; + case 2: + priv->svect_offset = ADI_RCU_REG_SVECT2; + break; + default: + dev_err(dev, "Invalid value %d for coreid, must be 1 or 2\n", coreid); + return -EINVAL; + } + + priv->rcu = syscon_regmap_lookup_by_phandle(dev, "adi,rcu"); + if (IS_ERR(priv->rcu)) + return PTR_ERR(priv->rcu); + + dev_err(dev, "sc5xx remoteproc core %d available\n", priv->coreid); + + return 0; +} + +static const struct udevice_id sc5xx_ids[] = { + { .compatible = "adi,sc5xx-rproc" }, + { } +}; + +U_BOOT_DRIVER(adi_sc5xx_rproc) = { + .name = "adi_sc5xx_rproc", + .of_match = sc5xx_ids, + .id = UCLASS_REMOTEPROC, + .ops = &sc5xx_ops, + .probe = sc5xx_probe, + .priv_auto = sizeof(struct sc5xx_rproc_data), + .flags = 0, +}; From 7535a9280be1fc81b280517c7c44b5f1c2efc0ba Mon Sep 17 00:00:00 2001 From: Nathan Barrett-Morrison Date: Wed, 26 Feb 2025 12:30:33 -0500 Subject: [PATCH 11/12] spi: Add support for ADI SC5XX-family processor SPI peripherals This adds support for the ADI-specific SPI driver present in the ADI SC5xx line of SoCs. This IP block is distinct from the QSPI/OSPI block that uses the Cadence driver. Both may be used at once with appropriate pin muxing configuration. Co-developed-by: Greg Malysa Signed-off-by: Greg Malysa Co-developed-by: Angelo Dureghello Signed-off-by: Angelo Dureghello Co-developed-by: Ian Roberts Signed-off-by: Ian Roberts Co-developed-by: Piotr Wojtaszczyk Signed-off-by: Piotr Wojtaszczyk Signed-off-by: Vasileios Bimpikas Signed-off-by: Utsav Agarwal Signed-off-by: Arturs Artamonovs Signed-off-by: Oliver Gaskell Signed-off-by: Nathan Barrett-Morrison --- MAINTAINERS | 1 + drivers/spi/Kconfig | 7 + drivers/spi/Makefile | 1 + drivers/spi/adi_spi3.c | 679 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 688 insertions(+) create mode 100644 drivers/spi/adi_spi3.c diff --git a/MAINTAINERS b/MAINTAINERS index 42ee02bf391..889695c1edd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -640,6 +640,7 @@ F: drivers/net/dwc_eth_qos_adi.c F: drivers/pinctrl/pinctrl-adi-adsp.c F: drivers/remoteproc/adi_sc5xx_rproc.c F: drivers/serial/serial_adi_uart4.c +F: drivers/spi/adi_spi3.c F: drivers/timer/adi_sc5xx_timer.c F: drivers/usb/musb-new/sc5xx.c F: drivers/watchdog/adi_wdt.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 96ea033082b..846a93a4e57 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -52,6 +52,13 @@ config SPI_DIRMAP if DM_SPI +config ADI_SPI3 + bool "Enable ADI SPI Driver" + depends on ARCH_SC5XX + help + Enable the ADI (Analog Devices) SPI controller driver. This + driver enables the support for SC5XX spi controller. + config ALTERA_SPI bool "Altera SPI driver" help diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 7051e2a00c6..21895d46429 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -19,6 +19,7 @@ obj-y += spi.o obj-$(CONFIG_SPI_MEM) += spi-mem-nodm.o endif +obj-$(CONFIG_ADI_SPI3) += adi_spi3.o obj-$(CONFIG_ALTERA_SPI) += altera_spi.o obj-$(CONFIG_APPLE_SPI) += apple_spi.o obj-$(CONFIG_ATH79_SPI) += ath79_spi.o diff --git a/drivers/spi/adi_spi3.c b/drivers/spi/adi_spi3.c new file mode 100644 index 00000000000..2125d25561a --- /dev/null +++ b/drivers/spi/adi_spi3.c @@ -0,0 +1,679 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright 2022 - Analog Devices, Inc. + * + * Written and/or maintained by Timesys Corporation + * + * Converted to driver model by Nathan Barrett-Morrison + * + * Contact: Nathan Barrett-Morrison + * Contact: Greg Malysa + * Contact: Ian Roberts + * Contact: Piotr Wojtaszczyk + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#define SPI_IDLE_VAL 0xff + +#define MAX_CTRL_CS 7 + +/* SPI_CONTROL */ +#define SPI_CTL_EN 0x00000001 /* Enable */ +#define SPI_CTL_MSTR 0x00000002 /* Master/Slave */ +#define SPI_CTL_PSSE 0x00000004 /* controls modf error in master mode */ +#define SPI_CTL_ODM 0x00000008 /* Open Drain Mode */ +#define SPI_CTL_CPHA 0x00000010 /* Clock Phase */ +#define SPI_CTL_CPOL 0x00000020 /* Clock Polarity */ +#define SPI_CTL_ASSEL 0x00000040 /* Slave Select Pin Control */ +#define SPI_CTL_SELST 0x00000080 /* Slave Select Polarity in transfers */ +#define SPI_CTL_EMISO 0x00000100 /*Enable MISO */ +#define SPI_CTL_SIZE 0x00000600 /*Word Transfer Size */ +#define SPI_CTL_SIZE08 0x00000000 /*SIZE: 8 bits */ +#define SPI_CTL_SIZE16 0x00000200 /*SIZE: 16 bits */ +#define SPI_CTL_SIZE32 0x00000400 /*SIZE: 32 bits */ +#define SPI_CTL_LSBF 0x00001000 /*LSB First */ +#define SPI_CTL_FCEN 0x00002000 /*Flow-Control Enable */ +#define SPI_CTL_FCCH 0x00004000 /*Flow-Control Channel Selection */ +#define SPI_CTL_FCPL 0x00008000 /*Flow-Control Polarity */ +#define SPI_CTL_FCWM 0x00030000 /*Flow-Control Water-Mark */ +#define SPI_CTL_FIFO0 0x00000000 /*FCWM: Tx empty or Rx Full */ +#define SPI_CTL_FIFO1 0x00010000 /*FCWM: Tx empty or Rx full (>=75%) */ +#define SPI_CTL_FIFO2 0x00020000 /*FCWM: Tx empty or Rx full (>=50%) */ +#define SPI_CTL_FMODE 0x00040000 /*Fast-mode Enable */ +#define SPI_CTL_MIOM 0x00300000 /*Multiple I/O Mode */ +#define SPI_CTL_MIO_DIS 0x00000000 /*MIOM: Disable */ +#define SPI_CTL_MIO_DUAL 0x00100000 /*MIOM: Enable DIOM (Dual I/O Mode) */ +#define SPI_CTL_MIO_QUAD 0x00200000 /*MIOM: Enable QUAD (Quad SPI Mode) */ +#define SPI_CTL_SOSI 0x00400000 /*Start on MOSI */ +#define SPI_CTL_MMWEM 0x40000000 /*Start on MMWEM */ +#define SPI_CTL_MMSE 0x80000000 /*Start on MMSE */ +/* SPI_RX_CONTROL */ +#define SPI_RXCTL_REN 0x00000001 /*Receive Channel Enable */ +#define SPI_RXCTL_RTI 0x00000004 /*Receive Transfer Initiate */ +#define SPI_RXCTL_RWCEN 0x00000008 /*Receive Word Counter Enable */ +#define SPI_RXCTL_RDR 0x00000070 /*Receive Data Request */ +#define SPI_RXCTL_RDR_DIS 0x00000000 /*RDR: Disabled */ +#define SPI_RXCTL_RDR_NE 0x00000010 /*RDR: RFIFO not empty */ +#define SPI_RXCTL_RDR_25 0x00000020 /*RDR: RFIFO 25% full */ +#define SPI_RXCTL_RDR_50 0x00000030 /*RDR: RFIFO 50% full */ +#define SPI_RXCTL_RDR_75 0x00000040 /*RDR: RFIFO 75% full */ +#define SPI_RXCTL_RDR_FULL 0x00000050 /*RDR: RFIFO full */ +#define SPI_RXCTL_RDO 0x00000100 /*Receive Data Over-Run */ +#define SPI_RXCTL_RRWM 0x00003000 /*FIFO Regular Water-Mark */ +#define SPI_RXCTL_RWM_0 0x00000000 /*RRWM: RFIFO Empty */ +#define SPI_RXCTL_RWM_25 0x00001000 /*RRWM: RFIFO 25% full */ +#define SPI_RXCTL_RWM_50 0x00002000 /*RRWM: RFIFO 50% full */ +#define SPI_RXCTL_RWM_75 0x00003000 /*RRWM: RFIFO 75% full */ +#define SPI_RXCTL_RUWM 0x00070000 /*FIFO Urgent Water-Mark */ +#define SPI_RXCTL_UWM_DIS 0x00000000 /*RUWM: Disabled */ +#define SPI_RXCTL_UWM_25 0x00010000 /*RUWM: RFIFO 25% full */ +#define SPI_RXCTL_UWM_50 0x00020000 /*RUWM: RFIFO 50% full */ +#define SPI_RXCTL_UWM_75 0x00030000 /*RUWM: RFIFO 75% full */ +#define SPI_RXCTL_UWM_FULL 0x00040000 /*RUWM: RFIFO full */ +/* SPI_TX_CONTROL */ +#define SPI_TXCTL_TEN 0x00000001 /*Transmit Channel Enable */ +#define SPI_TXCTL_TTI 0x00000004 /*Transmit Transfer Initiate */ +#define SPI_TXCTL_TWCEN 0x00000008 /*Transmit Word Counter Enable */ +#define SPI_TXCTL_TDR 0x00000070 /*Transmit Data Request */ +#define SPI_TXCTL_TDR_DIS 0x00000000 /*TDR: Disabled */ +#define SPI_TXCTL_TDR_NF 0x00000010 /*TDR: TFIFO not full */ +#define SPI_TXCTL_TDR_25 0x00000020 /*TDR: TFIFO 25% empty */ +#define SPI_TXCTL_TDR_50 0x00000030 /*TDR: TFIFO 50% empty */ +#define SPI_TXCTL_TDR_75 0x00000040 /*TDR: TFIFO 75% empty */ +#define SPI_TXCTL_TDR_EMPTY 0x00000050 /*TDR: TFIFO empty */ +#define SPI_TXCTL_TDU 0x00000100 /*Transmit Data Under-Run */ +#define SPI_TXCTL_TRWM 0x00003000 /*FIFO Regular Water-Mark */ +#define SPI_TXCTL_RWM_FULL 0x00000000 /*TRWM: TFIFO full */ +#define SPI_TXCTL_RWM_25 0x00001000 /*TRWM: TFIFO 25% empty */ +#define SPI_TXCTL_RWM_50 0x00002000 /*TRWM: TFIFO 50% empty */ +#define SPI_TXCTL_RWM_75 0x00003000 /*TRWM: TFIFO 75% empty */ +#define SPI_TXCTL_TUWM 0x00070000 /*FIFO Urgent Water-Mark */ +#define SPI_TXCTL_UWM_DIS 0x00000000 /*TUWM: Disabled */ +#define SPI_TXCTL_UWM_25 0x00010000 /*TUWM: TFIFO 25% empty */ +#define SPI_TXCTL_UWM_50 0x00020000 /*TUWM: TFIFO 50% empty */ +#define SPI_TXCTL_UWM_75 0x00030000 /*TUWM: TFIFO 75% empty */ +#define SPI_TXCTL_UWM_EMPTY 0x00040000 /*TUWM: TFIFO empty */ +/* SPI_CLOCK */ +#define SPI_CLK_BAUD 0x0000FFFF /*Baud Rate */ +/* SPI_DELAY */ +#define SPI_DLY_STOP 0x000000FF /*Transfer delay time */ +#define SPI_DLY_LEADX 0x00000100 /*Extended (1 SCK) LEAD Control */ +#define SPI_DLY_LAGX 0x00000200 /*Extended (1 SCK) LAG control */ +/* SPI_SSEL */ +#define SPI_SLVSEL_SSE1 0x00000002 /*SPISSEL1 Enable */ +#define SPI_SLVSEL_SSE2 0x00000004 /*SPISSEL2 Enable */ +#define SPI_SLVSEL_SSE3 0x00000008 /*SPISSEL3 Enable */ +#define SPI_SLVSEL_SSE4 0x00000010 /*SPISSEL4 Enable */ +#define SPI_SLVSEL_SSE5 0x00000020 /*SPISSEL5 Enable */ +#define SPI_SLVSEL_SSE6 0x00000040 /*SPISSEL6 Enable */ +#define SPI_SLVSEL_SSE7 0x00000080 /*SPISSEL7 Enable */ +#define SPI_SLVSEL_SSEL1 0x00000200 /*SPISSEL1 Value */ +#define SPI_SLVSEL_SSEL2 0x00000400 /*SPISSEL2 Value */ +#define SPI_SLVSEL_SSEL3 0x00000800 /*SPISSEL3 Value */ +#define SPI_SLVSEL_SSEL4 0x00001000 /*SPISSEL4 Value */ +#define SPI_SLVSEL_SSEL5 0x00002000 /*SPISSEL5 Value */ +#define SPI_SLVSEL_SSEL6 0x00004000 /*SPISSEL6 Value */ +#define SPI_SLVSEL_SSEL7 0x00008000 /*SPISSEL7 Value */ +/* SPI_RWC */ +#define SPI_RWC_VALUE 0x0000FFFF /*Received Word-Count */ +/* SPI_RWCR */ +#define SPI_RWCR_VALUE 0x0000FFFF /*Received Word-Count Reload */ +/* SPI_TWC */ +#define SPI_TWC_VALUE 0x0000FFFF /*Transmitted Word-Count */ +/* SPI_TWCR */ +#define SPI_TWCR_VALUE 0x0000FFFF /*Transmitted Word-Count Reload */ +/* SPI_IMASK */ +#define SPI_IMSK_RUWM 0x00000002 /*Receive Water-Mark Interrupt Mask */ +#define SPI_IMSK_TUWM 0x00000004 /*Transmit Water-Mark Interrupt Mask */ +#define SPI_IMSK_ROM 0x00000010 /*Receive Over-Run Interrupt Mask */ +#define SPI_IMSK_TUM 0x00000020 /*Transmit Under-Run Interrupt Mask */ +#define SPI_IMSK_TCM 0x00000040 /*Transmit Collision Interrupt Mask */ +#define SPI_IMSK_MFM 0x00000080 /*Mode Fault Interrupt Mask */ +#define SPI_IMSK_RSM 0x00000100 /*Receive Start Interrupt Mask */ +#define SPI_IMSK_TSM 0x00000200 /*Transmit Start Interrupt Mask */ +#define SPI_IMSK_RFM 0x00000400 /*Receive Finish Interrupt Mask */ +#define SPI_IMSK_TFM 0x00000800 /*Transmit Finish Interrupt Mask */ +/* SPI_IMASKCL */ +#define SPI_IMSK_CLR_RUW 0x00000002 /*Receive Water-Mark Interrupt Mask */ +#define SPI_IMSK_CLR_TUWM 0x00000004 /*Transmit Water-Mark Interrupt Mask */ +#define SPI_IMSK_CLR_ROM 0x00000010 /*Receive Over-Run Interrupt Mask */ +#define SPI_IMSK_CLR_TUM 0x00000020 /*Transmit Under-Run Interrupt Mask */ +#define SPI_IMSK_CLR_TCM 0x00000040 /*Transmit Collision Interrupt Mask */ +#define SPI_IMSK_CLR_MFM 0x00000080 /*Mode Fault Interrupt Mask */ +#define SPI_IMSK_CLR_RSM 0x00000100 /*Receive Start Interrupt Mask */ +#define SPI_IMSK_CLR_TSM 0x00000200 /*Transmit Start Interrupt Mask */ +#define SPI_IMSK_CLR_RFM 0x00000400 /*Receive Finish Interrupt Mask */ +#define SPI_IMSK_CLR_TFM 0x00000800 /*Transmit Finish Interrupt Mask */ +/* SPI_IMASKST */ +#define SPI_IMSK_SET_RUWM 0x00000002 /*Receive Water-Mark Interrupt Mask */ +#define SPI_IMSK_SET_TUWM 0x00000004 /*Transmit Water-Mark Interrupt Mask */ +#define SPI_IMSK_SET_ROM 0x00000010 /*Receive Over-Run Interrupt Mask */ +#define SPI_IMSK_SET_TUM 0x00000020 /*Transmit Under-Run Interrupt Mask */ +#define SPI_IMSK_SET_TCM 0x00000040 /*Transmit Collision Interrupt Mask */ +#define SPI_IMSK_SET_MFM 0x00000080 /*Mode Fault Interrupt Mask */ +#define SPI_IMSK_SET_RSM 0x00000100 /*Receive Start Interrupt Mask */ +#define SPI_IMSK_SET_TSM 0x00000200 /*Transmit Start Interrupt Mask */ +#define SPI_IMSK_SET_RFM 0x00000400 /*Receive Finish Interrupt Mask */ +#define SPI_IMSK_SET_TFM 0x00000800 /*Transmit Finish Interrupt Mask */ +/* SPI_STATUS */ +#define SPI_STAT_SPIF 0x00000001 /*SPI Finished */ +#define SPI_STAT_RUWM 0x00000002 /*Receive Water-Mark Breached */ +#define SPI_STAT_TUWM 0x00000004 /*Transmit Water-Mark Breached */ +#define SPI_STAT_ROE 0x00000010 /*Receive Over-Run Indication */ +#define SPI_STAT_TUE 0x00000020 /*Transmit Under-Run Indication */ +#define SPI_STAT_TCE 0x00000040 /*Transmit Collision Indication */ +#define SPI_STAT_MODF 0x00000080 /*Mode Fault Indication */ +#define SPI_STAT_RS 0x00000100 /*Receive Start Indication */ +#define SPI_STAT_TS 0x00000200 /*Transmit Start Indication */ +#define SPI_STAT_RF 0x00000400 /*Receive Finish Indication */ +#define SPI_STAT_TF 0x00000800 /*Transmit Finish Indication */ +#define SPI_STAT_RFS 0x00007000 /*SPI_RFIFO status */ +#define SPI_STAT_RFIFO_EMPTY 0x00000000 /*RFS: RFIFO Empty */ +#define SPI_STAT_RFIFO_25 0x00001000 /*RFS: RFIFO 25% Full */ +#define SPI_STAT_RFIFO_50 0x00002000 /*RFS: RFIFO 50% Full */ +#define SPI_STAT_RFIFO_75 0x00003000 /*RFS: RFIFO 75% Full */ +#define SPI_STAT_RFIFO_FULL 0x00004000 /*RFS: RFIFO Full */ +#define SPI_STAT_TFS 0x00070000 /*SPI_TFIFO status */ +#define SPI_STAT_TFIFO_FULL 0x00000000 /*TFS: TFIFO full */ +#define SPI_STAT_TFIFO_25 0x00010000 /*TFS: TFIFO 25% empty */ +#define SPI_STAT_TFIFO_50 0x00020000 /*TFS: TFIFO 50% empty */ +#define SPI_STAT_TFIFO_75 0x00030000 /*TFS: TFIFO 75% empty */ +#define SPI_STAT_TFIFO_EMPTY 0x00040000 /*TFS: TFIFO empty */ +#define SPI_STAT_FCS 0x00100000 /*Flow-Control Stall Indication */ +#define SPI_STAT_RFE 0x00400000 /*SPI_RFIFO Empty */ +#define SPI_STAT_TFF 0x00800000 /*SPI_TFIFO Full */ +/* SPI_ILAT */ +#define SPI_ILAT_RUWMI 0x00000002 /*Receive Water Mark Interrupt */ +#define SPI_ILAT_TUWMI 0x00000004 /*Transmit Water Mark Interrupt */ +#define SPI_ILAT_ROI 0x00000010 /*Receive Over-Run Indication */ +#define SPI_ILAT_TUI 0x00000020 /*Transmit Under-Run Indication */ +#define SPI_ILAT_TCI 0x00000040 /*Transmit Collision Indication */ +#define SPI_ILAT_MFI 0x00000080 /*Mode Fault Indication */ +#define SPI_ILAT_RSI 0x00000100 /*Receive Start Indication */ +#define SPI_ILAT_TSI 0x00000200 /*Transmit Start Indication */ +#define SPI_ILAT_RFI 0x00000400 /*Receive Finish Indication */ +#define SPI_ILAT_TFI 0x00000800 /*Transmit Finish Indication */ +/* SPI_ILATCL */ +#define SPI_ILAT_CLR_RUWMI 0x00000002 /*Receive Water Mark Interrupt */ +#define SPI_ILAT_CLR_TUWMI 0x00000004 /*Transmit Water Mark Interrupt */ +#define SPI_ILAT_CLR_ROI 0x00000010 /*Receive Over-Run Indication */ +#define SPI_ILAT_CLR_TUI 0x00000020 /*Transmit Under-Run Indication */ +#define SPI_ILAT_CLR_TCI 0x00000040 /*Transmit Collision Indication */ +#define SPI_ILAT_CLR_MFI 0x00000080 /*Mode Fault Indication */ +#define SPI_ILAT_CLR_RSI 0x00000100 /*Receive Start Indication */ +#define SPI_ILAT_CLR_TSI 0x00000200 /*Transmit Start Indication */ +#define SPI_ILAT_CLR_RFI 0x00000400 /*Receive Finish Indication */ +#define SPI_ILAT_CLR_TFI 0x00000800 /*Transmit Finish Indication */ +/* SPI_MMRDH */ +#define SPI_MMRDH_MERGE 0x04000000 /*Merge Enable */ +#define SPI_MMRDH_DMY_SZ 0x00007000 /*Bytes of Dummy */ +#define SPI_MMRDH_ADDR_PINS 0x00000800 /*Pins used for Address */ +#define SPI_MMRDH_ADDR_SZ 0x00000700 /*Bytes of Read Address */ +#define SPI_MMRDH_OPCODE 0x000000FF /*Read Opcode */ + +#define SPI_MMRDH_TRIDMY_OFF 24 /*Bytes of Dummy offset */ +#define SPI_MMRDH_DMY_SZ_OFF 12 /*Bytes of Dummy offset */ +#define SPI_MMRDH_ADDR_SZ_OFF 8 /*Bytes of Read Address offset */ + +#define BIT_SSEL_VAL(x) ((1 << 8) << (x)) /* Slave Select input value bit */ +#define BIT_SSEL_EN(x) (1 << (x)) /* Slave Select enable bit*/ + +struct adi_spi_regs { + u32 revid; + u32 control; + u32 rx_control; + u32 tx_control; + u32 clock; + u32 delay; + u32 ssel; + u32 rwc; + u32 rwcr; + u32 twc; + u32 twcr; + u32 reserved0; + u32 emask; + u32 emaskcl; + u32 emaskst; + u32 reserved1; + u32 status; + u32 elat; + u32 elatcl; + u32 reserved2; + u32 rfifo; + u32 reserved3; + u32 tfifo; + u32 reserved4; + u32 mmrdh; + u32 mmtop; +}; + +struct adi_spi_platdata { + u32 max_hz; + u32 bus_num; + struct adi_spi_regs __iomem *regs; +}; + +struct adi_spi_priv { + u32 control; + u32 clock; + u32 bus_num; + u32 max_cs; + struct adi_spi_regs __iomem *regs; +}; + +/** + * By convention, this driver uses the same CS numbering that is used with the SSEL bit + * definitions (both here and in the TRM on which this is based), which are 1-indexed not + * 0-indexed. The valid CS range is therefore [1,max_cs], in contrast with other drivers + * where it is [0,max_cs-1]. + */ +static int adi_spi_cs_info(struct udevice *bus, uint cs, + struct spi_cs_info *info) +{ + struct adi_spi_priv *priv = dev_get_priv(bus); + + if (cs == 0 || cs > priv->max_cs) { + dev_err(bus, "invalid chipselect %u\n", cs); + return -EINVAL; + } + + return 0; +} + +static int adi_spi_of_to_plat(struct udevice *bus) +{ + struct adi_spi_platdata *plat = dev_get_plat(bus); + fdt_addr_t addr; + + plat->max_hz = dev_read_u32_default(bus, "spi-max-frequency", 500000); + plat->bus_num = dev_read_u32_default(bus, "bus-num", 0); + addr = dev_read_addr(bus); + + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; + + plat->regs = map_sysmem(addr, sizeof(*plat->regs)); + + return 0; +} + +static int adi_spi_probe(struct udevice *bus) +{ + struct adi_spi_platdata *plat = dev_get_plat(bus); + struct adi_spi_priv *priv = dev_get_priv(bus); + + priv->bus_num = plat->bus_num; + priv->regs = plat->regs; + priv->max_cs = dev_read_u32_default(bus, "num-cs", MAX_CTRL_CS); + + iowrite32(0x0, &plat->regs->control); + iowrite32(0x0, &plat->regs->rx_control); + iowrite32(0x0, &plat->regs->tx_control); + + return 0; +} + +static int adi_spi_remove(struct udevice *dev) +{ + return -ENODEV; +} + +static int adi_spi_claim_bus(struct udevice *dev) +{ + struct adi_spi_priv *priv; + struct udevice *bus = dev->parent; + + priv = dev_get_priv(bus); + + debug("%s: control:%i clock:%i\n", + __func__, priv->control, priv->clock); + + iowrite32(priv->control, &priv->regs->control); + iowrite32(priv->clock, &priv->regs->clock); + iowrite32(0x0, &priv->regs->delay); + + return 0; +} + +static int adi_spi_release_bus(struct udevice *dev) +{ + struct adi_spi_priv *priv; + struct udevice *bus = dev->parent; + + priv = dev_get_priv(bus); + + debug("%s: control:%i clock:%i\n", + __func__, priv->control, priv->clock); + + iowrite32(0x0, &priv->regs->rx_control); + iowrite32(0x0, &priv->regs->tx_control); + iowrite32(0x0, &priv->regs->control); + + return 0; +} + +void adi_spi_enable_ssel(struct adi_spi_priv *priv, int cs) +{ + setbits_32(&priv->regs->ssel, BIT_SSEL_EN(cs)); +} + +void adi_spi_set_ssel(struct adi_spi_priv *priv, int cs, int high) +{ + if (high) + setbits_32(&priv->regs->ssel, BIT_SSEL_VAL(cs)); + else + clrbits_32(&priv->regs->ssel, BIT_SSEL_VAL(cs)); +} + +void adi_spi_cs_activate(struct adi_spi_priv *priv, struct dm_spi_slave_plat *slave_plat) +{ + bool high = slave_plat->mode & SPI_CS_HIGH; + + adi_spi_set_ssel(priv, slave_plat->cs[0], high); + adi_spi_enable_ssel(priv, slave_plat->cs[0]); +} + +void adi_spi_cs_deactivate(struct adi_spi_priv *priv, struct dm_spi_slave_plat *slave_plat) +{ + bool high = slave_plat->mode & SPI_CS_HIGH; + + /* invert CS for matching SSEL to deactivate */ + adi_spi_set_ssel(priv, slave_plat->cs[0], !high); +} + +static void discard_rx_fifo_contents(struct adi_spi_regs *regs) +{ + while (!(ioread32(®s->status) & SPI_STAT_RFE)) + ioread32(®s->rfifo); +} + +static int adi_spi_fifo_mio_xfer(struct adi_spi_priv *priv, const u8 *tx, u8 *rx, + uint bytes, uint32_t mio_mode) +{ + u8 value; + + /* switch current SPI transfer to mio SPI mode */ + clrsetbits_32(&priv->regs->control, SPI_CTL_SOSI, mio_mode); + /* + * Data can only be transferred in one direction in multi-io SPI + * modes, trigger the transfer in respective direction. + */ + if (rx) { + iowrite32(0x0, &priv->regs->tx_control); + iowrite32(SPI_RXCTL_REN | SPI_RXCTL_RTI, &priv->regs->rx_control); + + while (bytes--) { + while (ioread32(&priv->regs->status) & + SPI_STAT_RFE) + if (ctrlc()) + return -1; + value = ioread32(&priv->regs->rfifo); + *rx++ = value; + } + } else if (tx) { + iowrite32(0x0, &priv->regs->rx_control); + iowrite32(SPI_TXCTL_TEN | SPI_TXCTL_TTI, &priv->regs->tx_control); + + while (bytes--) { + value = *tx++; + iowrite32(value, &priv->regs->tfifo); + while (ioread32(&priv->regs->status) & + SPI_STAT_TFF) + if (ctrlc()) + return -1; + } + + /* Wait till the tfifo is empty */ + while ((ioread32(&priv->regs->status) & SPI_STAT_TFS) != SPI_STAT_TFIFO_EMPTY) + if (ctrlc()) + return -1; + } else { + return -1; + } + return 0; +} + +static int adi_spi_fifo_1x_xfer(struct adi_spi_priv *priv, const u8 *tx, u8 *rx, + uint bytes) +{ + u8 value; + + /* + * Set current SPI transfer in normal mode and trigger + * the bi-direction transfer by tx write operation. + */ + iowrite32(priv->control, &priv->regs->control); + iowrite32(SPI_RXCTL_REN, &priv->regs->rx_control); + iowrite32(SPI_TXCTL_TEN | SPI_TXCTL_TTI, &priv->regs->tx_control); + + while (bytes--) { + value = (tx ? *tx++ : SPI_IDLE_VAL); + debug("%s: tx:%x ", __func__, value); + iowrite32(value, &priv->regs->tfifo); + while (ioread32(&priv->regs->status) & SPI_STAT_RFE) + if (ctrlc()) + return -1; + value = ioread32(&priv->regs->rfifo); + if (rx) + *rx++ = value; + debug("rx:%x\n", value); + } + return 0; +} + +static int adi_spi_fifo_xfer(struct adi_spi_priv *priv, int buswidth, + const u8 *tx, u8 *rx, uint bytes) +{ + switch (buswidth) { + case 1: + return adi_spi_fifo_1x_xfer(priv, tx, rx, bytes); + case 2: + return adi_spi_fifo_mio_xfer(priv, tx, rx, bytes, SPI_CTL_MIO_DUAL); + case 4: + return adi_spi_fifo_mio_xfer(priv, tx, rx, bytes, SPI_CTL_MIO_QUAD); + default: + return -ENOTSUPP; + } +} + +static int adi_spi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct udevice *bus = dev->parent; + struct adi_spi_priv *priv = dev_get_priv(bus); + struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev); + + const u8 *tx = dout; + u8 *rx = din; + uint bytes = bitlen / 8; + int ret = 0; + + debug("%s: bus_num:%i cs:%i bitlen:%i bytes:%i flags:%lx\n", __func__, + priv->bus_num, slave_plat->cs[0], bitlen, bytes, flags); + + if (flags & SPI_XFER_BEGIN) + adi_spi_cs_activate(priv, slave_plat); + + if (bitlen == 0) + goto done; + + /* we can only do 8 bit transfers */ + if (bitlen % 8) { + flags |= SPI_XFER_END; + goto done; + } + + /* Discard invalid rx data and empty rfifo */ + discard_rx_fifo_contents(priv->regs); + + ret = adi_spi_fifo_1x_xfer(priv, tx, rx, bytes); + + done: + if (flags & SPI_XFER_END) + adi_spi_cs_deactivate(priv, slave_plat); + + return ret; +} + +static int adi_spi_set_speed(struct udevice *bus, uint speed) +{ + struct adi_spi_platdata *plat = dev_get_plat(bus); + struct adi_spi_priv *priv = dev_get_priv(bus); + int ret; + u32 clock, spi_base_clk; + struct clk spi_clk; + + ret = clk_get_by_name(bus, "spi", &spi_clk); + if (ret < 0) { + dev_err(bus, "Can't get SPI clk: %d\n", ret); + return ret; + } + spi_base_clk = clk_get_rate(&spi_clk); + + if (speed > plat->max_hz) + speed = plat->max_hz; + + if (speed > spi_base_clk) + return -ENODEV; + + clock = spi_base_clk / speed; + if (clock) + clock--; + + priv->clock = clock; + + debug("%s: priv->clock: %x, speed: %x, get_spi_clk(): %x\n", + __func__, clock, speed, spi_base_clk); + + return 0; +} + +static int adi_spi_set_mode(struct udevice *bus, uint mode) +{ + struct adi_spi_priv *priv = dev_get_priv(bus); + u32 reg; + + reg = SPI_CTL_EN | SPI_CTL_MSTR; + if (mode & SPI_CPHA) + reg |= SPI_CTL_CPHA; + if (mode & SPI_CPOL) + reg |= SPI_CTL_CPOL; + if (mode & SPI_LSB_FIRST) + reg |= SPI_CTL_LSBF; + reg &= ~SPI_CTL_ASSEL; + + priv->control = reg; + + debug("%s: control=%d, cs_pol=%d\n", __func__, reg, mode & SPI_CS_HIGH ? 1 : 0); + + return 0; +} + +/** + * U-boot's version of spi-mem does not support mixed bus-width + * commands nor anything more than 1x mode. + * Using a custom exec_op implementation, we can support it. + */ +static int adi_spi_mem_exec_op(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + int rv = 0; + struct udevice *bus = slave->dev->parent; + struct adi_spi_priv *priv = dev_get_priv(bus); + struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(slave->dev); + u8 tmpbuf[64]; + int i; + + if ((op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes) > + sizeof(tmpbuf)) + return -ENOMEM; + + for (i = 0; i < op->cmd.nbytes; i++) + tmpbuf[i] = op->cmd.opcode >> + (8 * (op->cmd.nbytes - i - 1)); + for (i = 0; i < op->addr.nbytes; i++) + tmpbuf[i + op->cmd.nbytes] = op->addr.val >> + (8 * (op->addr.nbytes - i - 1)); + memset(tmpbuf + op->addr.nbytes + op->cmd.nbytes, 0xff, + op->dummy.nbytes); + + adi_spi_cs_activate(priv, slave_plat); + discard_rx_fifo_contents(priv->regs); + + if (op->cmd.nbytes) { + rv = adi_spi_fifo_xfer(priv, op->cmd.buswidth, + tmpbuf, NULL, op->cmd.nbytes); + if (rv != 0) + goto cleanup; + } + + if (op->addr.nbytes) { + rv = adi_spi_fifo_xfer(priv, op->addr.buswidth, + tmpbuf + op->cmd.nbytes, NULL, + op->addr.nbytes); + if (rv != 0) + goto cleanup; + } + + if (op->dummy.nbytes) { + rv = adi_spi_fifo_xfer(priv, op->dummy.buswidth, + tmpbuf + op->cmd.nbytes + + op->addr.nbytes, + NULL, op->dummy.nbytes); + if (rv != 0) + goto cleanup; + } + + if (op->data.dir == SPI_MEM_DATA_IN) + rv = adi_spi_fifo_xfer(priv, op->data.buswidth, + NULL, op->data.buf.in, + op->data.nbytes); + else if (op->data.dir == SPI_MEM_DATA_OUT) + rv = adi_spi_fifo_xfer(priv, op->data.buswidth, + op->data.buf.out, NULL, + op->data.nbytes); + +cleanup: + adi_spi_cs_deactivate(priv, slave_plat); + return rv; +} + +static const struct spi_controller_mem_ops adi_spi_mem_ops = { + .exec_op = adi_spi_mem_exec_op, +}; + +static const struct dm_spi_ops adi_spi_ops = { + .claim_bus = adi_spi_claim_bus, + .release_bus = adi_spi_release_bus, + .xfer = adi_spi_xfer, + .set_speed = adi_spi_set_speed, + .set_mode = adi_spi_set_mode, + .cs_info = adi_spi_cs_info, + .mem_ops = &adi_spi_mem_ops, +}; + +static const struct udevice_id adi_spi_ids[] = { + { .compatible = "adi,spi3" }, + { } +}; + +U_BOOT_DRIVER(adi_spi3) = { + .name = "adi_spi3", + .id = UCLASS_SPI, + .of_match = adi_spi_ids, + .ops = &adi_spi_ops, + .of_to_plat = adi_spi_of_to_plat, + .probe = adi_spi_probe, + .remove = adi_spi_remove, + .plat_auto = sizeof(struct adi_spi_platdata), + .priv_auto = sizeof(struct adi_spi_priv), + .per_child_auto = sizeof(struct spi_slave), +}; From 036118ebd81e6b2c593d5ea6dc0056b34931c870 Mon Sep 17 00:00:00 2001 From: Nathan Barrett-Morrison Date: Wed, 26 Feb 2025 12:30:34 -0500 Subject: [PATCH 12/12] mmc: Add support for ADI SC5XX-family processor SDHCI peripherals Co-developed-by: Greg Malysa Signed-off-by: Greg Malysa Co-developed-by: Ian Roberts Signed-off-by: Ian Roberts Signed-off-by: Vasileios Bimpikas Signed-off-by: Utsav Agarwal Signed-off-by: Arturs Artamonovs Signed-off-by: Oliver Gaskell Signed-off-by: Nathan Barrett-Morrison --- MAINTAINERS | 1 + drivers/mmc/Kconfig | 9 +++ drivers/mmc/Makefile | 1 + drivers/mmc/adi_sdhci.c | 148 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 drivers/mmc/adi_sdhci.c diff --git a/MAINTAINERS b/MAINTAINERS index 889695c1edd..31adefd9f9c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -636,6 +636,7 @@ F: drivers/dma/adi_dma.c F: drivers/gpio/adp5588_gpio.c F: drivers/gpio/gpio-adi-adsp.c F: drivers/i2c/adi_i2c.c +F: drivers/mmc/adi_sdhci.c F: drivers/net/dwc_eth_qos_adi.c F: drivers/pinctrl/pinctrl-adi-adsp.c F: drivers/remoteproc/adi_sc5xx_rproc.c diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index 4827834b4aa..ab56bd3939f 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -292,6 +292,15 @@ config MMC_DW_ROCKCHIP SD 3.0, SDIO 3.0 and MMC 4.5 and supports common eMMC chips as well as removeable SD and micro-SD cards. +config MMC_SDHCI_ADI + bool "ADI SD/MMC controller support" + depends on ARCH_SC5XX + depends on DM_MMC && OF_CONTROL + depends on MMC_SDHCI && MMC_SDHCI_ADMA + help + This enables support for the SD/MMC controller included in some Analog + Devices SC5XX Socs. + config MMC_DW_SOCFPGA bool "SOCFPGA specific extensions for Synopsys DW Memory Card Interface" depends on ARCH_SOCFPGA diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 90e76f90769..94ed28ead71 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_MMC_SDHCI_MV) += mv_sdhci.o obj-$(CONFIG_MMC_SDHCI_NPCM) += npcm_sdhci.o obj-$(CONFIG_MMC_SDHCI_PIC32) += pic32_sdhci.o obj-$(CONFIG_MMC_SDHCI_ROCKCHIP) += rockchip_sdhci.o +obj-$(CONFIG_MMC_SDHCI_ADI) += adi_sdhci.o obj-$(CONFIG_MMC_SDHCI_S5P) += s5p_sdhci.o obj-$(CONFIG_MMC_SDHCI_SNPS) += snps_sdhci.o obj-$(CONFIG_MMC_SDHCI_STI) += sti_sdhci.o diff --git a/drivers/mmc/adi_sdhci.c b/drivers/mmc/adi_sdhci.c new file mode 100644 index 00000000000..65a22cefb71 --- /dev/null +++ b/drivers/mmc/adi_sdhci.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (C) Copyright 2022 - Analog Devices, Inc. + * + * Written and/or maintained by Timesys Corporation + * + * Contact: Nathan Barrett-Morrison + * Contact: Greg Malysa + * + * Based on Rockchip's sdhci.c file + */ + +#include +#include +#include +#include +#include + +/* 400KHz is max freq for card ID etc. Use that as min */ +#define EMMC_MIN_FREQ 400000 + +/* Check if an operation crossed a boundary of size ADMA_BOUNDARY_ALIGN */ +#define ADMA_BOUNDARY_ALGN SZ_128M +#define BOUNDARY_OK(addr, len) \ + (((addr) | (ADMA_BOUNDARY_ALGN - 1)) == (((addr) + (len) - 1) | \ + (ADMA_BOUNDARY_ALGN - 1))) + +/* We split a descriptor for every crossing of the ADMA alignment boundary, + * so we need an additional descriptor for every expected crossing. + * As I understand it, the max expected transaction size is: + * CONFIG_SYS_MMC_MAX_BLK_COUNT * MMC_MAX_BLOCK_LEN + * + * With the way the SDHCI-ADMA driver is implemented, if ADMA_MAX_LEN was a + * clean power of two, we'd only ever need +1 descriptor as the first + * descriptor that got split would then bring the remaining DMA + * destination addresses into alignment. Unfortunately, it's currently + * hardcoded to a non-power-of-two value. + * + * If that ever becomes parameterized, ADMA max length can be set to + * 0x10000, and set this to 1. + */ +#define ADMA_POTENTIAL_CROSSINGS \ + DIV_ROUND_UP((CONFIG_SYS_MMC_MAX_BLK_COUNT * MMC_MAX_BLOCK_LEN), \ + ADMA_BOUNDARY_ALGN) +/* +1 descriptor for each crossing. + */ +#define ADMA_TABLE_EXTRA_SZ (ADMA_POTENTIAL_CROSSINGS * ADMA_DESC_LEN) + +struct adi_sdhc_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +void adi_dwcmshc_adma_write_desc(struct sdhci_host *host, void **desc, + dma_addr_t addr, int len, bool end) +{ + int tmplen, offset; + + if (likely(!len || BOUNDARY_OK(addr, len))) { + sdhci_adma_write_desc(host, desc, addr, len, end); + return; + } + + offset = addr & (ADMA_BOUNDARY_ALGN - 1); + tmplen = ADMA_BOUNDARY_ALGN - offset; + sdhci_adma_write_desc(host, desc, addr, tmplen, false); + + addr += tmplen; + len -= tmplen; + sdhci_adma_write_desc(host, desc, addr, len, end); +} + +struct sdhci_ops adi_dwcmshc_sdhci_ops = { + .adma_write_desc = adi_dwcmshc_adma_write_desc, +}; + +static int adi_dwcmshc_sdhci_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct adi_sdhc_plat *plat = dev_get_plat(dev); + struct sdhci_host *host = dev_get_priv(dev); + int max_frequency, ret; + struct clk clk; + + max_frequency = dev_read_u32_default(dev, "max-frequency", 0); + ret = clk_get_by_index(dev, 0, &clk); + + host->quirks = 0; + host->max_clk = max_frequency; + /* + * The sdhci-driver only supports 4bit and 8bit, as sdhci_setup_cfg + * doesn't allow us to clear MMC_MODE_4BIT. Consequently, we don't + * check for other bus-width values. + */ + if (host->bus_width == 8) + host->host_caps |= MMC_MODE_8BIT; + + host->mmc = &plat->mmc; + host->mmc->priv = host; + host->mmc->dev = dev; + upriv->mmc = host->mmc; + + host->ops = &adi_dwcmshc_sdhci_ops; + host->adma_desc_table = memalign(ARCH_DMA_MINALIGN, + ADMA_TABLE_SZ + ADMA_TABLE_EXTRA_SZ); + host->adma_addr = virt_to_phys(host->adma_desc_table); + + ret = sdhci_setup_cfg(&plat->cfg, host, 0, EMMC_MIN_FREQ); + if (ret) + return ret; + + return sdhci_probe(dev); +} + +static int adi_dwcmshc_sdhci_of_to_plat(struct udevice *dev) +{ + struct sdhci_host *host = dev_get_priv(dev); + + host->name = dev->name; + host->ioaddr = dev_read_addr_ptr(dev); + host->bus_width = dev_read_u32_default(dev, "bus-width", 4); + + return 0; +} + +static int adi_sdhci_bind(struct udevice *dev) +{ + struct adi_sdhc_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id adi_dwcmshc_sdhci_ids[] = { + { .compatible = "adi,dwc-sdhci" }, + { } +}; + +U_BOOT_DRIVER(adi_dwcmshc_sdhci_drv) = { + .name = "adi_sdhci", + .id = UCLASS_MMC, + .of_match = adi_dwcmshc_sdhci_ids, + .of_to_plat = adi_dwcmshc_sdhci_of_to_plat, + .ops = &sdhci_ops, + .bind = adi_sdhci_bind, + .probe = adi_dwcmshc_sdhci_probe, + .priv_auto = sizeof(struct sdhci_host), + .plat_auto = sizeof(struct adi_sdhc_plat), +};