[U-Boot] [RFC PATCH 00/11] Add support for 96boards Dragonboard410C board

Hi All,
With a slight delay (Sorry Syed) I'm finally ready to show code I've been working recently on.
This series add support for: - New board - 96board Dragonboard 410C (ARMv8) - Qualcomm snapdragon architecture (for now only single SoC) - Some Qualcomm peripherals (UART, GPIO, PMIC including bus, SDHCI, USB...) - New bus (SPMI) - It also adds minor function to ehci HCD
Please take a look at it and give me feedback what can be changed so it will be ready for submission.
I'd like to thank Przemyslaw Marczak for doing initial reviews and helping me figure out what is the best approach in some cases.
Notes/Design decisions I made/Issues I have/had (please comment them as well) 0) If I missed someone on CC - please let me know; If I misused patman - apologize for that - It's my first time with patman.
1) Documentation is missing - dt-bindings, spmi bus documentation. I left it as I expect some redesign after your review.
2) I tried to use DM and Kconfig where possible, but some drivers (USB) depend on subsystems that don't support Kconfig yet
3) There are 3 long-line warnings and some complains about too short Kconfig - is it ok to ignore lines that are 1-2 characters longer (and making them shorter will cause code to be less readable)?
4) Licensing (@trini) My code is based on 2 trees: Linux: https://git.linaro.org/landing-teams/working/qualcomm/kernel.git LittleKernel: https://git.linaro.org/people/nicolas.dechesne/lk.git One is GPLv2, other BSD(-clause3 I think). When tried to figure out how to write drivers, I looked at both drivers, and even if some drivers are totally different than their originals, they are derivative work. I decided to use Linux license where possible (where I was almost sure I copied/analyzed Linux drivers), left BSD only for SPMI code. Is it ok? Or should I run tool like Protex and drop references where code no longer matches linux/lk codebase (not that I have Protex at home)
5) Clock mapping - I need advice on how to properly access clock controller from devices that need some clocks to be enabled. I want clock controller code to be as simple as possible, but am not sure how to add links to a specific clocks.
6) SPMI: I decided to add new bus. Contrary to other vendors, Qualcomm SoCs use SPMI to talk with PMICs. This is "closed" bus (you need to pay membership to get specification), and I need it to properly describe pmics inside device tree.
7) Snapdragon mach: I added new MACH as my plan is to support more than one Qualcomm SoC/board - hopefully days where U-Boot had no Qualcomm support will be over soon :)
8) I didn't modified Maintainers files - will do that for PATCH v1
9) I moved register offsets to driver files (didn't put them in one big header file. I know it's not common but IMO it's better if drivers are self contained (and there are not many registers anyways).
10) Compilation causes 8 warnings, none in my code: - 1x in asix88179 driver (format %u expects...) - 7x in ulpi-viewport code The problem is - this drivers are handling poorly ARMv8 processors, where sizeof(int) < sizeof(void*) and sizeof(int) < sizeof(long). Fix for asix would be trivial, with ULPI it's worse as in my opinion it needs slight redesign (use phaddr_t or something). Is it ok if I redesign that? Or just ignore warnings or write my own ulpi code (I wanted to avoid code duplication)
Best Regards, Mateusz
Mateusz Kulikowski (11): serial: Add support for Qualcomm serial port gpio: Add support for Qualcomm gpio controller mmc: Add support for Qualcomm SDHCI controller ehci-hcd: Add init_after_reset ehci: Add support for Qualcomm EHCI drivers: Add SPMI bus uclass drivers: spmi: Add support for Qualcomm SPMI bus driver pmic: Add support for Qualcomm PM8916 PMIC gpio: Add support for Qualcomm PM8916 gpios arm: Add support for Qualcomm Snapdragon family board: Add Qualcomm Dragonboard 410C support
arch/arm/Kconfig | 12 + arch/arm/Makefile | 1 + arch/arm/dts/Makefile | 2 + arch/arm/dts/dragonboard410c.dts | 157 +++++++++++ arch/arm/mach-snapdragon/Kconfig | 15 + arch/arm/mach-snapdragon/Makefile | 8 + arch/arm/mach-snapdragon/clock-apq8016.c | 262 ++++++++++++++++++ arch/arm/mach-snapdragon/include/mach/gpio.h | 25 ++ .../mach-snapdragon/include/mach/sysmap-apq8016.h | 15 + arch/arm/mach-snapdragon/reset.c | 40 +++ board/qualcomm/dragonboard410c/Kconfig | 15 + board/qualcomm/dragonboard410c/Makefile | 8 + board/qualcomm/dragonboard410c/dragonboard410c.c | 111 ++++++++ board/qualcomm/dragonboard410c/head.S | 20 ++ board/qualcomm/dragonboard410c/readme.txt | 40 +++ board/qualcomm/dragonboard410c/u-boot.lds | 90 ++++++ configs/dragonboard410c_defconfig | 29 ++ drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/gpio/Kconfig | 14 + drivers/gpio/Makefile | 3 +- drivers/gpio/msm_gpio.c | 115 ++++++++ drivers/gpio/pm8916_gpio.c | 306 +++++++++++++++++++++ drivers/mmc/Kconfig | 6 + drivers/mmc/Makefile | 1 + drivers/mmc/msm_sdhci.c | 149 ++++++++++ drivers/power/pmic/Kconfig | 14 + drivers/power/pmic/Makefile | 1 + drivers/power/pmic/pm8916.c | 92 +++++++ drivers/serial/Kconfig | 5 + drivers/serial/Makefile | 1 + drivers/serial/serial_msm.c | 204 ++++++++++++++ drivers/spmi/Kconfig | 16 ++ drivers/spmi/Makefile | 8 + drivers/spmi/spmi-msm.c | 183 ++++++++++++ drivers/spmi/spmi-uclass.c | 53 ++++ drivers/usb/host/Kconfig | 8 + drivers/usb/host/Makefile | 1 + drivers/usb/host/ehci-hcd.c | 6 + drivers/usb/host/ehci-msm.c | 198 +++++++++++++ drivers/usb/host/ehci.h | 1 + include/configs/dragonboard410c.h | 184 +++++++++++++ include/dm/uclass-id.h | 1 + include/spmi/spmi.h | 44 +++ 44 files changed, 2466 insertions(+), 1 deletion(-) create mode 100644 arch/arm/dts/dragonboard410c.dts create mode 100644 arch/arm/mach-snapdragon/Kconfig create mode 100644 arch/arm/mach-snapdragon/Makefile create mode 100644 arch/arm/mach-snapdragon/clock-apq8016.c create mode 100644 arch/arm/mach-snapdragon/include/mach/gpio.h create mode 100644 arch/arm/mach-snapdragon/include/mach/sysmap-apq8016.h create mode 100644 arch/arm/mach-snapdragon/reset.c create mode 100644 board/qualcomm/dragonboard410c/Kconfig create mode 100644 board/qualcomm/dragonboard410c/Makefile create mode 100644 board/qualcomm/dragonboard410c/dragonboard410c.c create mode 100644 board/qualcomm/dragonboard410c/head.S create mode 100644 board/qualcomm/dragonboard410c/readme.txt create mode 100644 board/qualcomm/dragonboard410c/u-boot.lds create mode 100644 configs/dragonboard410c_defconfig create mode 100644 drivers/gpio/msm_gpio.c create mode 100644 drivers/gpio/pm8916_gpio.c create mode 100644 drivers/mmc/msm_sdhci.c create mode 100644 drivers/power/pmic/pm8916.c create mode 100644 drivers/serial/serial_msm.c create mode 100644 drivers/spmi/Kconfig create mode 100644 drivers/spmi/Makefile create mode 100644 drivers/spmi/spmi-msm.c create mode 100644 drivers/spmi/spmi-uclass.c create mode 100644 drivers/usb/host/ehci-msm.c create mode 100644 include/configs/dragonboard410c.h create mode 100644 include/spmi/spmi.h

This driver works in "new" Data Mover UART mode, so will be compatible with modern Qualcomm chips only.
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com ---
drivers/serial/Kconfig | 5 ++ drivers/serial/Makefile | 1 + drivers/serial/serial_msm.c | 204 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 drivers/serial/serial_msm.c
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 1fc287e..f28c093 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -223,4 +223,9 @@ config UNIPHIER_SERIAL If you have a UniPhier based board and want to use the on-chip serial ports, say Y to this option. If unsure, say N.
+config MSM_SERIAL + bool "Qualcomm on-chip UART" + depends on DM_SERIAL + help + Support Data Mover UART used on Qualcomm Snapdragon SoCs. endmenu diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index dd87147..d052175 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_MXS_AUART) += mxs_auart.o obj-$(CONFIG_ARC_SERIAL) += serial_arc.o obj-$(CONFIG_UNIPHIER_SERIAL) += serial_uniphier.o obj-$(CONFIG_STM32_SERIAL) += serial_stm32.o +obj-$(CONFIG_MSM_SERIAL) += serial_msm.o
ifndef CONFIG_SPL_BUILD obj-$(CONFIG_USB_TTY) += usbtty.o diff --git a/drivers/serial/serial_msm.c b/drivers/serial/serial_msm.c new file mode 100644 index 0000000..13dad33 --- /dev/null +++ b/drivers/serial/serial_msm.c @@ -0,0 +1,204 @@ +/* + * Qualcomm UART driver + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + * + * UART will work in Data Mover mode. + * Based on Linux driver. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <asm/io.h> +#include <common.h> +#include <dm.h> +#include <clk.h> +#include <errno.h> +#include <linux/compiler.h> +#include <serial.h> +#include <watchdog.h> + +/* Serial registers - this driver works in uartdm mode*/ + +#define UARTDM_DMRX 0x34 /* Max RX transfer length */ +#define UARTDM_NCF_TX 0x40 /* Number of chars to TX */ + +#define UARTDM_RXFS 0x50 /* RX channel status register */ +#define UARTDM_RXFS_BUF_SHIFT 0x7 /* Number of bytes in the packing buffer */ +#define UARTDM_RXFS_BUF_MASK 0x7 + +#define UARTDM_SR 0xA4 /* Status register */ +#define UARTDM_SR_RX_READY (1 << 0) /* Word is the receiver FIFO */ +#define UARTDM_SR_TX_EMPTY (1 << 3) /* Transmitter underrun */ +#define UARTDM_SR_UART_OVERRUN (1 << 4) /* Receive overrun */ + +#define UARTDM_CR 0xA8 /* Command register */ +#define UARTDM_CR_CMD_RESET_ERR (3 << 4) /* Clear overrun error */ +#define UARTDM_CR_CMD_RESET_STALE_INT (8 << 4) /* Clears stale irq */ +#define UARTDM_CR_CMD_RESET_TX_READY (3 << 8) /* Clears TX Ready irq*/ +#define UARTDM_CR_CMD_FORCE_STALE (4 << 8) /* Causes stale event */ +#define UARTDM_CR_CMD_STALE_EVENT_DISABLE (6 << 8) /* Disable stale event */ + +#define UARTDM_IMR 0xB0 /* Interrupt mask register */ +#define UARTDM_ISR 0xB4 /* Interrupt status register */ +#define UARTDM_ISR_TX_READY 0x80 /* TX FIFO empty */ + +#define UARTDM_TF 0x100 /* UART Transmit FIFO register */ +#define UARTDM_RF 0x140 /* UART Receive FIFO register */ + + +DECLARE_GLOBAL_DATA_PTR; + +struct msm_serial_data { + phys_addr_t base; + unsigned chars_cnt; /* number of buffered chars */ + uint32_t chars_buf; /* buffered chars */ +}; + +static int msm_serial_getc(struct udevice *dev) +{ + struct msm_serial_data *p = dev_get_priv(dev); + unsigned sr; + char c; + + /* There was something buffered */ + if (p->chars_cnt) { + c = p->chars_buf & 0xFF; + p->chars_buf >>= 8; + p->chars_cnt--; + return c; + } + + /* Clear error in case of buffer overrun */ + if (readl(p->base + UARTDM_SR) & UARTDM_SR_UART_OVERRUN) + writel(UARTDM_CR_CMD_RESET_ERR, p->base + UARTDM_CR); + + /* We need to fetch new character */ + sr = readl(p->base + UARTDM_SR); + + /* There are at least 4 bytes in fifo */ + if (sr & UARTDM_SR_RX_READY) { + p->chars_buf = readl(p->base + UARTDM_RF); + c = p->chars_buf & 0xFF; + p->chars_cnt = 3; /* 4 - one read */ + p->chars_buf >>= 8; + return c; + } + + /* Check if there is anything in fifo */ + p->chars_cnt = readl(p->base + UARTDM_RXFS); + /* Extract number of characters in UART packing buffer */ + p->chars_cnt = (p->chars_cnt >> UARTDM_RXFS_BUF_SHIFT) & + UARTDM_RXFS_BUF_MASK; + if (!p->chars_cnt) + return -EAGAIN; + + /* There is at least one charcter, move it to fifo */ + writel(UARTDM_CR_CMD_FORCE_STALE, p->base + UARTDM_CR); + + p->chars_buf = readl(p->base + UARTDM_RF); + writel(UARTDM_CR_CMD_RESET_STALE_INT, p->base + UARTDM_CR); + writel(0xFFFFFF, p->base + UARTDM_DMRX); + + c = p->chars_buf & 0xFF; + p->chars_buf >>= 8; + p->chars_cnt--; + + return c; +} + +static int msm_serial_putc(struct udevice *dev, const char ch) +{ + struct msm_serial_data *p = dev_get_priv(dev); + + if (!(readl(p->base + UARTDM_SR) & UARTDM_SR_TX_EMPTY) && + !(readl(p->base + UARTDM_ISR) & UARTDM_ISR_TX_READY)) + return -EAGAIN; + + writel(UARTDM_CR_CMD_RESET_TX_READY, p->base + UARTDM_CR); + + writel(1, p->base + UARTDM_NCF_TX); + writel(ch, p->base + UARTDM_TF); + return 0; +} + +static int msm_serial_pending(struct udevice *dev, bool input) +{ + struct msm_serial_data *p = dev_get_priv(dev); + + if (input) { + if (p->chars_cnt) + return 1; + if (readl(p->base + UARTDM_SR) & UARTDM_SR_RX_READY) + return 1; + if (readl(p->base + UARTDM_RXFS)) + return 1; + } + return 0; +} + +static const struct dm_serial_ops msm_serial_ops = { + .putc = msm_serial_putc, + .pending = msm_serial_pending, + .getc = msm_serial_getc, +}; + +static int msm_uart_clk_init(struct udevice *dev) +{ + uint clk_rate = fdtdec_get_uint(gd->fdt_blob, dev->of_offset, + "clock-frequency", 115200); + uint clkd[2]; /* clk_id and clk_no */ + fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, "clock", clkd, 2); + clkd[0] = fdt_node_offset_by_phandle(gd->fdt_blob, clkd[0]); + + struct udevice *clk = NULL; + uclass_get_device_by_of_offset(UCLASS_CLK, clkd[0], &clk); + if (clk) + clk_set_periph_rate(clk, clkd[1], clk_rate); + + return 0; +} + +static int msm_serial_probe(struct udevice *dev) +{ + struct msm_serial_data *p = dev_get_priv(dev); + + msm_uart_clk_init(dev); + + if (readl(p->base + UARTDM_SR) & UARTDM_SR_UART_OVERRUN) + writel(UARTDM_CR_CMD_RESET_ERR, p->base + UARTDM_CR); + + writel(0, p->base + UARTDM_IMR); + writel(UARTDM_CR_CMD_STALE_EVENT_DISABLE, p->base + UARTDM_CR); + writel(0xFFFFFF, p->base + UARTDM_DMRX); + + p->chars_buf = 0; + p->chars_cnt = 0; + return 0; +} + +static int msm_serial_ofdata_to_platdata(struct udevice *dev) +{ + struct msm_serial_data *p = dev_get_priv(dev); + + p->base = dev_get_addr(dev); + if (p->base == FDT_ADDR_T_NONE) + return -EINVAL; + return 0; +} + +static const struct udevice_id msm_serial_ids[] = { + { .compatible = "qcom,msm-uartdm-v1.4" }, + { } +}; + +U_BOOT_DRIVER(serial_msm) = { + .name = "serial_msm", + .id = UCLASS_SERIAL, + .of_match = msm_serial_ids, + .ofdata_to_platdata = msm_serial_ofdata_to_platdata, + .priv_auto_alloc_size = sizeof(struct msm_serial_data), + .probe = msm_serial_probe, + .ops = &msm_serial_ops, + .flags = DM_FLAG_PRE_RELOC, +};

Hi Mateusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
This driver works in "new" Data Mover UART mode, so will be compatible with modern Qualcomm chips only.
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com
drivers/serial/Kconfig | 5 ++ drivers/serial/Makefile | 1 + drivers/serial/serial_msm.c | 204 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 drivers/serial/serial_msm.c
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 1fc287e..f28c093 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -223,4 +223,9 @@ config UNIPHIER_SERIAL If you have a UniPhier based board and want to use the on-chip serial ports, say Y to this option. If unsure, say N.
+config MSM_SERIAL
bool "Qualcomm on-chip UART"
depends on DM_SERIAL
help
Support Data Mover UART used on Qualcomm Snapdragon SoCs.
Any more details? What baud rates does it support? What SoCs use it?
endmenu diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index dd87147..d052175 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_MXS_AUART) += mxs_auart.o obj-$(CONFIG_ARC_SERIAL) += serial_arc.o obj-$(CONFIG_UNIPHIER_SERIAL) += serial_uniphier.o obj-$(CONFIG_STM32_SERIAL) += serial_stm32.o +obj-$(CONFIG_MSM_SERIAL) += serial_msm.o
ifndef CONFIG_SPL_BUILD obj-$(CONFIG_USB_TTY) += usbtty.o diff --git a/drivers/serial/serial_msm.c b/drivers/serial/serial_msm.c new file mode 100644 index 0000000..13dad33 --- /dev/null +++ b/drivers/serial/serial_msm.c @@ -0,0 +1,204 @@ +/*
- Qualcomm UART driver
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- UART will work in Data Mover mode.
- Based on Linux driver.
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <asm/io.h> +#include <common.h> +#include <dm.h> +#include <clk.h> +#include <errno.h> +#include <linux/compiler.h> +#include <serial.h> +#include <watchdog.h>
Ordering:
common.h clk.h dm.h errno.h serial.h watchdog.h asm/ linux/
+/* Serial registers - this driver works in uartdm mode*/
+#define UARTDM_DMRX 0x34 /* Max RX transfer length */ +#define UARTDM_NCF_TX 0x40 /* Number of chars to TX */
+#define UARTDM_RXFS 0x50 /* RX channel status register */ +#define UARTDM_RXFS_BUF_SHIFT 0x7 /* Number of bytes in the packing buffer */ +#define UARTDM_RXFS_BUF_MASK 0x7
+#define UARTDM_SR 0xA4 /* Status register */ +#define UARTDM_SR_RX_READY (1 << 0) /* Word is the receiver FIFO */ +#define UARTDM_SR_TX_EMPTY (1 << 3) /* Transmitter underrun */ +#define UARTDM_SR_UART_OVERRUN (1 << 4) /* Receive overrun */
+#define UARTDM_CR 0xA8 /* Command register */ +#define UARTDM_CR_CMD_RESET_ERR (3 << 4) /* Clear overrun error */ +#define UARTDM_CR_CMD_RESET_STALE_INT (8 << 4) /* Clears stale irq */ +#define UARTDM_CR_CMD_RESET_TX_READY (3 << 8) /* Clears TX Ready irq*/ +#define UARTDM_CR_CMD_FORCE_STALE (4 << 8) /* Causes stale event */ +#define UARTDM_CR_CMD_STALE_EVENT_DISABLE (6 << 8) /* Disable stale event */
+#define UARTDM_IMR 0xB0 /* Interrupt mask register */ +#define UARTDM_ISR 0xB4 /* Interrupt status register */ +#define UARTDM_ISR_TX_READY 0x80 /* TX FIFO empty */
+#define UARTDM_TF 0x100 /* UART Transmit FIFO register */ +#define UARTDM_RF 0x140 /* UART Receive FIFO register */
+DECLARE_GLOBAL_DATA_PTR;
+struct msm_serial_data {
phys_addr_t base;
unsigned chars_cnt; /* number of buffered chars */
uint32_t chars_buf; /* buffered chars */
+};
+static int msm_serial_getc(struct udevice *dev) +{
struct msm_serial_data *p = dev_get_priv(dev);
unsigned sr;
char c;
/* There was something buffered */
if (p->chars_cnt) {
c = p->chars_buf & 0xFF;
p->chars_buf >>= 8;
p->chars_cnt--;
return c;
}
/* Clear error in case of buffer overrun */
if (readl(p->base + UARTDM_SR) & UARTDM_SR_UART_OVERRUN)
writel(UARTDM_CR_CMD_RESET_ERR, p->base + UARTDM_CR);
/* We need to fetch new character */
sr = readl(p->base + UARTDM_SR);
/* There are at least 4 bytes in fifo */
if (sr & UARTDM_SR_RX_READY) {
p->chars_buf = readl(p->base + UARTDM_RF);
c = p->chars_buf & 0xFF;
p->chars_cnt = 3; /* 4 - one read */
p->chars_buf >>= 8;
return c;
}
/* Check if there is anything in fifo */
p->chars_cnt = readl(p->base + UARTDM_RXFS);
/* Extract number of characters in UART packing buffer */
p->chars_cnt = (p->chars_cnt >> UARTDM_RXFS_BUF_SHIFT) &
UARTDM_RXFS_BUF_MASK;
if (!p->chars_cnt)
return -EAGAIN;
/* There is at least one charcter, move it to fifo */
writel(UARTDM_CR_CMD_FORCE_STALE, p->base + UARTDM_CR);
p->chars_buf = readl(p->base + UARTDM_RF);
writel(UARTDM_CR_CMD_RESET_STALE_INT, p->base + UARTDM_CR);
writel(0xFFFFFF, p->base + UARTDM_DMRX);
c = p->chars_buf & 0xFF;
p->chars_buf >>= 8;
p->chars_cnt--;
Can you not rationalise this code a bit? E.g.
if (no chars in fifo) { try to get some } if (no chars in fifo) return -EAGAIN extract char from fifo return ch;
You seem to have three copies of the same code.
return c;
+}
+static int msm_serial_putc(struct udevice *dev, const char ch) +{
struct msm_serial_data *p = dev_get_priv(dev);
if (!(readl(p->base + UARTDM_SR) & UARTDM_SR_TX_EMPTY) &&
!(readl(p->base + UARTDM_ISR) & UARTDM_ISR_TX_READY))
return -EAGAIN;
writel(UARTDM_CR_CMD_RESET_TX_READY, p->base + UARTDM_CR);
writel(1, p->base + UARTDM_NCF_TX);
writel(ch, p->base + UARTDM_TF);
return 0;
+}
+static int msm_serial_pending(struct udevice *dev, bool input) +{
struct msm_serial_data *p = dev_get_priv(dev);
if (input) {
if (p->chars_cnt)
return 1;
if (readl(p->base + UARTDM_SR) & UARTDM_SR_RX_READY)
return 1;
if (readl(p->base + UARTDM_RXFS))
return 1;
}
return 0;
+}
+static const struct dm_serial_ops msm_serial_ops = {
.putc = msm_serial_putc,
.pending = msm_serial_pending,
.getc = msm_serial_getc,
+};
+static int msm_uart_clk_init(struct udevice *dev) +{
uint clk_rate = fdtdec_get_uint(gd->fdt_blob, dev->of_offset,
"clock-frequency", 115200);
uint clkd[2]; /* clk_id and clk_no */
blank line
fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, "clock", clkd, 2);
check return value and maybe return -EINVAL
clkd[0] = fdt_node_offset_by_phandle(gd->fdt_blob, clkd[0]);
struct udevice *clk = NULL;
Put this decl at the top. Also do you need to assign to NULL?
uclass_get_device_by_of_offset(UCLASS_CLK, clkd[0], &clk);
Check return value. -ENODEV means there is no clk. Is it OK to have no clock?
if (clk)
clk_set_periph_rate(clk, clkd[1], clk_rate);
If is OK to
return 0;
+}
+static int msm_serial_probe(struct udevice *dev) +{
struct msm_serial_data *p = dev_get_priv(dev);
msm_uart_clk_init(dev);
if (readl(p->base + UARTDM_SR) & UARTDM_SR_UART_OVERRUN)
writel(UARTDM_CR_CMD_RESET_ERR, p->base + UARTDM_CR);
writel(0, p->base + UARTDM_IMR);
writel(UARTDM_CR_CMD_STALE_EVENT_DISABLE, p->base + UARTDM_CR);
writel(0xFFFFFF, p->base + UARTDM_DMRX);
p->chars_buf = 0;
p->chars_cnt = 0;
No need for the above two lines. Private data is set to 0 by driver model.
return 0;
+}
+static int msm_serial_ofdata_to_platdata(struct udevice *dev) +{
struct msm_serial_data *p = dev_get_priv(dev);
p->base = dev_get_addr(dev);
if (p->base == FDT_ADDR_T_NONE)
return -EINVAL;
return 0;
+}
+static const struct udevice_id msm_serial_ids[] = {
{ .compatible = "qcom,msm-uartdm-v1.4" },
{ }
+};
+U_BOOT_DRIVER(serial_msm) = {
.name = "serial_msm",
.id = UCLASS_SERIAL,
.of_match = msm_serial_ids,
.ofdata_to_platdata = msm_serial_ofdata_to_platdata,
.priv_auto_alloc_size = sizeof(struct msm_serial_data),
.probe = msm_serial_probe,
.ops = &msm_serial_ops,
.flags = DM_FLAG_PRE_RELOC,
Do you need this? You can specify this with u-boot,dm-pre-reloc in the device tree.
+};
2.5.0
Regards, Simon

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256
Hi Simon,
Thank you for awesome review!
I fully agree with most of the suggestions (for this and following patches), so to keep it short will reply only to questions (or where I disagree)
On 15.12.2015 19:58, Simon Glass wrote:
Hi Mateusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
[...]
+static int msm_serial_getc(struct udevice *dev) +{
struct msm_serial_data *p = dev_get_priv(dev);
unsigned sr;
char c;
/* There was something buffered */
if (p->chars_cnt) {
c = p->chars_buf & 0xFF;
p->chars_buf >>= 8;
p->chars_cnt--;
return c;
}
/* Clear error in case of buffer overrun */
if (readl(p->base + UARTDM_SR) & UARTDM_SR_UART_OVERRUN)
writel(UARTDM_CR_CMD_RESET_ERR, p->base + UARTDM_CR);
/* We need to fetch new character */
sr = readl(p->base + UARTDM_SR);
/* There are at least 4 bytes in fifo */
if (sr & UARTDM_SR_RX_READY) {
p->chars_buf = readl(p->base + UARTDM_RF);
c = p->chars_buf & 0xFF;
p->chars_cnt = 3; /* 4 - one read */
p->chars_buf >>= 8;
return c;
}
/* Check if there is anything in fifo */
p->chars_cnt = readl(p->base + UARTDM_RXFS);
/* Extract number of characters in UART packing buffer */
p->chars_cnt = (p->chars_cnt >> UARTDM_RXFS_BUF_SHIFT) &
UARTDM_RXFS_BUF_MASK;
if (!p->chars_cnt)
return -EAGAIN;
/* There is at least one charcter, move it to fifo */
writel(UARTDM_CR_CMD_FORCE_STALE, p->base + UARTDM_CR);
p->chars_buf = readl(p->base + UARTDM_RF);
writel(UARTDM_CR_CMD_RESET_STALE_INT, p->base + UARTDM_CR);
writel(0xFFFFFF, p->base + UARTDM_DMRX);
c = p->chars_buf & 0xFF;
p->chars_buf >>= 8;
p->chars_cnt--;
Can you not rationalise this code a bit? E.g.
if (no chars in fifo) { try to get some } if (no chars in fifo) return -EAGAIN extract char from fifo return ch;
You seem to have three copies of the same code.
That is good idea. Just be warned that "try to get some" will have most of the current code inside. Reason: Characters from FIFO are packed into 32-bit register. There are different ways for extracting >=4 chars, and <4 chars.
uclass_get_device_by_of_offset(UCLASS_CLK, clkd[0], &clk);
Check return value. -ENODEV means there is no clk. Is it OK to have no clock?
if (clk)
clk_set_periph_rate(clk, clkd[1], clk_rate);
If is OK to
Of course will check ret value;
For now (at least when it comes to default UART) it's OK not to have clock, as U-Boot is chain-loaded from fastboot, but this will change once I get rid of fastboot dependency.
[...]
.ofdata_to_platdata = msm_serial_ofdata_to_platdata,
.priv_auto_alloc_size = sizeof(struct msm_serial_data),
.probe = msm_serial_probe,
.ops = &msm_serial_ops,
.flags = DM_FLAG_PRE_RELOC,
Do you need this? You can specify this with u-boot,dm-pre-reloc in the device tree.
Probably not, as I do it in dts :)
Regards, Mateusz

2015-12-16 3:58 GMT+09:00 Simon Glass sjg@chromium.org:
+++ b/drivers/serial/serial_msm.c @@ -0,0 +1,204 @@ +/*
- Qualcomm UART driver
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- UART will work in Data Mover mode.
- Based on Linux driver.
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <asm/io.h> +#include <common.h> +#include <dm.h> +#include <clk.h> +#include <errno.h> +#include <linux/compiler.h> +#include <serial.h> +#include <watchdog.h>
Ordering:
common.h clk.h dm.h errno.h serial.h watchdog.h asm/ linux/
No.
Put <linux/*.h> above <asm/*.h>, at least. (the same order in Linux)
BTW, the "Include file order" in http://www.denx.de/wiki/U-Boot/CodingStyle
Is this your opinion? Or community's opinion.
Did anybody review it?

Hi Masahiro,
On 20 December 2015 at 23:50, Masahiro Yamada yamada.masahiro@socionext.com wrote:
2015-12-16 3:58 GMT+09:00 Simon Glass sjg@chromium.org:
+++ b/drivers/serial/serial_msm.c @@ -0,0 +1,204 @@ +/*
- Qualcomm UART driver
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- UART will work in Data Mover mode.
- Based on Linux driver.
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <asm/io.h> +#include <common.h> +#include <dm.h> +#include <clk.h> +#include <errno.h> +#include <linux/compiler.h> +#include <serial.h> +#include <watchdog.h>
Ordering:
common.h clk.h dm.h errno.h serial.h watchdog.h asm/ linux/
No.
Put <linux/*.h> above <asm/*.h>, at least. (the same order in Linux)
BTW, the "Include file order" in http://www.denx.de/wiki/U-Boot/CodingStyle
Is this your opinion? Or community's opinion.
Did anybody review it?
This came from Mike Frysinger some years ago on the mailing list and I have followed it since. I took it to be a U-Boot standard and added it to the Wiki at some point. Linux perhaps has linux/ above asm/ for its own reasons (e.g. because it is Linux and needs its headers first) but I don't think that is a good idea for U-Boot. It is unnecessary and makes the sort order more confusing.
Regards, Simon

Hi Simon,
BTW, the "Include file order" in http://www.denx.de/wiki/U-Boot/CodingStyle
Is this your opinion? Or community's opinion.
Did anybody review it?
This came from Mike Frysinger some years ago on the mailing list and I have followed it since. I took it to be a U-Boot standard and added it to the Wiki at some point. Linux perhaps has linux/ above asm/ for its own reasons (e.g. because it is Linux and needs its headers first) but I don't think that is a good idea for U-Boot. It is unnecessary and makes the sort order more confusing.
OK, but I want to know the reason. Do you remember why Mike Frysinger suggested so?
I guess Linux sorts headers from global to local.
#include <linux/*.h> global in the project #include <asm/*.h> arch-specific #include "foo.h" local in the directory
Likewise, the following makes sense for U-Boot
#include <common.h> #include <*.h> global in the project (U-boot orignal) #include <linux/*.h> global in the project (come from Linux) #include <asm/*.h> arch-specific #include <asm/arch/*.h> SoC-specific #include "foo.h" local in the directory
if I am not missing something...

Hi Masahiro,
On 23.12.2015 04:52, Masahiro Yamada wrote: [..]
I guess Linux sorts headers from global to local.
#include <linux/*.h> global in the project #include <asm/*.h> arch-specific #include "foo.h" local in the directory
Likewise, the following makes sense for U-Boot
#include <common.h> #include <*.h> global in the project (U-boot orignal) #include <linux/*.h> global in the project (come from Linux) #include <asm/*.h> arch-specific #include <asm/arch/*.h> SoC-specific #include "foo.h" local in the directory
if I am not missing something...
Is it OK with you if for V1 (I'd like to post it soon) I'll use order proposed by Simon (and U-Boot page in general), and then depending on your discussion will swap (or not).
Regards, Mateusz

Hi Mateusz,
2015-12-28 1:51 GMT+09:00 Mateusz Kulikowski mateusz.kulikowski@gmail.com:
Hi Masahiro,
On 23.12.2015 04:52, Masahiro Yamada wrote: [..]
I guess Linux sorts headers from global to local.
#include <linux/*.h> global in the project #include <asm/*.h> arch-specific #include "foo.h" local in the directory
Likewise, the following makes sense for U-Boot
#include <common.h> #include <*.h> global in the project (U-boot orignal) #include <linux/*.h> global in the project (come from Linux) #include <asm/*.h> arch-specific #include <asm/arch/*.h> SoC-specific #include "foo.h" local in the directory
if I am not missing something...
Is it OK with you if for V1 (I'd like to post it soon) I'll use order proposed by Simon (and U-Boot page in general), and then depending on your discussion will swap (or not).
I am OK with that.

Hi Masahiro,
On 22 December 2015 at 20:52, Masahiro Yamada yamada.masahiro@socionext.com wrote:
Hi Simon,
BTW, the "Include file order" in http://www.denx.de/wiki/U-Boot/CodingStyle
Is this your opinion? Or community's opinion.
Did anybody review it?
This came from Mike Frysinger some years ago on the mailing list and I have followed it since. I took it to be a U-Boot standard and added it to the Wiki at some point. Linux perhaps has linux/ above asm/ for its own reasons (e.g. because it is Linux and needs its headers first) but I don't think that is a good idea for U-Boot. It is unnecessary and makes the sort order more confusing.
OK, but I want to know the reason. Do you remember why Mike Frysinger suggested so?
I guess Linux sorts headers from global to local.
#include <linux/*.h> global in the project #include <asm/*.h> arch-specific #include "foo.h" local in the directory
Likewise, the following makes sense for U-Boot
#include <common.h> #include <*.h> global in the project (U-boot orignal) #include <linux/*.h> global in the project (come from Linux) #include <asm/*.h> arch-specific #include <asm/arch/*.h> SoC-specific #include "foo.h" local in the directory
if I am not missing something...
The only difference here seems to be the position of the linux headers. I don't think it is a big deal to change it if you like. The only think I don't like so much is that it seems easier to put 'linux' after 'asm', since it matches the alphabetical order and may avoid confusion. But it's not important to me - mostly we just need to be consistent. If you want to do this, perhaps get an ack from Tom and then update the wiki?
-- Best Regards Masahiro Yamada
Regards, Simon

Hi Simon,
2015-12-28 13:29 GMT+09:00 Simon Glass sjg@chromium.org:
Hi Masahiro,
On 22 December 2015 at 20:52, Masahiro Yamada yamada.masahiro@socionext.com wrote:
Hi Simon,
BTW, the "Include file order" in http://www.denx.de/wiki/U-Boot/CodingStyle
Is this your opinion? Or community's opinion.
Did anybody review it?
This came from Mike Frysinger some years ago on the mailing list and I have followed it since. I took it to be a U-Boot standard and added it to the Wiki at some point. Linux perhaps has linux/ above asm/ for its own reasons (e.g. because it is Linux and needs its headers first) but I don't think that is a good idea for U-Boot. It is unnecessary and makes the sort order more confusing.
OK, but I want to know the reason. Do you remember why Mike Frysinger suggested so?
I guess Linux sorts headers from global to local.
#include <linux/*.h> global in the project #include <asm/*.h> arch-specific #include "foo.h" local in the directory
Likewise, the following makes sense for U-Boot
#include <common.h> #include <*.h> global in the project (U-boot orignal) #include <linux/*.h> global in the project (come from Linux) #include <asm/*.h> arch-specific #include <asm/arch/*.h> SoC-specific #include "foo.h" local in the directory
if I am not missing something...
The only difference here seems to be the position of the linux headers. I don't think it is a big deal to change it if you like. The only think I don't like so much is that it seems easier to put 'linux' after 'asm', since it matches the alphabetical order and may avoid confusion. But it's not important to me - mostly we just need to be consistent. If you want to do this, perhaps get an ack from Tom and then update the wiki?
OK.
Perhaps, we can continue this discussion in a more suitable subject.

Add support for gpio controllers on Qualcomm Snapdragon devices. This devices are usually called Top Level Mode Multiplexing in Qualcomm documentation.
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com ---
drivers/gpio/Kconfig | 7 +++ drivers/gpio/Makefile | 2 +- drivers/gpio/msm_gpio.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 drivers/gpio/msm_gpio.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index e60e9fd..1d9443b 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -46,6 +46,13 @@ config LPC32XX_GPIO help Support for the LPC32XX GPIO driver.
+config MSM_GPIO + bool "Qualcomm GPIO driver" + depends on DM_GPIO + default n + help + Support GPIO controllers on Qualcomm Snapdragon family of SoCs. + config ROCKCHIP_GPIO bool "Rockchip GPIO driver" depends on DM_GPIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index fb4fd25..9520b1e 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -46,4 +46,4 @@ obj-$(CONFIG_STM32_GPIO) += stm32_gpio.o obj-$(CONFIG_ZYNQ_GPIO) += zynq_gpio.o obj-$(CONFIG_VYBRID_GPIO) += vybrid_gpio.o obj-$(CONFIG_HIKEY_GPIO) += hi6220_gpio.o - +obj-$(CONFIG_MSM_GPIO) += msm_gpio.o diff --git a/drivers/gpio/msm_gpio.c b/drivers/gpio/msm_gpio.c new file mode 100644 index 0000000..9bb9e89 --- /dev/null +++ b/drivers/gpio/msm_gpio.c @@ -0,0 +1,115 @@ +/* + * Qualcomm GPIO driver + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <errno.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct msm_gpio_bank { + phys_addr_t base; +}; + +static int msm_gpio_direction_input(struct udevice *dev, unsigned int gpio) +{ + struct msm_gpio_bank *priv = dev_get_priv(dev); + phys_addr_t reg = priv->base + GPIO_CONFIG_OFF(gpio); + + /* Disable OE bit */ + writel((readl(reg) & ~GPIO_OE_MASK) | GPIO_OE_DISABLE , reg); + return 0; +} + +static int msm_gpio_set_value(struct udevice *dev, unsigned gpio, int value) +{ + struct msm_gpio_bank *priv = dev_get_priv(dev); + + value = !!value; + /* set value */ + writel(value << GPIO_OUT, priv->base + GPIO_IN_OUT_OFF(gpio)); + return 0; +} + +static int msm_gpio_direction_output(struct udevice *dev, unsigned gpio, + int value) +{ + struct msm_gpio_bank *priv = dev_get_priv(dev); + phys_addr_t reg = priv->base + GPIO_CONFIG_OFF(gpio); + + value = !!value; + /* set value */ + writel(value << GPIO_OUT, priv->base + GPIO_IN_OUT_OFF(gpio)); + /* switch direction */ + writel((readl(reg) & ~GPIO_OE_MASK) | GPIO_OE_ENABLE , reg); + return 0; +} + +static int msm_gpio_get_value(struct udevice *dev, unsigned gpio) +{ + struct msm_gpio_bank *priv = dev_get_priv(dev); + + return !!(readl(priv->base + GPIO_IN_OUT_OFF(gpio)) >> GPIO_IN); +} + +static int msm_gpio_get_function(struct udevice *dev, unsigned offset) +{ + struct msm_gpio_bank *priv = dev_get_priv(dev); + + if (readl(priv->base + GPIO_CONFIG_OFF(offset)) & GPIO_OE_ENABLE) + return GPIOF_OUTPUT; + return GPIOF_INPUT; +} + +static const struct dm_gpio_ops gpio_msm_ops = { + .direction_input = msm_gpio_direction_input, + .direction_output = msm_gpio_direction_output, + .get_value = msm_gpio_get_value, + .set_value = msm_gpio_set_value, + .get_function = msm_gpio_get_function, +}; + +static int msm_gpio_probe(struct udevice *dev) +{ + struct msm_gpio_bank *priv = dev_get_priv(dev); + + priv->base = dev_get_addr(dev); + return priv->base == FDT_ADDR_T_NONE; +} + +static int msm_gpio_ofdata_to_platdata(struct udevice *dev) +{ + struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); + + uc_priv->gpio_count = fdtdec_get_int(gd->fdt_blob, dev->of_offset, + "gpio-count", 0); + uc_priv->bank_name = fdt_getprop(gd->fdt_blob, dev->of_offset, + "gpio-bank-name", NULL); + if (uc_priv->bank_name == NULL) + uc_priv->bank_name = "soc"; + return 0; +} + +static const struct udevice_id msm_gpio_ids[] = { + { .compatible = "qcom,msm8916-pinctrl" }, + { } +}; + +U_BOOT_DRIVER(gpio_msm) = { + .name = "gpio_msm", + .id = UCLASS_GPIO, + .of_match = msm_gpio_ids, + .ofdata_to_platdata = msm_gpio_ofdata_to_platdata, + .probe = msm_gpio_probe, + .ops = &gpio_msm_ops, + .priv_auto_alloc_size = sizeof(struct msm_gpio_bank), +}; + +

Hi Mateusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
Add support for gpio controllers on Qualcomm Snapdragon devices. This devices are usually called Top Level Mode Multiplexing in Qualcomm documentation.
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com
drivers/gpio/Kconfig | 7 +++ drivers/gpio/Makefile | 2 +- drivers/gpio/msm_gpio.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 drivers/gpio/msm_gpio.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index e60e9fd..1d9443b 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -46,6 +46,13 @@ config LPC32XX_GPIO help Support for the LPC32XX GPIO driver.
+config MSM_GPIO
bool "Qualcomm GPIO driver"
depends on DM_GPIO
default n
help
Support GPIO controllers on Qualcomm Snapdragon family of SoCs.
Any more details? How many banks? How are they named? What features does the driver support?
config ROCKCHIP_GPIO bool "Rockchip GPIO driver" depends on DM_GPIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index fb4fd25..9520b1e 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -46,4 +46,4 @@ obj-$(CONFIG_STM32_GPIO) += stm32_gpio.o obj-$(CONFIG_ZYNQ_GPIO) += zynq_gpio.o obj-$(CONFIG_VYBRID_GPIO) += vybrid_gpio.o obj-$(CONFIG_HIKEY_GPIO) += hi6220_gpio.o
+obj-$(CONFIG_MSM_GPIO) += msm_gpio.o diff --git a/drivers/gpio/msm_gpio.c b/drivers/gpio/msm_gpio.c new file mode 100644 index 0000000..9bb9e89 --- /dev/null +++ b/drivers/gpio/msm_gpio.c @@ -0,0 +1,115 @@ +/*
- Qualcomm GPIO driver
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <common.h> +#include <dm.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <errno.h>
Put errno.h below dm.h
+DECLARE_GLOBAL_DATA_PTR;
+struct msm_gpio_bank {
phys_addr_t base;
+};
+static int msm_gpio_direction_input(struct udevice *dev, unsigned int gpio) +{
struct msm_gpio_bank *priv = dev_get_priv(dev);
phys_addr_t reg = priv->base + GPIO_CONFIG_OFF(gpio);
/* Disable OE bit */
writel((readl(reg) & ~GPIO_OE_MASK) | GPIO_OE_DISABLE , reg);
How about:
clrsetbits_le32(reg, GPIO_OE_MASK, GPIO_OE_DISABLE)
return 0;
+}
+static int msm_gpio_set_value(struct udevice *dev, unsigned gpio, int value) +{
struct msm_gpio_bank *priv = dev_get_priv(dev);
value = !!value;
/* set value */
writel(value << GPIO_OUT, priv->base + GPIO_IN_OUT_OFF(gpio));
return 0;
+}
+static int msm_gpio_direction_output(struct udevice *dev, unsigned gpio,
int value)
+{
struct msm_gpio_bank *priv = dev_get_priv(dev);
phys_addr_t reg = priv->base + GPIO_CONFIG_OFF(gpio);
value = !!value;
/* set value */
writel(value << GPIO_OUT, priv->base + GPIO_IN_OUT_OFF(gpio));
/* switch direction */
writel((readl(reg) & ~GPIO_OE_MASK) | GPIO_OE_ENABLE , reg);
return 0;
+}
+static int msm_gpio_get_value(struct udevice *dev, unsigned gpio) +{
struct msm_gpio_bank *priv = dev_get_priv(dev);
return !!(readl(priv->base + GPIO_IN_OUT_OFF(gpio)) >> GPIO_IN);
+}
+static int msm_gpio_get_function(struct udevice *dev, unsigned offset) +{
struct msm_gpio_bank *priv = dev_get_priv(dev);
if (readl(priv->base + GPIO_CONFIG_OFF(offset)) & GPIO_OE_ENABLE)
return GPIOF_OUTPUT;
return GPIOF_INPUT;
+}
+static const struct dm_gpio_ops gpio_msm_ops = {
.direction_input = msm_gpio_direction_input,
.direction_output = msm_gpio_direction_output,
.get_value = msm_gpio_get_value,
.set_value = msm_gpio_set_value,
.get_function = msm_gpio_get_function,
+};
+static int msm_gpio_probe(struct udevice *dev) +{
struct msm_gpio_bank *priv = dev_get_priv(dev);
priv->base = dev_get_addr(dev);
return priv->base == FDT_ADDR_T_NONE;
+}
+static int msm_gpio_ofdata_to_platdata(struct udevice *dev) +{
struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
uc_priv->gpio_count = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
"gpio-count", 0);
uc_priv->bank_name = fdt_getprop(gd->fdt_blob, dev->of_offset,
"gpio-bank-name", NULL);
if (uc_priv->bank_name == NULL)
uc_priv->bank_name = "soc";
return 0;
+}
+static const struct udevice_id msm_gpio_ids[] = {
{ .compatible = "qcom,msm8916-pinctrl" },
Is there a device binding tree binding file you can put in doc/device-tree-bindings?
{ }
+};
+U_BOOT_DRIVER(gpio_msm) = {
.name = "gpio_msm",
.id = UCLASS_GPIO,
.of_match = msm_gpio_ids,
.ofdata_to_platdata = msm_gpio_ofdata_to_platdata,
.probe = msm_gpio_probe,
.ops = &gpio_msm_ops,
.priv_auto_alloc_size = sizeof(struct msm_gpio_bank),
+};
-- 2.5.0
Regards, Simon

Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com ---
drivers/mmc/Kconfig | 6 ++ drivers/mmc/Makefile | 1 + drivers/mmc/msm_sdhci.c | 149 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 drivers/mmc/msm_sdhci.c
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index ceae7bc..3f75fa7 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -16,6 +16,12 @@ config DM_MMC appear as block devices in U-Boot and can support filesystems such as EXT4 and FAT.
+config MSM_SDHCI + bool "Qualcomm SDHCI controller" + depends on DM_MMC + help + Enables support for SDHCI 2.0 controller manufactured by Qualcomm. + config ROCKCHIP_DWMMC bool "Rockchip SD/MMC controller support" depends on DM_MMC && OF_CONTROL diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 5d35705..f4fdfca 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -49,3 +49,4 @@ else obj-$(CONFIG_GENERIC_MMC) += mmc_write.o endif
+obj-$(CONFIG_MSM_SDHCI) += msm_sdhci.o diff --git a/drivers/mmc/msm_sdhci.c b/drivers/mmc/msm_sdhci.c new file mode 100644 index 0000000..96119f0 --- /dev/null +++ b/drivers/mmc/msm_sdhci.c @@ -0,0 +1,149 @@ +/* + * Qualcomm SDHCI driver - SD/eMMC controller + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + * + * Based on Linux driver + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <asm/io.h> +#include <common.h> +#include <dm.h> +#include <linux/bitops.h> +#include <sdhci.h> +#include <clk.h> + +/* Non-standard registers needed for SDHCI startup */ +#define SDCC_MCI_POWER 0x0 +#define SDCC_MCI_POWER_SW_RST BIT(7) + +/* This is undocumented register */ +#define SDCC_MCI_VERSION 0x50 +#define SDCC_MCI_VERSION_MAJOR_SHIFT 28 +#define SDCC_MCI_VERSION_MAJOR_MASK (0xf << SDCC_MCI_VERSION_MAJOR_SHIFT) +#define SDCC_MCI_VERSION_MINOR_MASK 0xff + +#define SDCC_MCI_HC_MODE 0x78 + +/* Offset to SDHCI registers */ +#define SDCC_SDHCI_OFFSET 0x900 + +/* Non standard (?) SDHCI register */ +#define SDHCI_VENDOR_SPEC_CAPABILITIES0 0x11c + +struct msm_sdhc { + struct sdhci_host host; + phys_addr_t base; + unsigned width; +}; + +DECLARE_GLOBAL_DATA_PTR; + +static int msm_sdc_clk_init(struct udevice *dev) +{ + uint clk_rate = fdtdec_get_uint(gd->fdt_blob, dev->of_offset, + "clock-frequency", 400000); + uint clkd[2]; /* clk_id and clk_no */ + fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, "clock", clkd, 2); + clkd[0] = fdt_node_offset_by_phandle(gd->fdt_blob, clkd[0]); + + struct udevice *clk = NULL; + uclass_get_device_by_of_offset(UCLASS_CLK, clkd[0], &clk); + if (clk) + clk_set_periph_rate(clk, clkd[1], clk_rate); + + return 0; +} + +static int msm_sdc_probe(struct udevice *dev) +{ + struct msm_sdhc *prv = dev_get_priv(dev); + struct sdhci_host *host = &prv->host; + u32 core_version, core_minor, core_major; + + host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_BROKEN_R1B; + + /* Init clocks */ + if (msm_sdc_clk_init(dev)) + return -EIO; + + /* Reset the core and Enable SDHC mode */ + writel(readl(prv->base + SDCC_MCI_POWER) | SDCC_MCI_POWER_SW_RST, + prv->base + SDCC_MCI_POWER); + + /* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */ + mdelay(2); + + if (readl(prv->base + SDCC_MCI_POWER) & SDCC_MCI_POWER_SW_RST) { + printf("msm_sdhci: stuck in reset\n"); + return -1; + } + + /* Enable host-controller mode */ + writel(1, prv->base + SDCC_MCI_HC_MODE); + + core_version = readl(prv->base + SDCC_MCI_VERSION); + + core_major = (core_version & SDCC_MCI_VERSION_MAJOR_MASK); + core_major >>= SDCC_MCI_VERSION_MAJOR_SHIFT; + + core_minor = core_version & SDCC_MCI_VERSION_MINOR_MASK; + + /* + * Support for some capabilities is not advertised by newer + * controller versions and must be explicitly enabled. + */ + if (core_major >= 1 && core_minor != 0x11 && core_minor != 0x12) { + u32 caps = readl(host->ioaddr + SDHCI_CAPABILITIES); + caps |= SDHCI_CAN_VDD_300 | SDHCI_CAN_DO_8BIT; + writel(caps, host->ioaddr + SDHCI_VENDOR_SPEC_CAPABILITIES0); + } + + /* Set host controller version */ + host->version = sdhci_readw(host, SDHCI_HOST_VERSION); + + /* automatically detect max and min speed */ + return add_sdhci(host, 0, 0); +} + +static int msm_sdc_remove(struct udevice *dev) +{ + struct msm_sdhc *priv = dev_get_priv(dev); + /* Disable host-controller mode */ + writel(0, priv->base + SDCC_MCI_HC_MODE); + return 0; +} + +static int msm_ofdata_to_platdata(struct udevice *dev) +{ + struct msm_sdhc *priv = dev_get_priv(dev); + struct sdhci_host *host = &priv->host; + + host->name = strdup(dev->name); + host->ioaddr = (void *)dev_get_addr(dev); + host->bus_width = fdtdec_get_int(gd->fdt_blob, dev->of_offset, + "bus-width", 4); + host->index = fdtdec_get_uint(gd->fdt_blob, dev->of_offset, "index", 0); + priv->base = fdtdec_get_addr_size_auto_parent(gd->fdt_blob, + dev->parent->of_offset, + dev->of_offset, "reg", + 1, NULL); + return 0; +} + +static const struct udevice_id msm_mmc_ids[] = { + { .compatible = "qcom,sdhci-msm-v4" }, + { } +}; + +U_BOOT_DRIVER(msm_sdc_drv) = { + .name = "msm_sdc", + .id = UCLASS_MMC, + .of_match = msm_mmc_ids, + .ofdata_to_platdata = msm_ofdata_to_platdata, + .probe = msm_sdc_probe, + .remove = msm_sdc_remove, + .priv_auto_alloc_size = sizeof(struct msm_sdhc), +};

Hi Mateusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com
Please add a commit message.
drivers/mmc/Kconfig | 6 ++ drivers/mmc/Makefile | 1 + drivers/mmc/msm_sdhci.c | 149 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 drivers/mmc/msm_sdhci.c
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index ceae7bc..3f75fa7 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -16,6 +16,12 @@ config DM_MMC appear as block devices in U-Boot and can support filesystems such as EXT4 and FAT.
+config MSM_SDHCI
bool "Qualcomm SDHCI controller"
depends on DM_MMC
help
Enables support for SDHCI 2.0 controller manufactured by Qualcomm.
Can you add any more detail here?
config ROCKCHIP_DWMMC bool "Rockchip SD/MMC controller support" depends on DM_MMC && OF_CONTROL diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 5d35705..f4fdfca 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -49,3 +49,4 @@ else obj-$(CONFIG_GENERIC_MMC) += mmc_write.o endif
+obj-$(CONFIG_MSM_SDHCI) += msm_sdhci.o diff --git a/drivers/mmc/msm_sdhci.c b/drivers/mmc/msm_sdhci.c new file mode 100644 index 0000000..96119f0 --- /dev/null +++ b/drivers/mmc/msm_sdhci.c @@ -0,0 +1,149 @@ +/*
- Qualcomm SDHCI driver - SD/eMMC controller
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- Based on Linux driver
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <asm/io.h> +#include <common.h> +#include <dm.h> +#include <linux/bitops.h> +#include <sdhci.h> +#include <clk.h>
Ordering:
common.h always goes first clk.h dm.h sdhci.h asm/ linux/
+/* Non-standard registers needed for SDHCI startup */ +#define SDCC_MCI_POWER 0x0 +#define SDCC_MCI_POWER_SW_RST BIT(7)
+/* This is undocumented register */ +#define SDCC_MCI_VERSION 0x50 +#define SDCC_MCI_VERSION_MAJOR_SHIFT 28 +#define SDCC_MCI_VERSION_MAJOR_MASK (0xf << SDCC_MCI_VERSION_MAJOR_SHIFT) +#define SDCC_MCI_VERSION_MINOR_MASK 0xff
+#define SDCC_MCI_HC_MODE 0x78
+/* Offset to SDHCI registers */ +#define SDCC_SDHCI_OFFSET 0x900
+/* Non standard (?) SDHCI register */ +#define SDHCI_VENDOR_SPEC_CAPABILITIES0 0x11c
+struct msm_sdhc {
struct sdhci_host host;
phys_addr_t base;
unsigned width;
Comment - what is width?
+};
+DECLARE_GLOBAL_DATA_PTR;
+static int msm_sdc_clk_init(struct udevice *dev) +{
uint clk_rate = fdtdec_get_uint(gd->fdt_blob, dev->of_offset,
"clock-frequency", 400000);
uint clkd[2]; /* clk_id and clk_no */
fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, "clock", clkd, 2);
clkd[0] = fdt_node_offset_by_phandle(gd->fdt_blob, clkd[0]);
struct udevice *clk = NULL;
uclass_get_device_by_of_offset(UCLASS_CLK, clkd[0], &clk);
if (clk)
clk_set_periph_rate(clk, clkd[1], clk_rate);
See comments on the previous patch. Also you could move the DT decode code all into your ofdata_to_platdata function (if you like).
return 0;
+}
+static int msm_sdc_probe(struct udevice *dev) +{
struct msm_sdhc *prv = dev_get_priv(dev);
struct sdhci_host *host = &prv->host;
u32 core_version, core_minor, core_major;
host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_BROKEN_R1B;
/* Init clocks */
if (msm_sdc_clk_init(dev))
return -EIO;
/* Reset the core and Enable SDHC mode */
writel(readl(prv->base + SDCC_MCI_POWER) | SDCC_MCI_POWER_SW_RST,
prv->base + SDCC_MCI_POWER);
/* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */
mdelay(2);
So why such a long delay? Perhaps you should put the code immediately below int a timeout loop?
start = get_timer(0); while (readl...) { if (get_timer(start) > 2) return -ETIMEDOUT; }
if (readl(prv->base + SDCC_MCI_POWER) & SDCC_MCI_POWER_SW_RST) {
printf("msm_sdhci: stuck in reset\n");
return -1;
-ETIMEDOUT?
}
/* Enable host-controller mode */
writel(1, prv->base + SDCC_MCI_HC_MODE);
core_version = readl(prv->base + SDCC_MCI_VERSION);
core_major = (core_version & SDCC_MCI_VERSION_MAJOR_MASK);
core_major >>= SDCC_MCI_VERSION_MAJOR_SHIFT;
core_minor = core_version & SDCC_MCI_VERSION_MINOR_MASK;
/*
* Support for some capabilities is not advertised by newer
* controller versions and must be explicitly enabled.
*/
if (core_major >= 1 && core_minor != 0x11 && core_minor != 0x12) {
u32 caps = readl(host->ioaddr + SDHCI_CAPABILITIES);
caps |= SDHCI_CAN_VDD_300 | SDHCI_CAN_DO_8BIT;
writel(caps, host->ioaddr + SDHCI_VENDOR_SPEC_CAPABILITIES0);
}
/* Set host controller version */
host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
/* automatically detect max and min speed */
return add_sdhci(host, 0, 0);
+}
+static int msm_sdc_remove(struct udevice *dev) +{
struct msm_sdhc *priv = dev_get_priv(dev);
blank line after decls
/* Disable host-controller mode */
writel(0, priv->base + SDCC_MCI_HC_MODE);
blank line before return
return 0;
+}
+static int msm_ofdata_to_platdata(struct udevice *dev) +{
struct msm_sdhc *priv = dev_get_priv(dev);
struct sdhci_host *host = &priv->host;
host->name = strdup(dev->name);
host->ioaddr = (void *)dev_get_addr(dev);
host->bus_width = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
"bus-width", 4);
host->index = fdtdec_get_uint(gd->fdt_blob, dev->of_offset, "index", 0);
priv->base = fdtdec_get_addr_size_auto_parent(gd->fdt_blob,
dev->parent->of_offset,
dev->of_offset, "reg",
1, NULL);
Odd that you are reading the second cell. How come?
return 0;
+}
+static const struct udevice_id msm_mmc_ids[] = {
{ .compatible = "qcom,sdhci-msm-v4" },
{ }
+};
+U_BOOT_DRIVER(msm_sdc_drv) = {
.name = "msm_sdc",
.id = UCLASS_MMC,
.of_match = msm_mmc_ids,
.ofdata_to_platdata = msm_ofdata_to_platdata,
.probe = msm_sdc_probe,
.remove = msm_sdc_remove,
.priv_auto_alloc_size = sizeof(struct msm_sdhc),
+};
2.5.0
Regards, Simon

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256
Hi Simon,
On 15.12.2015 19:58, Simon Glass wrote:
Hi Mateusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
[...]
+DECLARE_GLOBAL_DATA_PTR;
+static int msm_sdc_clk_init(struct udevice *dev) +{
uint clk_rate = fdtdec_get_uint(gd->fdt_blob, dev->of_offset,
"clock-frequency", 400000);
uint clkd[2]; /* clk_id and clk_no */
fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, "clock", clkd, 2);
clkd[0] = fdt_node_offset_by_phandle(gd->fdt_blob, clkd[0]);
struct udevice *clk = NULL;
uclass_get_device_by_of_offset(UCLASS_CLK, clkd[0], &clk);
if (clk)
clk_set_periph_rate(clk, clkd[1], clk_rate);
See comments on the previous patch. Also you could move the DT decode code all into your ofdata_to_platdata function (if you like).
But requesting clock must be handled in probe (i.e. when clock is already bound) right?
return 0;
+}
+static int msm_sdc_probe(struct udevice *dev) +{
struct msm_sdhc *prv = dev_get_priv(dev);
struct sdhci_host *host = &prv->host;
u32 core_version, core_minor, core_major;
host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_BROKEN_R1B;
/* Init clocks */
if (msm_sdc_clk_init(dev))
return -EIO;
/* Reset the core and Enable SDHC mode */
writel(readl(prv->base + SDCC_MCI_POWER) | SDCC_MCI_POWER_SW_RST,
prv->base + SDCC_MCI_POWER);
/* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */
mdelay(2);
So why such a long delay? Perhaps you should put the code immediately below int a timeout loop?
start = get_timer(0); while (readl...) { if (get_timer(start) > 2) return -ETIMEDOUT; }
I'm not sure if I can do that - I can test it, perhaps it will even work on my board, but it was put explicitly in Linux driver (1-5ms delay).
Documentation says: "SW should wait until bit 0 (MCLK_REG_WR_ACTIVE) of MCI_STATUS2 register is 0, after writing to MCI_POWER register and before accessing this register again."
But this applies to all writes, not to reset request - I'm not sure how MCI_STATUS2 will behave during reset (it may get zeroed earlier).
Do you really think I should try to decrease this delay?
[...]
+}
+static int msm_ofdata_to_platdata(struct udevice *dev) +{
struct msm_sdhc *priv = dev_get_priv(dev);
struct sdhci_host *host = &priv->host;
host->name = strdup(dev->name);
host->ioaddr = (void *)dev_get_addr(dev);
host->bus_width = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
"bus-width", 4);
host->index = fdtdec_get_uint(gd->fdt_blob, dev->of_offset, "index", 0);
priv->base = fdtdec_get_addr_size_auto_parent(gd->fdt_blob,
dev->parent->of_offset,
dev->of_offset, "reg",
1, NULL);
Odd that you are reading the second cell. How come?
I tried to use as much parts of original qcom Linux dts as possible. For mmc controller, there are 2 addresses needed: - - SDCC base (i.e. base of IP block with vendor-specific regs etc.) - - SDHCI base (i.e. place where SDHCI "standard" registers start)
My assumption is that SDHCI offset may be different on different SoCs, and wanted to have generic driver.
[...] Regards, Mateusz

Hi Mateusz,
On 16 December 2015 at 15:46, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256
Hi Simon,
On 15.12.2015 19:58, Simon Glass wrote:
Hi Mateusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
[...]
+DECLARE_GLOBAL_DATA_PTR;
+static int msm_sdc_clk_init(struct udevice *dev) +{
uint clk_rate = fdtdec_get_uint(gd->fdt_blob, dev->of_offset,
"clock-frequency", 400000);
uint clkd[2]; /* clk_id and clk_no */
fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, "clock", clkd, 2);
clkd[0] = fdt_node_offset_by_phandle(gd->fdt_blob, clkd[0]);
struct udevice *clk = NULL;
uclass_get_device_by_of_offset(UCLASS_CLK, clkd[0], &clk);
if (clk)
clk_set_periph_rate(clk, clkd[1], clk_rate);
See comments on the previous patch. Also you could move the DT decode code all into your ofdata_to_platdata function (if you like).
But requesting clock must be handled in probe (i.e. when clock is already bound) right?
Yes. Although bear in mind that the ofdata_to_platdata() call is made just before probe() so it is OK to put 'get' calls in there. In general all devices are bound before any are probed.
return 0;
+}
+static int msm_sdc_probe(struct udevice *dev) +{
struct msm_sdhc *prv = dev_get_priv(dev);
struct sdhci_host *host = &prv->host;
u32 core_version, core_minor, core_major;
host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_BROKEN_R1B;
/* Init clocks */
if (msm_sdc_clk_init(dev))
return -EIO;
/* Reset the core and Enable SDHC mode */
writel(readl(prv->base + SDCC_MCI_POWER) | SDCC_MCI_POWER_SW_RST,
prv->base + SDCC_MCI_POWER);
/* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */
mdelay(2);
So why such a long delay? Perhaps you should put the code immediately below int a timeout loop?
start = get_timer(0); while (readl...) { if (get_timer(start) > 2) return -ETIMEDOUT; }
I'm not sure if I can do that - I can test it, perhaps it will even work on my board, but it was put explicitly in Linux driver (1-5ms delay).
Documentation says: "SW should wait until bit 0 (MCLK_REG_WR_ACTIVE) of MCI_STATUS2 register is 0, after writing to MCI_POWER register and before accessing this register again."
But this applies to all writes, not to reset request - I'm not sure how MCI_STATUS2 will behave during reset (it may get zeroed earlier).
Do you really think I should try to decrease this delay?
Can you change the code to wait until bit 0 is 0, instead of having a delay?
[...]
+}
+static int msm_ofdata_to_platdata(struct udevice *dev) +{
struct msm_sdhc *priv = dev_get_priv(dev);
struct sdhci_host *host = &priv->host;
host->name = strdup(dev->name);
host->ioaddr = (void *)dev_get_addr(dev);
host->bus_width = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
"bus-width", 4);
host->index = fdtdec_get_uint(gd->fdt_blob, dev->of_offset, "index", 0);
priv->base = fdtdec_get_addr_size_auto_parent(gd->fdt_blob,
dev->parent->of_offset,
dev->of_offset, "reg",
1, NULL);
Odd that you are reading the second cell. How come?
I tried to use as much parts of original qcom Linux dts as possible. For mmc controller, there are 2 addresses needed:
- SDCC base (i.e. base of IP block with vendor-specific regs etc.)
- SDHCI base (i.e. place where SDHCI "standard" registers start)
My assumption is that SDHCI offset may be different on different SoCs, and wanted to have generic driver.
OK I see, thanks.
Regards, Simon

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256
Hi Simon,
On 18.12.2015 23:41, Simon Glass wrote:
Hi Mateusz,
On 16 December 2015 at 15:46, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
[...]
I'm not sure if I can do that - I can test it, perhaps it will even work on my board, but it was put explicitly in Linux driver (1-5ms delay).
Documentation says: "SW should wait until bit 0 (MCLK_REG_WR_ACTIVE) of MCI_STATUS2 register is 0, after writing to MCI_POWER register and before accessing this register again."
But this applies to all writes, not to reset request - I'm not sure how MCI_STATUS2 will behave during reset (it may get zeroed earlier).
Do you really think I should try to decrease this delay?
Can you change the code to wait until bit 0 is 0, instead of having a delay?
This seems to work - at least on my board so will do it that way - thanks :)
Regards, Mateusz

Some host controllers need addidional initialization after ehci_reset() In non-dm implementation it is possible to use CONFIG_EHCI_HCD_INIT_AFTER_RESET. This patch adds similar option to ehci drivers using dm.
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com ---
drivers/usb/host/ehci-hcd.c | 6 ++++++ drivers/usb/host/ehci.h | 1 + 2 files changed, 7 insertions(+)
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index c85dbce..a54f817 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -1654,6 +1654,12 @@ int ehci_register(struct udevice *dev, struct ehci_hccr *hccr, if (ret) goto err;
+ if (ops->init_after_reset) { + ret = ops->init_after_reset(ctrl); + if (ret) + goto err; + } + ret = ehci_common_init(ctrl, tweaks); if (ret) goto err; diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index b41c04a..ed567fc 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -239,6 +239,7 @@ struct ehci_ops { void (*powerup_fixup)(struct ehci_ctrl *ctrl, uint32_t *status_reg, uint32_t *reg); uint32_t *(*get_portsc_register)(struct ehci_ctrl *ctrl, int port); + int (*init_after_reset)(struct ehci_ctrl *ctrl); };
struct ehci_ctrl {

On Thursday, December 10, 2015 at 10:41:40 PM, Mateusz Kulikowski wrote:
Some host controllers need addidional initialization after ehci_reset() In non-dm implementation it is possible to use CONFIG_EHCI_HCD_INIT_AFTER_RESET. This patch adds similar option to ehci drivers using dm.
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com
Acked-by: Marek Vasut marex@denx.de
Best regards, Marek Vasut

On Thu, Dec 10, 2015 at 10:41:40PM +0100, Mateusz Kulikowski wrote:
Some host controllers need addidional initialization after ehci_reset() In non-dm implementation it is possible to use CONFIG_EHCI_HCD_INIT_AFTER_RESET. This patch adds similar option to ehci drivers using dm.
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com
Reviewed-by: Tom Rini trini@konsulko.com

This driver is able to reconfigure OTG controller into HOST mode. Board can add board-specific initialization as board_prepare_usb(). It requires USB_ULPI_VIEWPORT enabled in board configuration.
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com ---
drivers/usb/host/Kconfig | 8 ++ drivers/usb/host/Makefile | 1 + drivers/usb/host/ehci-msm.c | 198 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 drivers/usb/host/ehci-msm.c
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 0096a2f..5a63475 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -66,6 +66,14 @@ config USB_EHCI_MX6 ---help--- Enables support for the on-chip EHCI controller on i.MX6 SoCs.
+config USB_EHCI_MSM + bool "Support for Qualcomm on-chip EHCI USB controller" + depends on DM_USB + default n + ---help--- + Enables support for the on-chip EHCI controller on Qualcomm + Snapdragon SoCs. + config USB_EHCI_UNIPHIER bool "Support for UniPhier on-chip EHCI USB controller" depends on ARCH_UNIPHIER && OF_CONTROL diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 0b4b458..d4a556a 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -42,6 +42,7 @@ obj-$(CONFIG_USB_EHCI_MX7) += ehci-mx6.o obj-$(CONFIG_USB_EHCI_OMAP) += ehci-omap.o obj-$(CONFIG_USB_EHCI_PPC4XX) += ehci-ppc4xx.o obj-$(CONFIG_USB_EHCI_MARVELL) += ehci-marvell.o +obj-$(CONFIG_USB_EHCI_MSM) += ehci-msm.o obj-$(CONFIG_USB_EHCI_PCI) += ehci-pci.o obj-$(CONFIG_USB_EHCI_SPEAR) += ehci-spear.o obj-$(CONFIG_USB_EHCI_SUNXI) += ehci-sunxi.o diff --git a/drivers/usb/host/ehci-msm.c b/drivers/usb/host/ehci-msm.c new file mode 100644 index 0000000..d17a29a --- /dev/null +++ b/drivers/usb/host/ehci-msm.c @@ -0,0 +1,198 @@ +/* + * Qualcomm EHCI driver + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + * + * Based on Linux driver + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <asm/gpio.h> +#include <asm-generic/errno.h> +#include <linux/compat.h> +#include <dm.h> +#include <fdtdec.h> +#include <libfdt.h> +#include <asm/io.h> +#include <usb.h> +#include <usb/ulpi.h> +#include "ehci.h" + +#ifndef CONFIG_USB_ULPI_VIEWPORT +#error Please enable CONFIG_USB_ULPI_VIEWPORT +#endif + +#define MSM_USB_ULPI_OFFSET 0x170 /* ULPI viewport (PHY) */ +#define MSM_USB_EHCI_OFFSET 0x100 /* Start of EHCI registers */ + +/* PHY viewport regs */ +#define ULPI_MISC_A_READ 0x96 +#define ULPI_MISC_A_SET 0x97 +#define ULPI_MISC_A_CLEAR 0x98 +#define ULPI_MISC_A_VBUSVLDEXTSEL (1 << 1) +#define ULPI_MISC_A_VBUSVLDEXT (1 << 0) + +/* qcom specific registers (OTG) */ +#define USB_GENCONFIG_2 0x00A0 +#define GEN2_SESS_VLD_CTRL_EN (1 << 7) + +#define USB_USBCMD (0x0140) +#define SESS_VLD_CTRL (1 << 25) +#define USBCMD_RESET 2 +#define USBCMD_ATTACH 1 + +/* USB2_HSIC_USB_OTG_HS_BASE_USB_OTG_HS_PORTSC */ +#define USB_PORTSC 0x0184 +#define USB_SBUSCFG 0x0090 +#define USB_AHB_MODE 0x0098 + +#define USB_USBMODE 0x01A8 +#define USBMODE_DEVICE 2 +#define USBMODE_HOST 3 + +struct msm_ehci_priv { + struct ehci_ctrl ctrl; /* Needed by EHCI */ + phys_addr_t base; + phys_addr_t ehci_base; + u32 ulpi_base; + u32 ulpi_port; +}; + +int __weak board_prepare_usb(enum usb_init_type type) +{ + return 0; +} + +static void setup_usb_phy(struct msm_ehci_priv *priv) +{ + struct ulpi_viewport ulpi_vp = {.port_num = priv->ulpi_port, + .viewport_addr = priv->ulpi_base}; + + /* Select and enable external configuration with USB PHY */ + ulpi_write(&ulpi_vp, (u8 *)ULPI_MISC_A_SET, + ULPI_MISC_A_VBUSVLDEXTSEL | ULPI_MISC_A_VBUSVLDEXT); +} + +static void reset_usb_phy(struct msm_ehci_priv *priv) +{ + struct ulpi_viewport ulpi_vp = {.port_num = priv->ulpi_port, + .viewport_addr = priv->ulpi_base}; + + /* Disable VBUS mimicing in the controller. */ + ulpi_write(&ulpi_vp, (u8 *)ULPI_MISC_A_CLEAR, + ULPI_MISC_A_VBUSVLDEXTSEL | ULPI_MISC_A_VBUSVLDEXT); +} + + +static int msm_init_after_reset(struct ehci_ctrl *dev) +{ + struct msm_ehci_priv *p = container_of(dev, struct msm_ehci_priv, ctrl); + uint32_t val; + + /* select ULPI phy */ + writel(0x80000000, p->base + USB_PORTSC); + setup_usb_phy(p); + + /* Enable sess_vld */ + val = readl(p->base + USB_GENCONFIG_2) | GEN2_SESS_VLD_CTRL_EN; + writel(val, p->base + USB_GENCONFIG_2); + + /* Enable external vbus configuration in the LINK */ + val = readl(p->base + USB_USBCMD); + val |= SESS_VLD_CTRL; + writel(val, p->base + USB_USBCMD); + + /* USB_OTG_HS_AHB_BURST */ + writel(0x0, p->base + USB_SBUSCFG); + + /* USB_OTG_HS_AHB_MODE: HPROT_MODE */ + /* Bus access related config. */ + writel(0x08, p->base + USB_AHB_MODE); + + /* set mode to host controller */ + writel(USBMODE_HOST, p->base + USB_USBMODE); + + return 0; +} + +static const struct ehci_ops msm_ehci_ops = { + .init_after_reset = msm_init_after_reset +}; + +static int ehci_usb_probe(struct udevice *dev) +{ + struct msm_ehci_priv *p = dev_get_priv(dev); + struct ehci_hccr *cr; + struct ehci_hcor *or; + int ret; + + cr = (struct ehci_hccr *)p->ehci_base; + or = (struct ehci_hcor *)(p->ehci_base + + HC_LENGTH(readl(p->ehci_base))); + + ret = board_prepare_usb(USB_INIT_HOST); + if (ret < 0) + return ret; + + + return ehci_register(dev, cr, or, &msm_ehci_ops, 0, USB_INIT_HOST); +} + +static int ehci_usb_remove(struct udevice *dev) +{ + struct msm_ehci_priv *p = dev_get_priv(dev); + phys_addr_t reg = p->base + USB_USBCMD; + int ret; + + ret = ehci_deregister(dev); + if (ret) + return ret; + + /* Stop controller. */ + writel(readl(reg) & ~USBCMD_ATTACH, reg); + + reset_usb_phy(p); + + ret = board_prepare_usb(USB_INIT_DEVICE); /* Board specific hook */ + if (ret < 0) + return ret; + + /* Reset controller */ + writel(0x00080002, reg); /* reset usb */ + mdelay(20); + /* Wait for completion */ + while (readl(reg) & 2) + ; + + return 0; +} + +static int ehci_usb_ofdata_to_platdata(struct udevice *dev) +{ + struct msm_ehci_priv *priv = dev_get_priv(dev); + + priv->base = dev_get_addr(dev); + priv->ehci_base = priv->base + MSM_USB_EHCI_OFFSET; + priv->ulpi_base = priv->base + MSM_USB_ULPI_OFFSET; + priv->ulpi_port = 0; + return 0; +} + +static const struct udevice_id ehci_usb_ids[] = { + { .compatible = "qcom,ehci-host", }, + { } +}; + +U_BOOT_DRIVER(usb_ehci) = { + .name = "ehci_msm", + .id = UCLASS_USB, + .of_match = ehci_usb_ids, + .ofdata_to_platdata = ehci_usb_ofdata_to_platdata, + .probe = ehci_usb_probe, + .remove = ehci_usb_remove, + .ops = &ehci_usb_ops, + .priv_auto_alloc_size = sizeof(struct msm_ehci_priv), + .flags = DM_FLAG_ALLOC_PRIV_DMA, +};

On Thursday, December 10, 2015 at 10:41:41 PM, Mateusz Kulikowski wrote:
This driver is able to reconfigure OTG controller into HOST mode. Board can add board-specific initialization as board_prepare_usb(). It requires USB_ULPI_VIEWPORT enabled in board configuration.
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com
Hi,
minor nits below.
[...]
diff --git a/drivers/usb/host/ehci-msm.c b/drivers/usb/host/ehci-msm.c new file mode 100644 index 0000000..d17a29a --- /dev/null +++ b/drivers/usb/host/ehci-msm.c @@ -0,0 +1,198 @@ +/*
- Qualcomm EHCI driver
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- Based on Linux driver
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <common.h> +#include <asm/gpio.h> +#include <asm-generic/errno.h> +#include <linux/compat.h> +#include <dm.h> +#include <fdtdec.h> +#include <libfdt.h> +#include <asm/io.h> +#include <usb.h> +#include <usb/ulpi.h> +#include "ehci.h"
+#ifndef CONFIG_USB_ULPI_VIEWPORT +#error Please enable CONFIG_USB_ULPI_VIEWPORT +#endif
The driver should select this in Kconfig instead of this check, right ?
+#define MSM_USB_ULPI_OFFSET 0x170 /* ULPI viewport (PHY) */ +#define MSM_USB_EHCI_OFFSET 0x100 /* Start of EHCI registers */
+/* PHY viewport regs */ +#define ULPI_MISC_A_READ 0x96 +#define ULPI_MISC_A_SET 0x97 +#define ULPI_MISC_A_CLEAR 0x98 +#define ULPI_MISC_A_VBUSVLDEXTSEL (1 << 1) +#define ULPI_MISC_A_VBUSVLDEXT (1 << 0)
+/* qcom specific registers (OTG) */ +#define USB_GENCONFIG_2 0x00A0 +#define GEN2_SESS_VLD_CTRL_EN (1 << 7)
+#define USB_USBCMD (0x0140)
Please drop the parenthesis.
btw. this register layout looks very similar to struct usb_ehci in include/usb/ehci-fsl.h , can the header be made more universal to cover your driver as well ? Then these macros here won't be needed.
+#define SESS_VLD_CTRL (1 << 25) +#define USBCMD_RESET 2 +#define USBCMD_ATTACH 1
+/* USB2_HSIC_USB_OTG_HS_BASE_USB_OTG_HS_PORTSC */ +#define USB_PORTSC 0x0184 +#define USB_SBUSCFG 0x0090 +#define USB_AHB_MODE 0x0098
+#define USB_USBMODE 0x01A8 +#define USBMODE_DEVICE 2 +#define USBMODE_HOST 3
+struct msm_ehci_priv {
- struct ehci_ctrl ctrl; /* Needed by EHCI */
- phys_addr_t base;
- phys_addr_t ehci_base;
- u32 ulpi_base;
- u32 ulpi_port;
+};
+int __weak board_prepare_usb(enum usb_init_type type) +{
- return 0;
+}
+static void setup_usb_phy(struct msm_ehci_priv *priv) +{
- struct ulpi_viewport ulpi_vp = {.port_num = priv->ulpi_port,
.viewport_addr = priv->ulpi_base};
- /* Select and enable external configuration with USB PHY */
- ulpi_write(&ulpi_vp, (u8 *)ULPI_MISC_A_SET,
ULPI_MISC_A_VBUSVLDEXTSEL | ULPI_MISC_A_VBUSVLDEXT);
+}
+static void reset_usb_phy(struct msm_ehci_priv *priv) +{
- struct ulpi_viewport ulpi_vp = {.port_num = priv->ulpi_port,
.viewport_addr = priv->ulpi_base};
- /* Disable VBUS mimicing in the controller. */
- ulpi_write(&ulpi_vp, (u8 *)ULPI_MISC_A_CLEAR,
This should be a pointer to a field in struct ulpi_regs, so the (u8 *) cast does not seem right. See for example ehci-zynq.c
ULPI_MISC_A_VBUSVLDEXTSEL | ULPI_MISC_A_VBUSVLDEXT);
+}
[...]
+static int ehci_usb_remove(struct udevice *dev) +{
- struct msm_ehci_priv *p = dev_get_priv(dev);
- phys_addr_t reg = p->base + USB_USBCMD;
- int ret;
- ret = ehci_deregister(dev);
- if (ret)
return ret;
- /* Stop controller. */
- writel(readl(reg) & ~USBCMD_ATTACH, reg);
This should use clrbits_le32() instead.
- reset_usb_phy(p);
- ret = board_prepare_usb(USB_INIT_DEVICE); /* Board specific hook */
- if (ret < 0)
return ret;
- /* Reset controller */
- writel(0x00080002, reg); /* reset usb */
- mdelay(20);
- /* Wait for completion */
- while (readl(reg) & 2)
;
Look at wait_for_bit() implementations in the U-Boot tree and avoid the unbounded waiting here please.
- return 0;
+}
+static int ehci_usb_ofdata_to_platdata(struct udevice *dev) +{
- struct msm_ehci_priv *priv = dev_get_priv(dev);
- priv->base = dev_get_addr(dev);
- priv->ehci_base = priv->base + MSM_USB_EHCI_OFFSET;
- priv->ulpi_base = priv->base + MSM_USB_ULPI_OFFSET;
- priv->ulpi_port = 0;
- return 0;
+}
+static const struct udevice_id ehci_usb_ids[] = {
- { .compatible = "qcom,ehci-host", },
- { }
+};
+U_BOOT_DRIVER(usb_ehci) = {
- .name = "ehci_msm",
- .id = UCLASS_USB,
- .of_match = ehci_usb_ids,
- .ofdata_to_platdata = ehci_usb_ofdata_to_platdata,
- .probe = ehci_usb_probe,
- .remove = ehci_usb_remove,
- .ops = &ehci_usb_ops,
- .priv_auto_alloc_size = sizeof(struct msm_ehci_priv),
- .flags = DM_FLAG_ALLOC_PRIV_DMA,
+};

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256
Hi,
Thanks for quick review;
On 11.12.2015 00:22, Marek Vasut wrote:
On Thursday, December 10, 2015 at 10:41:41 PM, Mateusz Kulikowski wrote:
[...]
+#ifndef CONFIG_USB_ULPI_VIEWPORT +#error Please enable CONFIG_USB_ULPI_VIEWPORT +#endif
The driver should select this in Kconfig instead of this check, right ?
That was my first attempt, but ULPI_VIEWPORT is not Kconfig option, and it seems it just doesn't work :(
It doesn't matter if I add it as select USB_ULPI_VIEWPORT in usb KConfig, or forcibly add CONFIG_USB_ULPI_VIEWPORT to .config
[...]
+#define USB_USBCMD (0x0140)
Please drop the parenthesis.
Doh, missed this one - surely will do it.
btw. this register layout looks very similar to struct usb_ehci in include/usb/ehci-fsl.h , can the header be made more universal to cover your driver as well ? Then these macros here won't be needed.
You're right.. in fact contrary to what I expected, Qualcomm didn't implemented their own USB controller.
It is designed by Chipidea, and PHY as far as I see is made by Synapsys.
I can use fsl headers with little exception that two registers are marked as reserved: USB_AHB_MODE (0x98) and USB_GENCONFIG_2 (0xA0)
My guess is that it's just different revision/config of IP core.
Do you think it wouldn't look awkward if I use fsl headers?
I think once this series gets to mainline we can create shared driver that will support both vendors.
I also noticed that U-Boot have ci_udc already so there is a chance I make device controller working pretty fast (but I prefer not to include it in this series yet).
[...]
- struct ulpi_viewport ulpi_vp = {.port_num = priv->ulpi_port,
.viewport_addr = priv->ulpi_base};
- /* Disable VBUS mimicing in the controller. */
- ulpi_write(&ulpi_vp, (u8 *)ULPI_MISC_A_CLEAR,
This should be a pointer to a field in struct ulpi_regs, so the (u8 *) cast does not seem right. See for example ehci-zynq.c
Perhaps I misussed ulpi_viewport code in that case;
The reason is I need to access MISC_A register (0x96+) that is not in ulpi_regs structure - afaik it's vendor-specific.
Any hints how to tackle that properly?
I can of course duplicate ulpi code, but it probably doesn't make much sense.
[...]
- /* Stop controller. */
- writel(readl(reg) & ~USBCMD_ATTACH, reg);
This should use clrbits_le32() instead.
Ok
[...]
- /* Wait for completion */
- while (readl(reg) & 2)
;
Look at wait_for_bit() implementations in the U-Boot tree and avoid the unbounded waiting here please.
Ok, btw I noticed there are 3 copies of almost the same code that does that :)
Perhaps I can add a patch to add this function to /lib as it seems it's common use case?
The following drivers would benefit: ehci-msm, zynq_gem, dwc2, ehci-mx6, ohci-lpc32xx
Regards, Mateusz

On Sunday, December 13, 2015 at 01:38:41 PM, Mateusz Kulikowski wrote:
Hi,
Thanks for quick review;
On 11.12.2015 00:22, Marek Vasut wrote:
On Thursday, December 10, 2015 at 10:41:41 PM, Mateusz Kulikowski wrote:
[...]
+#ifndef CONFIG_USB_ULPI_VIEWPORT +#error Please enable CONFIG_USB_ULPI_VIEWPORT +#endif
The driver should select this in Kconfig instead of this check, right ?
That was my first attempt, but ULPI_VIEWPORT is not Kconfig option, and it seems it just doesn't work :(
It doesn't matter if I add it as select USB_ULPI_VIEWPORT in usb KConfig, or forcibly add CONFIG_USB_ULPI_VIEWPORT to .config
I think it should be quite easily possible to add this to USB Kconfig.
[...]
+#define USB_USBCMD (0x0140)
Please drop the parenthesis.
Doh, missed this one - surely will do it.
btw. this register layout looks very similar to struct usb_ehci in include/usb/ehci-fsl.h , can the header be made more universal to cover your driver as well ? Then these macros here won't be needed.
You're right.. in fact contrary to what I expected, Qualcomm didn't implemented their own USB controller.
Well building a chip is like going to a IP block supermarket afterall ;-)
It is designed by Chipidea, and PHY as far as I see is made by Synapsys.
All of the drivers/usb/host/ehci-{mxs,mx5,mx6,vf}.c are also chipidea cores. MXS and MX6 have chipidea PHY too.
I can use fsl headers with little exception that two registers are marked as reserved: USB_AHB_MODE (0x98) and USB_GENCONFIG_2 (0xA0)
My guess is that it's just different revision/config of IP core.
Do you think it wouldn't look awkward if I use fsl headers?
Just rename them to ehci-ci.h, that should be the quickest.
I think once this series gets to mainline we can create shared driver that will support both vendors.
I'm all for that.
I also noticed that U-Boot have ci_udc already so there is a chance I make device controller working pretty fast (but I prefer not to include it in this series yet).
Correct, that works too. I think it was also tested on some marvell chip.
[...]
- struct ulpi_viewport ulpi_vp = {.port_num = priv->ulpi_port,
.viewport_addr = priv->ulpi_base};
- /* Disable VBUS mimicing in the controller. */
- ulpi_write(&ulpi_vp, (u8 *)ULPI_MISC_A_CLEAR,
This should be a pointer to a field in struct ulpi_regs, so the (u8 *) cast does not seem right. See for example ehci-zynq.c
Perhaps I misussed ulpi_viewport code in that case;
The reason is I need to access MISC_A register (0x96+) that is not in ulpi_regs structure - afaik it's vendor-specific.
Any hints how to tackle that properly?
I can of course duplicate ulpi code, but it probably doesn't make much sense.
I don't have a better suggestion, sorry. Let's keep this as-is unless someone can come up with something better. Code duplication is not a good idea, so we won't do that.
[...]
- /* Stop controller. */
- writel(readl(reg) & ~USBCMD_ATTACH, reg);
This should use clrbits_le32() instead.
Ok
[...]
- /* Wait for completion */
- while (readl(reg) & 2)
;
Look at wait_for_bit() implementations in the U-Boot tree and avoid the unbounded waiting here please.
Ok, btw I noticed there are 3 copies of almost the same code that does that :)
Perhaps I can add a patch to add this function to /lib as it seems it's common use case?
That'd be great, it was on my list to do it for a while, but I didn't get around doing that yet.
The following drivers would benefit: ehci-msm, zynq_gem, dwc2, ehci-mx6, ohci-lpc32xx
Thanks!

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256
Hi Marek,
On 13.12.2015 16:48, Marek Vasut wrote:
On Sunday, December 13, 2015 at 01:38:41 PM, Mateusz Kulikowski wrote:
[...]
It doesn't matter if I add it as select USB_ULPI_VIEWPORT in usb KConfig, or forcibly add CONFIG_USB_ULPI_VIEWPORT to .config
I think it should be quite easily possible to add this to USB Kconfig.
Will do.
I can use fsl headers with little exception that two registers are marked as reserved: USB_AHB_MODE (0x98) and USB_GENCONFIG_2 (0xA0)
My guess is that it's just different revision/config of IP core.
Do you think it wouldn't look awkward if I use fsl headers?
Just rename them to ehci-ci.h, that should be the quickest.
Will do.
[...]
- struct ulpi_viewport ulpi_vp = {.port_num = priv->ulpi_port,
.viewport_addr = priv->ulpi_base};
- /* Disable VBUS mimicing in the controller. */
- ulpi_write(&ulpi_vp, (u8 *)ULPI_MISC_A_CLEAR,
This should be a pointer to a field in struct ulpi_regs, so the (u8 *) cast does not seem right. See for example ehci-zynq.c
Perhaps I misussed ulpi_viewport code in that case;
The reason is I need to access MISC_A register (0x96+) that is not in ulpi_regs structure - afaik it's vendor-specific.
Any hints how to tackle that properly?
I can of course duplicate ulpi code, but it probably doesn't make much sense.
I don't have a better suggestion, sorry. Let's keep this as-is unless someone can come up with something better. Code duplication is not a good idea, so we won't do that.
OK
Thanks, Mateusz

Qualcom processors use proprietary bus to talk with PMIC devices - SPMI (System Power Management Interface). On wiring level it is similar to I2C, but on protocol level, it's multi-master and has simple autodetection capabilities. This commit adds simple uclass that provides bus read/write interface.
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com ---
drivers/Kconfig | 2 ++ drivers/Makefile | 1 + drivers/spmi/Kconfig | 10 +++++++++ drivers/spmi/Makefile | 7 ++++++ drivers/spmi/spmi-uclass.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++ include/dm/uclass-id.h | 1 + include/spmi/spmi.h | 44 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 118 insertions(+) create mode 100644 drivers/spmi/Kconfig create mode 100644 drivers/spmi/Makefile create mode 100644 drivers/spmi/spmi-uclass.c create mode 100644 include/spmi/spmi.h
diff --git a/drivers/Kconfig b/drivers/Kconfig index c481e93..4cbe976 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -52,6 +52,8 @@ source "drivers/remoteproc/Kconfig"
source "drivers/rtc/Kconfig"
+source "drivers/spmi/Kconfig" + source "drivers/serial/Kconfig"
source "drivers/sound/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index c9031f2..7ae72c0 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -53,6 +53,7 @@ obj-y += pcmcia/ obj-y += dfu/ obj-y += rtc/ obj-y += sound/ +obj-y += spmi/ obj-y += timer/ obj-y += tpm/ obj-y += twserial/ diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig new file mode 100644 index 0000000..2d8d78b --- /dev/null +++ b/drivers/spmi/Kconfig @@ -0,0 +1,10 @@ +menu "SPMI support" + +config DM_SPMI + bool "Enable SPMI bus support" + depends on DM + ---help--- + Select this to enable to support SPMI bus. + SPMI (System Power Management Interface) bus is used + to connect PMIC devices on various SoCs. +endmenu diff --git a/drivers/spmi/Makefile b/drivers/spmi/Makefile new file mode 100644 index 0000000..2015b1a --- /dev/null +++ b/drivers/spmi/Makefile @@ -0,0 +1,7 @@ +# +# (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com +# +# SPDX-License-Identifier: GPL-2.0+ +# + +obj-$(CONFIG_DM_SPMI) += spmi-uclass.o diff --git a/drivers/spmi/spmi-uclass.c b/drivers/spmi/spmi-uclass.c new file mode 100644 index 0000000..981da68 --- /dev/null +++ b/drivers/spmi/spmi-uclass.c @@ -0,0 +1,53 @@ +/* + * SPMI bus uclass driver + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <fdtdec.h> +#include <errno.h> +#include <dm.h> +#include <dm/root.h> +#include <vsprintf.h> +#include <dm/lists.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> +#include <linux/ctype.h> +#include <spmi/spmi.h> + +DECLARE_GLOBAL_DATA_PTR; + +int spmi_reg_read(struct udevice *dev, int usid, int pid, int reg) +{ + const struct dm_spmi_ops *ops = dev_get_driver_ops(dev); + + if (!ops || !ops->read) + return -ENOSYS; + + return ops->read(dev, usid, pid, reg); +} + +int spmi_reg_write(struct udevice *dev, int usid, int pid, int reg, + uint8_t value) +{ + const struct dm_spmi_ops *ops = dev_get_driver_ops(dev); + + if (!ops || !ops->write) + return -ENOSYS; + + return ops->write(dev, usid, pid, reg, value); +} + +static int msm_spmi_post_bind(struct udevice *dev) +{ + return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false); +} + +UCLASS_DRIVER(spmi) = { + .id = UCLASS_SPMI, + .name = "spmi", + .post_bind = msm_spmi_post_bind, +}; diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 27fa0b6..772e780 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -57,6 +57,7 @@ enum uclass_id { UCLASS_RTC, /* Real time clock device */ UCLASS_SERIAL, /* Serial UART */ UCLASS_SPI, /* SPI bus */ + UCLASS_SPMI, /* System Power Management Interface bus */ UCLASS_SPI_FLASH, /* SPI flash */ UCLASS_SPI_GENERIC, /* Generic SPI flash target */ UCLASS_SYSCON, /* System configuration device */ diff --git a/include/spmi/spmi.h b/include/spmi/spmi.h new file mode 100644 index 0000000..e06a79e --- /dev/null +++ b/include/spmi/spmi.h @@ -0,0 +1,44 @@ +#ifndef _SPMI_SPMI_H +#define _SPMI_SPMI_H + +/** + * struct dm_spmi_ops - SPMI device I/O interface + * + * Should be implemented by UCLASS_SPMI device drivers. The standard + * device operations provides the I/O interface for it's childs. + * + * @read: read register + * @write: write register + */ +struct dm_spmi_ops { + int (*read)(struct udevice *dev, int usid, int pid, int reg); + int (*write)(struct udevice *dev, int usid, int pid, int reg, + uint8_t value); +}; + +/** + * spmi_reg_read() - read a register from specific slave/peripheral + * + * @dev: SPMI bus to read + * @usid SlaveID + * @pid Peripheral ID + * @reg: Register to read + * @return value read on success or negative value of errno. + */ +int spmi_reg_read(struct udevice *dev, int usid, int pid, int reg); + +/** + * spmi_reg_write() - write a register of specific slave/peripheral + * + * @dev: SPMI bus to write + * @usid SlaveID + * @pid Peripheral ID + * @reg: Register to write + * @value: Value to write + * @return 0 on success or negative value of errno. + */ +int spmi_reg_write(struct udevice *dev, int usid, int pid, int reg, + uint8_t value); + +#endif +

Hi Matheusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
Qualcom processors use proprietary bus to talk with PMIC devices - SPMI (System Power Management Interface). On wiring level it is similar to I2C, but on protocol level, it's multi-master and has simple autodetection capabilities. This commit adds simple uclass that provides bus read/write interface.
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com
drivers/Kconfig | 2 ++ drivers/Makefile | 1 + drivers/spmi/Kconfig | 10 +++++++++ drivers/spmi/Makefile | 7 ++++++ drivers/spmi/spmi-uclass.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++ include/dm/uclass-id.h | 1 + include/spmi/spmi.h | 44 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 118 insertions(+) create mode 100644 drivers/spmi/Kconfig create mode 100644 drivers/spmi/Makefile create mode 100644 drivers/spmi/spmi-uclass.c create mode 100644 include/spmi/spmi.h
diff --git a/drivers/Kconfig b/drivers/Kconfig index c481e93..4cbe976 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -52,6 +52,8 @@ source "drivers/remoteproc/Kconfig"
source "drivers/rtc/Kconfig"
+source "drivers/spmi/Kconfig"
source "drivers/serial/Kconfig"
source "drivers/sound/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index c9031f2..7ae72c0 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -53,6 +53,7 @@ obj-y += pcmcia/ obj-y += dfu/ obj-y += rtc/ obj-y += sound/ +obj-y += spmi/ obj-y += timer/ obj-y += tpm/ obj-y += twserial/ diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig new file mode 100644 index 0000000..2d8d78b --- /dev/null +++ b/drivers/spmi/Kconfig @@ -0,0 +1,10 @@ +menu "SPMI support"
+config DM_SPMI
bool "Enable SPMI bus support"
depends on DM
---help---
Select this to enable to support SPMI bus.
SPMI (System Power Management Interface) bus is used
to connect PMIC devices on various SoCs.
+endmenu diff --git a/drivers/spmi/Makefile b/drivers/spmi/Makefile new file mode 100644 index 0000000..2015b1a --- /dev/null +++ b/drivers/spmi/Makefile @@ -0,0 +1,7 @@ +# +# (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com +# +# SPDX-License-Identifier: GPL-2.0+ +#
+obj-$(CONFIG_DM_SPMI) += spmi-uclass.o diff --git a/drivers/spmi/spmi-uclass.c b/drivers/spmi/spmi-uclass.c new file mode 100644 index 0000000..981da68 --- /dev/null +++ b/drivers/spmi/spmi-uclass.c @@ -0,0 +1,53 @@ +/*
- SPMI bus uclass driver
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <common.h> +#include <fdtdec.h> +#include <errno.h> +#include <dm.h> +#include <dm/root.h> +#include <vsprintf.h> +#include <dm/lists.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> +#include <linux/ctype.h> +#include <spmi/spmi.h>
Fix ordering
+DECLARE_GLOBAL_DATA_PTR;
+int spmi_reg_read(struct udevice *dev, int usid, int pid, int reg) +{
const struct dm_spmi_ops *ops = dev_get_driver_ops(dev);
if (!ops || !ops->read)
return -ENOSYS;
return ops->read(dev, usid, pid, reg);
+}
+int spmi_reg_write(struct udevice *dev, int usid, int pid, int reg,
uint8_t value)
+{
const struct dm_spmi_ops *ops = dev_get_driver_ops(dev);
if (!ops || !ops->write)
return -ENOSYS;
return ops->write(dev, usid, pid, reg, value);
+}
+static int msm_spmi_post_bind(struct udevice *dev) +{
return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false);
+}
+UCLASS_DRIVER(spmi) = {
.id = UCLASS_SPMI,
.name = "spmi",
.post_bind = msm_spmi_post_bind,
+}; diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 27fa0b6..772e780 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -57,6 +57,7 @@ enum uclass_id { UCLASS_RTC, /* Real time clock device */ UCLASS_SERIAL, /* Serial UART */ UCLASS_SPI, /* SPI bus */
UCLASS_SPMI, /* System Power Management Interface bus */ UCLASS_SPI_FLASH, /* SPI flash */ UCLASS_SPI_GENERIC, /* Generic SPI flash target */ UCLASS_SYSCON, /* System configuration device */
diff --git a/include/spmi/spmi.h b/include/spmi/spmi.h new file mode 100644 index 0000000..e06a79e --- /dev/null +++ b/include/spmi/spmi.h @@ -0,0 +1,44 @@ +#ifndef _SPMI_SPMI_H +#define _SPMI_SPMI_H
+/**
- struct dm_spmi_ops - SPMI device I/O interface
- Should be implemented by UCLASS_SPMI device drivers. The standard
- device operations provides the I/O interface for it's childs.
- @read: read register
- @write: write register
You should describe the args also.
What is different between this and I2C? Could you use that uclass? It seems to have an additional address value (pid)- is that right?
If you do end up with a new uclass, please add a sandbox driver for it and a test.
- */
+struct dm_spmi_ops {
int (*read)(struct udevice *dev, int usid, int pid, int reg);
int (*write)(struct udevice *dev, int usid, int pid, int reg,
uint8_t value);
+};
+/**
- spmi_reg_read() - read a register from specific slave/peripheral
- @dev: SPMI bus to read
- @usid SlaveID
- @pid Peripheral ID
- @reg: Register to read
- @return value read on success or negative value of errno.
- */
+int spmi_reg_read(struct udevice *dev, int usid, int pid, int reg);
+/**
- spmi_reg_write() - write a register of specific slave/peripheral
- @dev: SPMI bus to write
- @usid SlaveID
- @pid Peripheral ID
- @reg: Register to write
- @value: Value to write
- @return 0 on success or negative value of errno.
- */
+int spmi_reg_write(struct udevice *dev, int usid, int pid, int reg,
uint8_t value);
+#endif
-- 2.5.0
Regards, Simon

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256
Hi Simon,
On 15.12.2015 19:58, Simon Glass wrote:
Hi Matheusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
[...]
+/**
- struct dm_spmi_ops - SPMI device I/O interface
- Should be implemented by UCLASS_SPMI device drivers. The standard
- device operations provides the I/O interface for it's childs.
- @read: read register
- @write: write register
You should describe the args also.
OK
What is different between this and I2C? Could you use that uclass? It seems to have an additional address value (pid)- is that right?
That's a difficult question, as my knowledge about this bus is based only on drivers/SoC documentation/speculation (bus spec is not public :()
At wiring level it looks exactly like multi-master I2C On logical level: - - Each bus can have several slaves (up to 16). - - Each slave can have several peripherals (up to 256), - - Each peripheral can have up to 256 registers (I think each is 8-bit wide). - - Each peripheral have 2 ID registers (type/subtype) that can be used for probing
PM8916 implements two slaves (but for some reason in Linux they are presented as 2 sibling nodes on SPMI bus).
But this is device perspective.
On SoC side, implementation is different and I fully don't understand all rationale behind it.
SoC has some kind of arbiter with 127 channels (2x - separate for reads called "observers" and separate for writers).
Each peripheral (not slave) has assigned single channel that I have to map in probe(). This means that (as far as I understand) there can be no more than 127 peripherals on single bus.
Weird think is that while doing reads/writes I have to use not only channel #, but also full slave/peripheral/register address.
Perhaps my driver oversimplified SPMI, or maybe SPMI is just I2C, using some simple protocol to charge royalty fees ;)
If you do end up with a new uclass, please add a sandbox driver for it and a test.
OK
Thanks, Mateusz

Support SPMI arbiter on Qualcomm Snapdragon devices.
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com ---
drivers/spmi/Kconfig | 6 ++ drivers/spmi/Makefile | 1 + drivers/spmi/spmi-msm.c | 183 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 drivers/spmi/spmi-msm.c
diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig index 2d8d78b..4f17df0 100644 --- a/drivers/spmi/Kconfig +++ b/drivers/spmi/Kconfig @@ -7,4 +7,10 @@ config DM_SPMI Select this to enable to support SPMI bus. SPMI (System Power Management Interface) bus is used to connect PMIC devices on various SoCs. + +config SPMI_MSM + boolean "Support Qualcomm SPMI bus" + depends on DM_SPMI + ---help--- + Support SPMI implementation found on Qualcomm Snapdragon SoCs. endmenu diff --git a/drivers/spmi/Makefile b/drivers/spmi/Makefile index 2015b1a..419fe1d 100644 --- a/drivers/spmi/Makefile +++ b/drivers/spmi/Makefile @@ -5,3 +5,4 @@ #
obj-$(CONFIG_DM_SPMI) += spmi-uclass.o +obj-$(CONFIG_SPMI_MSM) += spmi-msm.o diff --git a/drivers/spmi/spmi-msm.c b/drivers/spmi/spmi-msm.c new file mode 100644 index 0000000..6781a92 --- /dev/null +++ b/drivers/spmi/spmi-msm.c @@ -0,0 +1,183 @@ +/* + * Qualcomm SPMI bus driver + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + * + * Loosely based on Little Kernel driver + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <asm/io.h> +#include <common.h> +#include <fdtdec.h> +#include <errno.h> +#include <dm.h> +#include <spmi/spmi.h> + +DECLARE_GLOBAL_DATA_PTR; + +#define ARB_CHANNEL_OFFSET(n) (0x800 + 0x4 * (n)) +#define SPMI_CH_OFFSET(chnl) ((chnl) * 0x8000) + +#define SPMI_REG_CMD0 0x0 +#define SPMI_REG_CONFIG 0x4 +#define SPMI_REG_STATUS 0x8 +#define SPMI_REG_WDATA 0x10 +#define SPMI_REG_RDATA 0x18 + +#define SPMI_CMD_OPCODE_SHIFT 27 +#define SPMI_CMD_SLAVE_ID_SHIFT 20 +#define SPMI_CMD_ADDR_SHIFT 12 +#define SPMI_CMD_ADDR_OFFSET_SHIFT 4 +#define SPMI_CMD_BYTE_CNT_SHIFT 0 + +#define SPMI_CMD_EXT_REG_WRITE_LONG 0x00 +#define SPMI_CMD_EXT_REG_READ_LONG 0x01 + +#define SPMI_STATUS_DONE 0x1 + +#define SPMI_MAX_CHANNELS 128 +#define SPMI_MAX_SLAVES 16 +#define SPMI_MAX_PERIPH 256 + +struct msm_spmi_priv { + phys_addr_t arb_core; /* ARB Core base */ + phys_addr_t spmi_core; /* SPMI core */ + phys_addr_t spmi_obs; /* SPMI observer */ + /* SPMI channel map */ + uint8_t channel_map[SPMI_MAX_SLAVES][SPMI_MAX_PERIPH]; +}; + +static int msm_spmi_write(struct udevice *dev, int usid, int pid, int off, + uint8_t val) +{ + struct msm_spmi_priv *p = dev_get_priv(dev); + unsigned channel; + uint32_t reg = 0; + + if (usid >= SPMI_MAX_SLAVES) + return -EIO; + if (pid >= SPMI_MAX_PERIPH) + return -EIO; + + channel = p->channel_map[usid][pid]; + + /* Disable IRQ mode for the current channel*/ + writel(0x0, p->spmi_core + SPMI_CH_OFFSET(channel) + SPMI_REG_CONFIG); + + /* Write single byte */ + writel(val, p->spmi_core + SPMI_CH_OFFSET(channel) + SPMI_REG_WDATA); + + /* Prepare write command */ + reg |= SPMI_CMD_EXT_REG_WRITE_LONG << SPMI_CMD_OPCODE_SHIFT; + reg |= (usid << SPMI_CMD_SLAVE_ID_SHIFT); + reg |= (pid << SPMI_CMD_ADDR_SHIFT); + reg |= (off << SPMI_CMD_ADDR_OFFSET_SHIFT); + reg |= 1; /* byte count */ + + /* Send write command */ + writel(reg, p->spmi_core + SPMI_CH_OFFSET(channel) + SPMI_REG_CMD0); + + /* Wait till CMD DONE status */ + reg = 0; + while (!reg) { + reg = readl(p->spmi_core + SPMI_CH_OFFSET(channel) + + SPMI_REG_STATUS); + } + + if (reg ^ SPMI_STATUS_DONE) { + printf("SPMI write failure.\n"); + return -EIO; + } + + return 0; +} + +static int msm_spmi_read(struct udevice *dev, int usid, int pid, int off) +{ + struct msm_spmi_priv *p = dev_get_priv(dev); + unsigned channel; + uint32_t reg = 0; + + if (usid >= SPMI_MAX_SLAVES) + return -EIO; + if (pid >= SPMI_MAX_PERIPH) + return -EIO; + + channel = p->channel_map[usid][pid]; + + /* Disable IRQ mode for the current channel*/ + writel(0x0, p->spmi_obs + SPMI_CH_OFFSET(channel) + SPMI_REG_CONFIG); + + /* Prepare read command */ + reg |= SPMI_CMD_EXT_REG_READ_LONG << SPMI_CMD_OPCODE_SHIFT; + reg |= (usid << SPMI_CMD_SLAVE_ID_SHIFT); + reg |= (pid << SPMI_CMD_ADDR_SHIFT); + reg |= (off << SPMI_CMD_ADDR_OFFSET_SHIFT); + reg |= 1; /* byte count */ + + /* Request read */ + writel(reg, p->spmi_obs + SPMI_CH_OFFSET(channel) + SPMI_REG_CMD0); + + /* Wait till CMD DONE status */ + reg = 0; + while (!reg) { + reg = readl(p->spmi_obs + SPMI_CH_OFFSET(channel) + + SPMI_REG_STATUS); + } + + if (reg ^ SPMI_STATUS_DONE) { + printf("SPMI read failure.\n"); + return -EIO; + } + + /* Read the data */ + return readl(p->spmi_obs + SPMI_CH_OFFSET(channel) + SPMI_REG_RDATA) & + 0xFF; +} + +static struct dm_spmi_ops msm_spmi_ops = { + .read = msm_spmi_read, + .write = msm_spmi_write, +}; + +static int msm_spmi_probe(struct udevice *dev) +{ + struct msm_spmi_priv *priv = dev_get_priv(dev); + int i; + + priv->arb_core = dev_get_addr(dev); + priv->spmi_core = fdtdec_get_addr_size_auto_parent(gd->fdt_blob, + dev->parent->of_offset, + dev->of_offset, "reg", + 1, NULL); + priv->spmi_obs = fdtdec_get_addr_size_auto_parent(gd->fdt_blob, + dev->parent->of_offset, + dev->of_offset, "reg", + 2, NULL); + + /* Scan peripherals connected to each SPMI channel */ + for (i = 0; i < SPMI_MAX_CHANNELS ; i++) { + uint32_t periph = readl(priv->arb_core + ARB_CHANNEL_OFFSET(i)); + uint8_t slave_id = (periph & 0xf0000) >> 16; + uint8_t pid = (periph & 0xff00) >> 8; + + priv->channel_map[slave_id][pid] = i; + } + return 0; +} + +static const struct udevice_id msm_spmi_ids[] = { + { .compatible = "qcom,spmi-pmic-arb" }, + { } +}; + +U_BOOT_DRIVER(msm_spmi) = { + .name = "msm_spmi", + .id = UCLASS_SPMI, + .of_match = msm_spmi_ids, + .ops = &msm_spmi_ops, + .probe = msm_spmi_probe, + .priv_auto_alloc_size = sizeof(struct msm_spmi_priv), +};

This PMIC is connected on SPMI bus so needs SPMI support enabled.
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com ---
drivers/power/pmic/Kconfig | 14 +++++++ drivers/power/pmic/Makefile | 1 + drivers/power/pmic/pm8916.c | 92 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 drivers/power/pmic/pm8916.c
diff --git a/drivers/power/pmic/Kconfig b/drivers/power/pmic/Kconfig index fb29843..eb1dde0 100644 --- a/drivers/power/pmic/Kconfig +++ b/drivers/power/pmic/Kconfig @@ -89,3 +89,17 @@ config PMIC_TPS65090 FETs and a battery charger. This driver provides register access only, and you can enable the regulator/charger drivers separately if required. + +config PMIC_PM8916 + bool "Enable Driver Model for Qualcomm PM8916 PMIC" + depends on DM_PMIC + ---help--- + The PM8916 is a PMIC connected to one (or several) processors + with SPMI bus. It has 2 slaves with several peripherals: + - 18x LDO + - 4x GPIO + - Power and Reset buttons + - Watchdog + - RTC + - Vibrator drivers + - Others diff --git a/drivers/power/pmic/Makefile b/drivers/power/pmic/Makefile index 91e78f8..ac653c7 100644 --- a/drivers/power/pmic/Makefile +++ b/drivers/power/pmic/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_DM_PMIC_SANDBOX) += sandbox.o i2c_pmic_emul.o obj-$(CONFIG_PMIC_ACT8846) += act8846.o obj-$(CONFIG_PMIC_TPS65090) += tps65090.o obj-$(CONFIG_PMIC_S5M8767) += s5m8767.o +obj-$(CONFIG_PMIC_PM8916) += pm8916.o
obj-$(CONFIG_POWER_LTC3676) += pmic_ltc3676.o obj-$(CONFIG_POWER_MAX77696) += pmic_max77696.o diff --git a/drivers/power/pmic/pm8916.c b/drivers/power/pmic/pm8916.c new file mode 100644 index 0000000..5fe50ef --- /dev/null +++ b/drivers/power/pmic/pm8916.c @@ -0,0 +1,92 @@ +/* + * Qualcomm pm8916 pmic driver + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + * + * SPDX-License-Identifier: GPL-2.0+ + */ +#include <common.h> +#include <dm.h> +#include <asm/io.h> +#include <linux/bitops.h> +#include <power/pmic.h> +#include <spmi/spmi.h> +#include <asm/gpio.h> +#include <dm/root.h> + +DECLARE_GLOBAL_DATA_PTR; + +#define EXTRACT_PID(x) (((x) >> 8) & 0xFF) +#define EXTRACT_REG(x) ((x) & 0xFF) + +struct pm8916_priv { + uint16_t usid; /* Slave ID on SPMI bus */ +}; + +static int pm8916_reg_count(struct udevice *dev) +{ + return 0xFFFF; +} + +static int pm8916_write(struct udevice *dev, uint reg, const uint8_t *buff, + int len) +{ + struct pm8916_priv *priv = dev_get_priv(dev); + + if (len != 1) + return -EINVAL; + + return spmi_reg_write(dev->parent, priv->usid, EXTRACT_PID(reg), + EXTRACT_REG(reg), *buff); +} + +static int pm8916_read(struct udevice *dev, uint reg, uint8_t *buff, int len) +{ + struct pm8916_priv *priv = dev_get_priv(dev); + int val; + + if (len != 1) + return -EINVAL; + + val = spmi_reg_read(dev->parent, priv->usid, EXTRACT_PID(reg), + EXTRACT_REG(reg)); + + if (val < 0) + return val; + *buff = val; + return 0; +} + +static struct dm_pmic_ops pm8916_ops = { + .reg_count = pm8916_reg_count, + .read = pm8916_read, + .write = pm8916_write, +}; + +static const struct udevice_id pm8916_ids[] = { + { .compatible = "qcom,spmi-pmic" }, + { } +}; + +static int pm8916_probe(struct udevice *dev) +{ + struct pm8916_priv *priv = dev_get_priv(dev); + priv->usid = dev_get_addr(dev); + return 0; +} + + +static int pm8916_bind(struct udevice *dev) +{ + return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false); +} + +U_BOOT_DRIVER(pmic_pm8916) = { + .name = "pmic_pm8916", + .id = UCLASS_PMIC, + .of_match = pm8916_ids, + .bind = pm8916_bind, + .probe = pm8916_probe, + .ops = &pm8916_ops, + .priv_auto_alloc_size = sizeof(struct pm8916_priv), +};

Hi Mateusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
This PMIC is connected on SPMI bus so needs SPMI support enabled.
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com
drivers/power/pmic/Kconfig | 14 +++++++ drivers/power/pmic/Makefile | 1 + drivers/power/pmic/pm8916.c | 92 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 drivers/power/pmic/pm8916.c
diff --git a/drivers/power/pmic/Kconfig b/drivers/power/pmic/Kconfig index fb29843..eb1dde0 100644 --- a/drivers/power/pmic/Kconfig +++ b/drivers/power/pmic/Kconfig @@ -89,3 +89,17 @@ config PMIC_TPS65090 FETs and a battery charger. This driver provides register access only, and you can enable the regulator/charger drivers separately if required.
+config PMIC_PM8916
bool "Enable Driver Model for Qualcomm PM8916 PMIC"
depends on DM_PMIC
---help---
The PM8916 is a PMIC connected to one (or several) processors
with SPMI bus. It has 2 slaves with several peripherals:
- 18x LDO
- 4x GPIO
- Power and Reset buttons
- Watchdog
- RTC
- Vibrator drivers
- Others
Nice help!
diff --git a/drivers/power/pmic/Makefile b/drivers/power/pmic/Makefile index 91e78f8..ac653c7 100644 --- a/drivers/power/pmic/Makefile +++ b/drivers/power/pmic/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_DM_PMIC_SANDBOX) += sandbox.o i2c_pmic_emul.o obj-$(CONFIG_PMIC_ACT8846) += act8846.o obj-$(CONFIG_PMIC_TPS65090) += tps65090.o obj-$(CONFIG_PMIC_S5M8767) += s5m8767.o +obj-$(CONFIG_PMIC_PM8916) += pm8916.o
obj-$(CONFIG_POWER_LTC3676) += pmic_ltc3676.o obj-$(CONFIG_POWER_MAX77696) += pmic_max77696.o diff --git a/drivers/power/pmic/pm8916.c b/drivers/power/pmic/pm8916.c new file mode 100644 index 0000000..5fe50ef --- /dev/null +++ b/drivers/power/pmic/pm8916.c @@ -0,0 +1,92 @@ +/*
- Qualcomm pm8916 pmic driver
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <common.h> +#include <dm.h> +#include <asm/io.h> +#include <linux/bitops.h> +#include <power/pmic.h> +#include <spmi/spmi.h> +#include <asm/gpio.h> +#include <dm/root.h>
Fix ordering
+DECLARE_GLOBAL_DATA_PTR;
+#define EXTRACT_PID(x) (((x) >> 8) & 0xFF) +#define EXTRACT_REG(x) ((x) & 0xFF)
Can you define
#define PID_SHIFT 8 #define PID_MASK (0xff << PID_SHIFT)
and use those? I prefer lower-case hex but there is no convention on that.
+struct pm8916_priv {
uint16_t usid; /* Slave ID on SPMI bus */
+};
+static int pm8916_reg_count(struct udevice *dev) +{
return 0xFFFF;
+}
+static int pm8916_write(struct udevice *dev, uint reg, const uint8_t *buff,
int len)
+{
struct pm8916_priv *priv = dev_get_priv(dev);
if (len != 1)
return -EINVAL;
return spmi_reg_write(dev->parent, priv->usid, EXTRACT_PID(reg),
EXTRACT_REG(reg), *buff);
+}
+static int pm8916_read(struct udevice *dev, uint reg, uint8_t *buff, int len) +{
struct pm8916_priv *priv = dev_get_priv(dev);
int val;
if (len != 1)
return -EINVAL;
val = spmi_reg_read(dev->parent, priv->usid, EXTRACT_PID(reg),
EXTRACT_REG(reg));
if (val < 0)
return val;
*buff = val;
return 0;
+}
+static struct dm_pmic_ops pm8916_ops = {
.reg_count = pm8916_reg_count,
.read = pm8916_read,
.write = pm8916_write,
+};
+static const struct udevice_id pm8916_ids[] = {
{ .compatible = "qcom,spmi-pmic" },
Binding doc?
{ }
+};
+static int pm8916_probe(struct udevice *dev) +{
struct pm8916_priv *priv = dev_get_priv(dev);
blank line
priv->usid = dev_get_addr(dev);
return 0;
+}
+static int pm8916_bind(struct udevice *dev) +{
return dm_scan_fdt_node(dev, gd->fdt_blob, dev->of_offset, false);
+}
+U_BOOT_DRIVER(pmic_pm8916) = {
.name = "pmic_pm8916",
.id = UCLASS_PMIC,
.of_match = pm8916_ids,
.bind = pm8916_bind,
.probe = pm8916_probe,
.ops = &pm8916_ops,
.priv_auto_alloc_size = sizeof(struct pm8916_priv),
+};
2.5.0
Regards, Simon

This driver supports GPIOs present on PM8916 PMIC. There are 2 device drivers inside: - GPIO driver (4 "generic" GPIOs) - Keypad driver that presents itself as GPIO with 2 inputs (power and reset)
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com ---
drivers/gpio/Kconfig | 7 ++ drivers/gpio/Makefile | 1 + drivers/gpio/pm8916_gpio.c | 306 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 314 insertions(+) create mode 100644 drivers/gpio/pm8916_gpio.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 1d9443b..1fbdfeb 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -53,6 +53,13 @@ config MSM_GPIO help Support GPIO controllers on Qualcomm Snapdragon family of SoCs.
+config PM8916_GPIO + bool "Qualcomm PM8916 PMIC GPIO/keypad driver" + depends on DM_GPIO && PMIC_PM8916 + help + Support for GPIO1-GPIO4 pins and power/reset buttons found on + Qualcomm PM8916 PMIC. + config ROCKCHIP_GPIO bool "Rockchip GPIO driver" depends on DM_GPIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 9520b1e..aaa9f8d 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -47,3 +47,4 @@ obj-$(CONFIG_ZYNQ_GPIO) += zynq_gpio.o obj-$(CONFIG_VYBRID_GPIO) += vybrid_gpio.o obj-$(CONFIG_HIKEY_GPIO) += hi6220_gpio.o obj-$(CONFIG_MSM_GPIO) += msm_gpio.o +obj-$(CONFIG_PM8916_GPIO) += pm8916_gpio.o diff --git a/drivers/gpio/pm8916_gpio.c b/drivers/gpio/pm8916_gpio.c new file mode 100644 index 0000000..9214984 --- /dev/null +++ b/drivers/gpio/pm8916_gpio.c @@ -0,0 +1,306 @@ +/* + * Qualcomm pm8916 pmic gpio driver - part of Qualcomm PM8916 PMIC + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <asm/io.h> +#include <linux/bitops.h> +#include <power/pmic.h> +#include <spmi/spmi.h> +#include <asm/gpio.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* Register offset for each gpio */ +#define REG_OFFSET(x) ((x) * 0x100) + +/* Register maps */ +#define REG_STATUS 0x08 +#define REG_STATUS_VAL_MASK 0x1 + +/* MODE_CTL */ +#define REG_CTL 0x40 +#define REG_CTL_MODE_MASK 0x70 +#define REG_CTL_MODE_INPUT 0x00 +#define REG_CTL_MODE_INOUT 0x20 +#define REG_CTL_MODE_OUTPUT 0x10 +#define REG_CTL_OUTPUT_MASK 0x0F + +#define REG_DIG_VIN_CTL 0x41 +#define REG_DIG_VIN_VIN0 0 + +#define REG_DIG_PULL_CTL 0x42 +#define REG_DIG_PULL_NO_PU 0x5 + +#define REG_DIG_OUT_CTL 0x45 +#define REG_DIG_OUT_CTL_CMOS (0x0 << 4) +#define REG_DIG_OUT_CTL_DRIVE_L 0x1 + +#define REG_EN_CTL 0x46 +#define REG_EN_CTL_ENABLE (1 << 7) + +struct pm8916_gpio_bank { + uint16_t pid; /* Peripheral ID on SPMI bus */ +}; + +static int pm8916_gpio_direction_input(struct udevice *dev, unsigned offset) +{ + struct pm8916_gpio_bank *priv = dev_get_priv(dev); + uint32_t gpio_base = priv->pid + REG_OFFSET(offset); + int val, ret; + + /* Disable the GPIO */ + val = pmic_reg_read(dev->parent, gpio_base + REG_EN_CTL); + if (val < 0) + return val; + + val &= ~REG_EN_CTL_ENABLE; + ret = pmic_reg_write(dev->parent, gpio_base + REG_EN_CTL, val); + if (ret < 0) + return ret; + + /* Select the mode */ + val = REG_CTL_MODE_INPUT; + ret = pmic_reg_write(dev->parent, gpio_base + REG_CTL, val); + if (ret < 0) + return ret; + + /* Set the right pull (no pull) */ + ret = pmic_reg_write(dev->parent, gpio_base + REG_DIG_PULL_CTL, + REG_DIG_PULL_NO_PU); + if (ret < 0) + return ret; + + /* Select the VIN - VIN0, pin is input so it doesn't matter */ + ret = pmic_reg_write(dev->parent, gpio_base + REG_DIG_VIN_CTL, + REG_DIG_VIN_VIN0); + if (ret < 0) + return ret; + + /* Enable the GPIO */ + val = pmic_reg_read(dev->parent, gpio_base + REG_EN_CTL); + if (val < 0) + return val; + + val |= REG_EN_CTL_ENABLE; + + ret = pmic_reg_write(dev->parent, gpio_base + REG_EN_CTL, val); + return ret; +} + +static int pm8916_gpio_direction_output(struct udevice *dev, unsigned offset, + int value) +{ + struct pm8916_gpio_bank *priv = dev_get_priv(dev); + uint32_t gpio_base = priv->pid + REG_OFFSET(offset); + int val, ret; + + /* Disable the GPIO */ + val = pmic_reg_read(dev->parent, gpio_base + REG_EN_CTL); + if (val < 0) + return val; + + val &= REG_EN_CTL_ENABLE; + ret = pmic_reg_write(dev->parent, gpio_base + REG_EN_CTL, val); + if (ret < 0) + return ret; + + /* Select the mode and output value*/ + val = REG_CTL_MODE_INOUT | (value ? 1 : 0); + ret = pmic_reg_write(dev->parent, gpio_base + REG_CTL, val); + if (ret < 0) + return ret; + + /* Set the right pull */ + ret = pmic_reg_write(dev->parent, gpio_base + REG_DIG_PULL_CTL, + REG_DIG_PULL_NO_PU); + if (ret < 0) + return ret; + + /* Select the VIN - VIN0*/ + ret = pmic_reg_write(dev->parent, gpio_base + REG_DIG_VIN_CTL, + REG_DIG_VIN_VIN0); + if (ret < 0) + return ret; + + /* Set the right dig out control */ + val = REG_DIG_OUT_CTL_CMOS | REG_DIG_OUT_CTL_DRIVE_L; + ret = pmic_reg_write(dev->parent, gpio_base + REG_DIG_OUT_CTL, val); + if (ret < 0) + return ret; + + /* Enable the GPIO */ + val = pmic_reg_read(dev->parent, gpio_base + REG_EN_CTL); + if (val < 0) + return val; + + val |= REG_EN_CTL_ENABLE; + + ret = pmic_reg_write(dev->parent, gpio_base + REG_EN_CTL, val); + return ret; +} + +static int pm8916_gpio_get_function(struct udevice *dev, unsigned offset) +{ + struct pm8916_gpio_bank *priv = dev_get_priv(dev); + uint32_t gpio_base = priv->pid + REG_OFFSET(offset); + int reg; + + /* Set the output value of the gpio */ + reg = pmic_reg_read(dev->parent, gpio_base + REG_CTL); + if (reg < 0) + return reg; + + if ((reg & REG_CTL_MODE_MASK) == REG_CTL_MODE_INPUT) + return GPIOF_INPUT; + if ((reg & REG_CTL_MODE_MASK) == REG_CTL_MODE_INOUT) + return GPIOF_OUTPUT; + if ((reg & REG_CTL_MODE_MASK) == REG_CTL_MODE_OUTPUT) + return GPIOF_OUTPUT; + return GPIOF_UNKNOWN; +} + +static int pm8916_gpio_get_value(struct udevice *dev, unsigned offset) +{ + struct pm8916_gpio_bank *priv = dev_get_priv(dev); + uint32_t gpio_base = priv->pid + REG_OFFSET(offset); + int reg; + + reg = pmic_reg_read(dev->parent, gpio_base + REG_STATUS); + if (reg < 0) + return reg; + + return !!(reg & REG_STATUS_VAL_MASK); +} + +static int pm8916_gpio_set_value(struct udevice *dev, unsigned offset, + int value) +{ + struct pm8916_gpio_bank *priv = dev_get_priv(dev); + uint32_t gpio_base = priv->pid + REG_OFFSET(offset); + int reg; + + /* Set the output value of the gpio */ + reg = pmic_reg_read(dev->parent, gpio_base + REG_CTL); + if (reg < 0) + return reg; + + reg = (reg & ~REG_CTL_OUTPUT_MASK) | value; + + return pmic_reg_write(dev->parent, gpio_base + REG_CTL, reg); +} + +static const struct dm_gpio_ops pm8916_gpio_ops = { + .direction_input = pm8916_gpio_direction_input, + .direction_output = pm8916_gpio_direction_output, + .get_value = pm8916_gpio_get_value, + .set_value = pm8916_gpio_set_value, + .get_function = pm8916_gpio_get_function, +}; + +static int pm8916_gpio_probe(struct udevice *dev) +{ + struct pm8916_gpio_bank *priv = dev_get_priv(dev); + priv->pid = dev_get_addr(dev); + return 0; +} + +static int pm8916_gpio_ofdata_to_platdata(struct udevice *dev) +{ + struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); + + uc_priv->gpio_count = fdtdec_get_int(gd->fdt_blob, dev->of_offset, + "gpio-count", 0); + uc_priv->bank_name = fdt_getprop(gd->fdt_blob, dev->of_offset, + "gpio-bank-name", NULL); + if (uc_priv->bank_name == NULL) + uc_priv->bank_name = "pm8916"; + return 0; +} + +static const struct udevice_id pm8916_gpio_ids[] = { + { .compatible = "qcom,pm8916-gpio" }, + { } +}; + +U_BOOT_DRIVER(gpio_pm8916) = { + .name = "gpio_pm8916", + .id = UCLASS_GPIO, + .of_match = pm8916_gpio_ids, + .ofdata_to_platdata = pm8916_gpio_ofdata_to_platdata, + .probe = pm8916_gpio_probe, + .ops = &pm8916_gpio_ops, + .priv_auto_alloc_size = sizeof(struct pm8916_gpio_bank), +}; + + +/* Add pmic buttons as GPIO as well - there is no generic way for now */ +#define PON_INT_RT_STS 0x10 +#define KPDPWR_ON_INT_BIT 0 +#define RESIN_ON_INT_BIT 1 + +static int pm8941_pwrkey_get_function(struct udevice *dev, unsigned offset) +{ + return GPIOF_INPUT; +} + +static int pm8941_pwrkey_get_value(struct udevice *dev, unsigned offset) +{ + struct pm8916_gpio_bank *priv = dev_get_priv(dev); + + int reg = pmic_reg_read(dev->parent, priv->pid + PON_INT_RT_STS); + + if (reg < 0) + return 0; + + switch (offset) { + case 0: /* Power button */ + return (reg & BIT(KPDPWR_ON_INT_BIT)) != 0; + break; + case 1: /* Reset button */ + default: + return (reg & BIT(RESIN_ON_INT_BIT)) != 0; + break; + } +} + +static const struct dm_gpio_ops pm8941_pwrkey_ops = { + .get_value = pm8941_pwrkey_get_value, + .get_function = pm8941_pwrkey_get_function, +}; + +static int pm8941_pwrkey_probe(struct udevice *dev) +{ + struct pm8916_gpio_bank *priv = dev_get_priv(dev); + + priv->pid = dev_get_addr(dev); + return 0; +} + +static int pm8941_pwrkey_ofdata_to_platdata(struct udevice *dev) +{ + struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); + uc_priv->gpio_count = 2; + uc_priv->bank_name = "pm8916_key"; + return 0; +} + +static const struct udevice_id pm8941_pwrkey_ids[] = { + { .compatible = "qcom,pm8916-pwrkey" }, + { } +}; + +U_BOOT_DRIVER(pwrkey_pm8941) = { + .name = "pwrkey_pm8916", + .id = UCLASS_GPIO, + .of_match = pm8941_pwrkey_ids, + .ofdata_to_platdata = pm8941_pwrkey_ofdata_to_platdata, + .probe = pm8941_pwrkey_probe, + .ops = &pm8941_pwrkey_ops, + .priv_auto_alloc_size = sizeof(struct pm8916_gpio_bank), +};

Hi Mateusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
This driver supports GPIOs present on PM8916 PMIC. There are 2 device drivers inside:
- GPIO driver (4 "generic" GPIOs)
- Keypad driver that presents itself as GPIO with 2 inputs (power and reset)
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com
drivers/gpio/Kconfig | 7 ++ drivers/gpio/Makefile | 1 + drivers/gpio/pm8916_gpio.c | 306 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 314 insertions(+) create mode 100644 drivers/gpio/pm8916_gpio.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 1d9443b..1fbdfeb 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -53,6 +53,13 @@ config MSM_GPIO help Support GPIO controllers on Qualcomm Snapdragon family of SoCs.
+config PM8916_GPIO
bool "Qualcomm PM8916 PMIC GPIO/keypad driver"
depends on DM_GPIO && PMIC_PM8916
help
Support for GPIO1-GPIO4 pins and power/reset buttons found on
Qualcomm PM8916 PMIC.
config ROCKCHIP_GPIO bool "Rockchip GPIO driver" depends on DM_GPIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 9520b1e..aaa9f8d 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -47,3 +47,4 @@ obj-$(CONFIG_ZYNQ_GPIO) += zynq_gpio.o obj-$(CONFIG_VYBRID_GPIO) += vybrid_gpio.o obj-$(CONFIG_HIKEY_GPIO) += hi6220_gpio.o obj-$(CONFIG_MSM_GPIO) += msm_gpio.o +obj-$(CONFIG_PM8916_GPIO) += pm8916_gpio.o diff --git a/drivers/gpio/pm8916_gpio.c b/drivers/gpio/pm8916_gpio.c new file mode 100644 index 0000000..9214984 --- /dev/null +++ b/drivers/gpio/pm8916_gpio.c @@ -0,0 +1,306 @@ +/*
- Qualcomm pm8916 pmic gpio driver - part of Qualcomm PM8916 PMIC
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <common.h> +#include <dm.h> +#include <asm/io.h> +#include <linux/bitops.h> +#include <power/pmic.h> +#include <spmi/spmi.h> +#include <asm/gpio.h>
Ordering
+DECLARE_GLOBAL_DATA_PTR;
+/* Register offset for each gpio */ +#define REG_OFFSET(x) ((x) * 0x100)
+/* Register maps */ +#define REG_STATUS 0x08 +#define REG_STATUS_VAL_MASK 0x1
+/* MODE_CTL */ +#define REG_CTL 0x40 +#define REG_CTL_MODE_MASK 0x70 +#define REG_CTL_MODE_INPUT 0x00 +#define REG_CTL_MODE_INOUT 0x20 +#define REG_CTL_MODE_OUTPUT 0x10 +#define REG_CTL_OUTPUT_MASK 0x0F
+#define REG_DIG_VIN_CTL 0x41 +#define REG_DIG_VIN_VIN0 0
+#define REG_DIG_PULL_CTL 0x42 +#define REG_DIG_PULL_NO_PU 0x5
+#define REG_DIG_OUT_CTL 0x45 +#define REG_DIG_OUT_CTL_CMOS (0x0 << 4) +#define REG_DIG_OUT_CTL_DRIVE_L 0x1
+#define REG_EN_CTL 0x46 +#define REG_EN_CTL_ENABLE (1 << 7)
+struct pm8916_gpio_bank {
uint16_t pid; /* Peripheral ID on SPMI bus */
+};
+static int pm8916_gpio_direction_input(struct udevice *dev, unsigned offset) +{
struct pm8916_gpio_bank *priv = dev_get_priv(dev);
uint32_t gpio_base = priv->pid + REG_OFFSET(offset);
int val, ret;
/* Disable the GPIO */
val = pmic_reg_read(dev->parent, gpio_base + REG_EN_CTL);
if (val < 0)
return val;
val &= ~REG_EN_CTL_ENABLE;
ret = pmic_reg_write(dev->parent, gpio_base + REG_EN_CTL, val);
if (ret < 0)
return ret;
Can you use pmic_clrsetbits()?
/* Select the mode */
val = REG_CTL_MODE_INPUT;
ret = pmic_reg_write(dev->parent, gpio_base + REG_CTL, val);
if (ret < 0)
return ret;
/* Set the right pull (no pull) */
ret = pmic_reg_write(dev->parent, gpio_base + REG_DIG_PULL_CTL,
REG_DIG_PULL_NO_PU);
if (ret < 0)
return ret;
/* Select the VIN - VIN0, pin is input so it doesn't matter */
ret = pmic_reg_write(dev->parent, gpio_base + REG_DIG_VIN_CTL,
REG_DIG_VIN_VIN0);
if (ret < 0)
return ret;
/* Enable the GPIO */
val = pmic_reg_read(dev->parent, gpio_base + REG_EN_CTL);
if (val < 0)
return val;
val |= REG_EN_CTL_ENABLE;
ret = pmic_reg_write(dev->parent, gpio_base + REG_EN_CTL, val);
return ret;
+}
+static int pm8916_gpio_direction_output(struct udevice *dev, unsigned offset,
int value)
+{
struct pm8916_gpio_bank *priv = dev_get_priv(dev);
uint32_t gpio_base = priv->pid + REG_OFFSET(offset);
int val, ret;
/* Disable the GPIO */
val = pmic_reg_read(dev->parent, gpio_base + REG_EN_CTL);
if (val < 0)
return val;
val &= REG_EN_CTL_ENABLE;
ret = pmic_reg_write(dev->parent, gpio_base + REG_EN_CTL, val);
if (ret < 0)
return ret;
/* Select the mode and output value*/
val = REG_CTL_MODE_INOUT | (value ? 1 : 0);
ret = pmic_reg_write(dev->parent, gpio_base + REG_CTL, val);
if (ret < 0)
return ret;
/* Set the right pull */
ret = pmic_reg_write(dev->parent, gpio_base + REG_DIG_PULL_CTL,
REG_DIG_PULL_NO_PU);
if (ret < 0)
return ret;
/* Select the VIN - VIN0*/
ret = pmic_reg_write(dev->parent, gpio_base + REG_DIG_VIN_CTL,
REG_DIG_VIN_VIN0);
if (ret < 0)
return ret;
/* Set the right dig out control */
val = REG_DIG_OUT_CTL_CMOS | REG_DIG_OUT_CTL_DRIVE_L;
ret = pmic_reg_write(dev->parent, gpio_base + REG_DIG_OUT_CTL, val);
if (ret < 0)
return ret;
/* Enable the GPIO */
val = pmic_reg_read(dev->parent, gpio_base + REG_EN_CTL);
if (val < 0)
return val;
val |= REG_EN_CTL_ENABLE;
ret = pmic_reg_write(dev->parent, gpio_base + REG_EN_CTL, val);
return ret;
+}
Any chance that the above two functions have some common elements that could go in a shared function?
+static int pm8916_gpio_get_function(struct udevice *dev, unsigned offset) +{
struct pm8916_gpio_bank *priv = dev_get_priv(dev);
uint32_t gpio_base = priv->pid + REG_OFFSET(offset);
int reg;
/* Set the output value of the gpio */
reg = pmic_reg_read(dev->parent, gpio_base + REG_CTL);
if (reg < 0)
return reg;
if ((reg & REG_CTL_MODE_MASK) == REG_CTL_MODE_INPUT)
How about:
switch (reg & ...) { case ... }
return GPIOF_INPUT;
if ((reg & REG_CTL_MODE_MASK) == REG_CTL_MODE_INOUT)
return GPIOF_OUTPUT;
if ((reg & REG_CTL_MODE_MASK) == REG_CTL_MODE_OUTPUT)
return GPIOF_OUTPUT;
return GPIOF_UNKNOWN;
+}
+static int pm8916_gpio_get_value(struct udevice *dev, unsigned offset) +{
struct pm8916_gpio_bank *priv = dev_get_priv(dev);
uint32_t gpio_base = priv->pid + REG_OFFSET(offset);
int reg;
Suggest ret as it is not always a register. pmic_reg_read() returns an error code sometimes.
reg = pmic_reg_read(dev->parent, gpio_base + REG_STATUS);
if (reg < 0)
return reg;
return !!(reg & REG_STATUS_VAL_MASK);
+}
+static int pm8916_gpio_set_value(struct udevice *dev, unsigned offset,
int value)
+{
struct pm8916_gpio_bank *priv = dev_get_priv(dev);
uint32_t gpio_base = priv->pid + REG_OFFSET(offset);
int reg;
/* Set the output value of the gpio */
reg = pmic_reg_read(dev->parent, gpio_base + REG_CTL);
if (reg < 0)
return reg;
reg = (reg & ~REG_CTL_OUTPUT_MASK) | value;
return pmic_reg_write(dev->parent, gpio_base + REG_CTL, reg);
pmic_clrsetbits()?
+}
+static const struct dm_gpio_ops pm8916_gpio_ops = {
.direction_input = pm8916_gpio_direction_input,
.direction_output = pm8916_gpio_direction_output,
.get_value = pm8916_gpio_get_value,
.set_value = pm8916_gpio_set_value,
.get_function = pm8916_gpio_get_function,
+};
+static int pm8916_gpio_probe(struct udevice *dev) +{
struct pm8916_gpio_bank *priv = dev_get_priv(dev);
priv->pid = dev_get_addr(dev);
return 0;
+}
+static int pm8916_gpio_ofdata_to_platdata(struct udevice *dev) +{
struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
uc_priv->gpio_count = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
"gpio-count", 0);
uc_priv->bank_name = fdt_getprop(gd->fdt_blob, dev->of_offset,
"gpio-bank-name", NULL);
if (uc_priv->bank_name == NULL)
uc_priv->bank_name = "pm8916";
return 0;
+}
+static const struct udevice_id pm8916_gpio_ids[] = {
{ .compatible = "qcom,pm8916-gpio" },
Binding doc?
{ }
+};
+U_BOOT_DRIVER(gpio_pm8916) = {
.name = "gpio_pm8916",
.id = UCLASS_GPIO,
.of_match = pm8916_gpio_ids,
.ofdata_to_platdata = pm8916_gpio_ofdata_to_platdata,
.probe = pm8916_gpio_probe,
.ops = &pm8916_gpio_ops,
.priv_auto_alloc_size = sizeof(struct pm8916_gpio_bank),
+};
+/* Add pmic buttons as GPIO as well - there is no generic way for now */ +#define PON_INT_RT_STS 0x10 +#define KPDPWR_ON_INT_BIT 0 +#define RESIN_ON_INT_BIT 1
+static int pm8941_pwrkey_get_function(struct udevice *dev, unsigned offset) +{
return GPIOF_INPUT;
+}
+static int pm8941_pwrkey_get_value(struct udevice *dev, unsigned offset) +{
struct pm8916_gpio_bank *priv = dev_get_priv(dev);
int reg = pmic_reg_read(dev->parent, priv->pid + PON_INT_RT_STS);
if (reg < 0)
return 0;
switch (offset) {
case 0: /* Power button */
return (reg & BIT(KPDPWR_ON_INT_BIT)) != 0;
break;
case 1: /* Reset button */
default:
return (reg & BIT(RESIN_ON_INT_BIT)) != 0;
break;
}
+}
+static const struct dm_gpio_ops pm8941_pwrkey_ops = {
.get_value = pm8941_pwrkey_get_value,
.get_function = pm8941_pwrkey_get_function,
+};
+static int pm8941_pwrkey_probe(struct udevice *dev) +{
struct pm8916_gpio_bank *priv = dev_get_priv(dev);
priv->pid = dev_get_addr(dev);
return 0;
+}
+static int pm8941_pwrkey_ofdata_to_platdata(struct udevice *dev) +{
struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
uc_priv->gpio_count = 2;
uc_priv->bank_name = "pm8916_key";
return 0;
+}
+static const struct udevice_id pm8941_pwrkey_ids[] = {
{ .compatible = "qcom,pm8916-pwrkey" },
{ }
+};
+U_BOOT_DRIVER(pwrkey_pm8941) = {
.name = "pwrkey_pm8916",
.id = UCLASS_GPIO,
.of_match = pm8941_pwrkey_ids,
.ofdata_to_platdata = pm8941_pwrkey_ofdata_to_platdata,
.probe = pm8941_pwrkey_probe,
.ops = &pm8941_pwrkey_ops,
.priv_auto_alloc_size = sizeof(struct pm8916_gpio_bank),
+};
2.5.0
Regards, Simon

First supported chip is APQ8016 (that is compatible with MSM8916). Drivers in SoC code: - Reset controller (PSHOLD) - Clock controller (very simple clock configuration for MMC and UART)
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com ---
arch/arm/Kconfig | 12 + arch/arm/Makefile | 1 + arch/arm/mach-snapdragon/Kconfig | 6 + arch/arm/mach-snapdragon/Makefile | 8 + arch/arm/mach-snapdragon/clock-apq8016.c | 262 +++++++++++++++++++++ arch/arm/mach-snapdragon/include/mach/gpio.h | 25 ++ .../mach-snapdragon/include/mach/sysmap-apq8016.h | 15 ++ arch/arm/mach-snapdragon/reset.c | 40 ++++ 8 files changed, 369 insertions(+) create mode 100644 arch/arm/mach-snapdragon/Kconfig create mode 100644 arch/arm/mach-snapdragon/Makefile create mode 100644 arch/arm/mach-snapdragon/clock-apq8016.c create mode 100644 arch/arm/mach-snapdragon/include/mach/gpio.h create mode 100644 arch/arm/mach-snapdragon/include/mach/sysmap-apq8016.h create mode 100644 arch/arm/mach-snapdragon/reset.c
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index fb9176b..0241980 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -493,6 +493,16 @@ config OMAP54XX select CPU_V7 select SUPPORT_SPL
+config ARCH_SNAPDRAGON + bool "Qualcomm Snapdragon SoCs" + select ARM64 + select DM + select DM_GPIO + select DM_SERIAL + select DM_SPMI + select OF_CONTROL + select OF_SEPARATE + config RMOBILE bool "Renesas ARM SoCs" select CPU_V7 @@ -745,6 +755,8 @@ source "arch/arm/mach-rockchip/Kconfig"
source "arch/arm/mach-s5pc1xx/Kconfig"
+source "arch/arm/mach-snapdragon/Kconfig" + source "arch/arm/mach-socfpga/Kconfig"
source "arch/arm/mach-tegra/Kconfig" diff --git a/arch/arm/Makefile b/arch/arm/Makefile index 18283d1..b977309 100644 --- a/arch/arm/Makefile +++ b/arch/arm/Makefile @@ -54,6 +54,7 @@ machine-$(CONFIG_ARMADA_XP) += mvebu # TODO: rename CONFIG_ORION5X -> CONFIG_ARCH_ORION5X machine-$(CONFIG_ORION5X) += orion5x machine-$(CONFIG_ARCH_S5PC1XX) += s5pc1xx +machine-$(CONFIG_ARCH_SNAPDRAGON) += snapdragon machine-$(CONFIG_ARCH_SOCFPGA) += socfpga machine-$(CONFIG_ARCH_ROCKCHIP) += rockchip machine-$(CONFIG_TEGRA) += tegra diff --git a/arch/arm/mach-snapdragon/Kconfig b/arch/arm/mach-snapdragon/Kconfig new file mode 100644 index 0000000..156e733 --- /dev/null +++ b/arch/arm/mach-snapdragon/Kconfig @@ -0,0 +1,6 @@ +if ARCH_SNAPDRAGON + +config SYS_SOC + default "snapdragon" + +endif diff --git a/arch/arm/mach-snapdragon/Makefile b/arch/arm/mach-snapdragon/Makefile new file mode 100644 index 0000000..8e84705 --- /dev/null +++ b/arch/arm/mach-snapdragon/Makefile @@ -0,0 +1,8 @@ +# +# (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com +# +# SPDX-License-Identifier: GPL-2.0+ +# + +obj-y += clock-apq8016.o +obj-y += reset.o diff --git a/arch/arm/mach-snapdragon/clock-apq8016.c b/arch/arm/mach-snapdragon/clock-apq8016.c new file mode 100644 index 0000000..c8c15b4 --- /dev/null +++ b/arch/arm/mach-snapdragon/clock-apq8016.c @@ -0,0 +1,262 @@ +/* + * Clock drivers for Qualcomm APQ8016 + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + * + * Based on Little Kernel driver, simplified + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <asm/io.h> +#include <dm.h> +#include <clk.h> +#include <common.h> +#include <errno.h> +#include <linux/bitops.h> + +/* GPLL0 clock control registers */ +#define GPLL0_STATUS 0x2101C +#define GPLL0_STATUS_ACTIVE BIT(17) + +#define APCS_GPLL_ENA_VOTE 0x45000 +#define APCS_GPLL_ENA_VOTE_GPLL0 BIT(0) + +/* vote reg for blsp1 clock */ +#define APCS_CLOCK_BRANCH_ENA_VOTE 0x45004 +#define APCS_CLOCK_BRANCH_ENA_VOTE_BLSP1 BIT(10) + +/* SDC(n) clock control registers; n=1,2 */ + +/* block reset*/ +#define SDCC_BCR(n) (((n*0x1000)) + 0x41000) +/* cmd */ +#define SDCC_CMD_RCGR(n) (((n*0x1000)) + 0x41004) +/* cfg */ +#define SDCC_CFG_RCGR(n) (((n*0x1000)) + 0x41008) +/* m */ +#define SDCC_M(n) (((n*0x1000)) + 0x4100C) +/* n */ +#define SDCC_N(n) (((n*0x1000)) + 0x41010) +/* d */ +#define SDCC_D(n) (((n*0x1000)) + 0x41014) +/* branch control */ +#define SDCC_APPS_CBCR(n) (((n*0x1000)) + 0x41018) +#define SDCC_AHB_CBCR(n) (((n*0x1000)) + 0x4101C) + +/* BLSP1 AHB clock (root clock for BLSP) */ +#define BLSP1_AHB_CBCR 0x1008 + +/* Uart clock control registers */ +#define BLSP1_UART2_BCR 0x3028 +#define BLSP1_UART2_APPS_CBCR 0x302C +#define BLSP1_UART2_APPS_CMD_RCGR 0x3034 +#define BLSP1_UART2_APPS_CFG_RCGR 0x3038 +#define BLSP1_UART2_APPS_M 0x303C +#define BLSP1_UART2_APPS_N 0x3040 +#define BLSP1_UART2_APPS_D 0x3044 + +/* CBCR register fields */ +#define CBCR_BRANCH_ENABLE_BIT BIT(0) +#define CBCR_BRANCH_OFF_BIT BIT(31) + +struct msm_clk_priv { + void *base; +}; + +/* Enable clock controlled by CBC soft macro */ +static void clk_enable_cbc(void *cbcr) +{ + uint32_t val = readl(cbcr); + val |= CBCR_BRANCH_ENABLE_BIT; + writel(val, cbcr); + + while (readl(cbcr) & CBCR_BRANCH_OFF_BIT) + ; +} + +/* clock has 800MHz */ +static void clk_enable_gpll0(void *base) +{ + uint32_t ena; + + if (readl(base + GPLL0_STATUS) & GPLL0_STATUS_ACTIVE) + return; /* clock already enabled */ + + ena = readl(base + APCS_GPLL_ENA_VOTE); + ena |= APCS_GPLL_ENA_VOTE_GPLL0; + writel(ena, base + APCS_GPLL_ENA_VOTE); + + while ((readl(base + GPLL0_STATUS) & GPLL0_STATUS_ACTIVE) == 0) + ; +} + +#define APPS_CMD_RGCR_UPDATE BIT(0) + +/* Update clock command via CMD_RGCR */ +static void clk_bcr_update(void *apps_cmd_rgcr) +{ + uint32_t cmd; + + cmd = readl(apps_cmd_rgcr); + cmd |= APPS_CMD_RGCR_UPDATE; + writel(cmd, apps_cmd_rgcr); + + /* Wait for frequency to be updated. */ + while (readl(apps_cmd_rgcr) & APPS_CMD_RGCR_UPDATE) + ; +} + +struct bcr_regs { + uintptr_t cfg_rcgr; + uintptr_t cmd_rcgr; + uintptr_t M; + uintptr_t N; + uintptr_t D; +}; + +/* RCGR_CFG register fields */ +#define CFG_MODE_DUAL_EDGE (0x2 << 12) /* Counter mode */ + +/* sources */ +#define CFG_CLK_SRC_CXO (0 << 8) +#define CFG_CLK_SRC_GPLL0 (1 << 8) +#define CFG_CLK_SRC_MASK (7 << 8) + +/* Mask for supported fields */ +#define CFG_MASK 0x3FFF + +#define BM(msb, lsb) (((((uint32_t)-1) << (31-msb)) >> (31-msb+lsb)) << lsb) +#define BVAL(msb, lsb, val) (((val) << lsb) & BM(msb, lsb)) + +/* root set rate for clocks with half integer and MND divider */ +static void clk_rcg_set_rate_mnd(void *base, const struct bcr_regs *regs, + int div, int m, int n, int source) +{ + uint32_t cfg; + /* This register houses the M value for MND divider. */ + uint32_t m_val = m; + /* This register houses the NOT(N-M) value for MND divider. */ + uint32_t n_val = ~((n)-(m)) * !!(n); + /* This register houses the NOT 2D value for MND divider. */ + uint32_t d_val = ~(n); + + /* Program MND values */ + writel(m_val, base + regs->M); /* M */ + writel(n_val, base + regs->N); /* N */ + writel(d_val, base + regs->D); /* D */ + + /* setup src select and divider */ + cfg = readl(base + regs->cfg_rcgr); + cfg &= ~CFG_MASK; + cfg |= source & CFG_CLK_SRC_MASK; /* Select clock source */ + cfg |= BVAL(4, 0, (int)(2*(div) - 1)) | BVAL(10, 8, source); + if (n_val) + cfg |= CFG_MODE_DUAL_EDGE; + + writel(cfg, base + regs->cfg_rcgr); /* Write new clock configuration */ + + /* Inform h/w to start using the new config. */ + clk_bcr_update(base + regs->cmd_rcgr); +} + +static const struct bcr_regs sdc_regs[] = { + { + .cfg_rcgr = SDCC_CFG_RCGR(1), + .cmd_rcgr = SDCC_CMD_RCGR(1), + .M = SDCC_M(1), + .N = SDCC_N(1), + .D = SDCC_D(1), + }, + { + .cfg_rcgr = SDCC_CFG_RCGR(2), + .cmd_rcgr = SDCC_CMD_RCGR(2), + .M = SDCC_M(2), + .N = SDCC_N(2), + .D = SDCC_D(2), + } +}; + +/* Init clock for SDHCI controller */ +static int clk_init_sdc(struct msm_clk_priv *p, int slot, uint rate) +{ + int div = 8; /* 100MHz default */ + + if (rate == 200000000) + div = 4; + + clk_enable_cbc(p->base + SDCC_AHB_CBCR(slot)); + /* 800Mhz/div, gpll0 */ + clk_rcg_set_rate_mnd(p->base, &sdc_regs[slot], div, 0, 0, + CFG_CLK_SRC_GPLL0); + clk_enable_gpll0(p->base); + clk_enable_cbc(p->base + SDCC_APPS_CBCR(slot)); + return rate; +} + +static const struct bcr_regs uart2_regs = { + .cfg_rcgr = BLSP1_UART2_APPS_CFG_RCGR, + .cmd_rcgr = BLSP1_UART2_APPS_CMD_RCGR, + .M = BLSP1_UART2_APPS_M, + .N = BLSP1_UART2_APPS_N, + .D = BLSP1_UART2_APPS_D, +}; + +/* Init UART clock, 115200 */ +static int clk_init_uart(struct msm_clk_priv *p) +{ + /* Enable iface clk */ + clk_enable_cbc(p->base + BLSP1_AHB_CBCR); + /* 7372800 uart block clock @ GPLL0 */ + clk_rcg_set_rate_mnd(p->base, &uart2_regs, 1, 144, 15625, + CFG_CLK_SRC_GPLL0); + clk_enable_gpll0(p->base); + /* Enable core clk */ + clk_enable_cbc(p->base + BLSP1_UART2_APPS_CBCR); + return 0; +} + +ulong msm_set_periph_rate(struct udevice *dev, int periph, ulong rate) +{ + struct msm_clk_priv *priv = dev_get_priv(dev); + + switch (periph) { + case 0: /* SDC1 */ + return clk_init_sdc(priv, 0, rate); + break; + case 1: /* SDC2 */ + return clk_init_sdc(priv, 1, rate); + break; + case 4: /* UART2 */ + return clk_init_uart(priv); + break; + default: + return 0; + } + return 0; +} + +static int msm_clk_probe(struct udevice *dev) +{ + struct msm_clk_priv *priv = dev_get_priv(dev); + priv->base = (void *)dev_get_addr(dev); + return 0; +} + +static struct clk_ops msm_clk_ops = { + .set_periph_rate = msm_set_periph_rate, +}; + +static const struct udevice_id msm_clk_ids[] = { + { .compatible = "qcom,gcc-msm8916" }, + { } +}; + +U_BOOT_DRIVER(clk_msm) = { + .name = "clk_msm", + .id = UCLASS_CLK, + .of_match = msm_clk_ids, + .ops = &msm_clk_ops, + .priv_auto_alloc_size = sizeof(struct msm_clk_priv), + .probe = msm_clk_probe, +}; diff --git a/arch/arm/mach-snapdragon/include/mach/gpio.h b/arch/arm/mach-snapdragon/include/mach/gpio.h new file mode 100644 index 0000000..e2ce28b --- /dev/null +++ b/arch/arm/mach-snapdragon/include/mach/gpio.h @@ -0,0 +1,25 @@ +/* + * Qualcomm GPIO definitions + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + * + * SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef _MACH_GPIO_H +#define _MACH_GPIO_H + +/* Register offsets */ +#define GPIO_CONFIG_OFF(no) ((no)*0x1000) +#define GPIO_IN_OUT_OFF(no) ((no)*0x1000 + 0x4) + +/* OE */ +#define GPIO_OE_DISABLE (0x0 << 9) +#define GPIO_OE_ENABLE (0x1 << 9) +#define GPIO_OE_MASK (0x1 << 9) + +/* GPIO_IN_OUT register shifts. */ +#define GPIO_IN 0 +#define GPIO_OUT 1 + +#endif /* _ASM_ARCH_GPIO_H */ + diff --git a/arch/arm/mach-snapdragon/include/mach/sysmap-apq8016.h b/arch/arm/mach-snapdragon/include/mach/sysmap-apq8016.h new file mode 100644 index 0000000..32bc761 --- /dev/null +++ b/arch/arm/mach-snapdragon/include/mach/sysmap-apq8016.h @@ -0,0 +1,15 @@ +/* + * Qualcomm APQ8916 sysmap + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + * + * SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef _MACH_APQ8016_SYSMAP_H +#define _MACH_APQ8016_SYSMAP_H + +#define GICD_BASE 0x0b000000 +#define GICC_BASE 0x0a20c000 + +#endif + diff --git a/arch/arm/mach-snapdragon/reset.c b/arch/arm/mach-snapdragon/reset.c new file mode 100644 index 0000000..ade3237 --- /dev/null +++ b/arch/arm/mach-snapdragon/reset.c @@ -0,0 +1,40 @@ +/* + * Qualcomm APQ8016 reset controller driver + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <reset.h> +#include <asm/io.h> + +DECLARE_GLOBAL_DATA_PTR; + +static int msm_reset_request(struct udevice *dev, enum reset_t type) +{ + phys_addr_t addr = dev_get_addr(dev); + if (!addr) + return -EINVAL; + writel(0, addr); + return -EINPROGRESS; +} + +static struct reset_ops msm_reset_ops = { + .request = msm_reset_request, +}; + +static const struct udevice_id msm_reset_ids[] = { + { .compatible = "qcom,pshold" }, + { } +}; + +U_BOOT_DRIVER(reset_sandbox) = { + .name = "msm_reset", + .id = UCLASS_RESET, + .of_match = msm_reset_ids, + .ops = &msm_reset_ops, +};

Hi Mateusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
First supported chip is APQ8016 (that is compatible with MSM8916). Drivers in SoC code:
- Reset controller (PSHOLD)
- Clock controller (very simple clock configuration for MMC and UART)
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com
arch/arm/Kconfig | 12 + arch/arm/Makefile | 1 + arch/arm/mach-snapdragon/Kconfig | 6 + arch/arm/mach-snapdragon/Makefile | 8 + arch/arm/mach-snapdragon/clock-apq8016.c | 262 +++++++++++++++++++++ arch/arm/mach-snapdragon/include/mach/gpio.h | 25 ++ .../mach-snapdragon/include/mach/sysmap-apq8016.h | 15 ++ arch/arm/mach-snapdragon/reset.c | 40 ++++ 8 files changed, 369 insertions(+) create mode 100644 arch/arm/mach-snapdragon/Kconfig create mode 100644 arch/arm/mach-snapdragon/Makefile create mode 100644 arch/arm/mach-snapdragon/clock-apq8016.c create mode 100644 arch/arm/mach-snapdragon/include/mach/gpio.h create mode 100644 arch/arm/mach-snapdragon/include/mach/sysmap-apq8016.h create mode 100644 arch/arm/mach-snapdragon/reset.c
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index fb9176b..0241980 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -493,6 +493,16 @@ config OMAP54XX select CPU_V7 select SUPPORT_SPL
+config ARCH_SNAPDRAGON
bool "Qualcomm Snapdragon SoCs"
select ARM64
select DM
select DM_GPIO
select DM_SERIAL
select DM_SPMI
select OF_CONTROL
select OF_SEPARATE
config RMOBILE bool "Renesas ARM SoCs" select CPU_V7 @@ -745,6 +755,8 @@ source "arch/arm/mach-rockchip/Kconfig"
source "arch/arm/mach-s5pc1xx/Kconfig"
+source "arch/arm/mach-snapdragon/Kconfig"
source "arch/arm/mach-socfpga/Kconfig"
source "arch/arm/mach-tegra/Kconfig" diff --git a/arch/arm/Makefile b/arch/arm/Makefile index 18283d1..b977309 100644 --- a/arch/arm/Makefile +++ b/arch/arm/Makefile @@ -54,6 +54,7 @@ machine-$(CONFIG_ARMADA_XP) += mvebu # TODO: rename CONFIG_ORION5X -> CONFIG_ARCH_ORION5X machine-$(CONFIG_ORION5X) += orion5x machine-$(CONFIG_ARCH_S5PC1XX) += s5pc1xx +machine-$(CONFIG_ARCH_SNAPDRAGON) += snapdragon machine-$(CONFIG_ARCH_SOCFPGA) += socfpga machine-$(CONFIG_ARCH_ROCKCHIP) += rockchip machine-$(CONFIG_TEGRA) += tegra diff --git a/arch/arm/mach-snapdragon/Kconfig b/arch/arm/mach-snapdragon/Kconfig new file mode 100644 index 0000000..156e733 --- /dev/null +++ b/arch/arm/mach-snapdragon/Kconfig @@ -0,0 +1,6 @@ +if ARCH_SNAPDRAGON
+config SYS_SOC
default "snapdragon"
+endif diff --git a/arch/arm/mach-snapdragon/Makefile b/arch/arm/mach-snapdragon/Makefile new file mode 100644 index 0000000..8e84705 --- /dev/null +++ b/arch/arm/mach-snapdragon/Makefile @@ -0,0 +1,8 @@ +# +# (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com +# +# SPDX-License-Identifier: GPL-2.0+ +#
+obj-y += clock-apq8016.o +obj-y += reset.o diff --git a/arch/arm/mach-snapdragon/clock-apq8016.c b/arch/arm/mach-snapdragon/clock-apq8016.c new file mode 100644 index 0000000..c8c15b4 --- /dev/null +++ b/arch/arm/mach-snapdragon/clock-apq8016.c
Can this go in drivers/clk?
@@ -0,0 +1,262 @@ +/*
- Clock drivers for Qualcomm APQ8016
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- Based on Little Kernel driver, simplified
- SPDX-License-Identifier: BSD-3-Clause
- */
+#include <asm/io.h> +#include <dm.h> +#include <clk.h> +#include <common.h> +#include <errno.h> +#include <linux/bitops.h>
Ordering - please fix globally.
+/* GPLL0 clock control registers */ +#define GPLL0_STATUS 0x2101C +#define GPLL0_STATUS_ACTIVE BIT(17)
+#define APCS_GPLL_ENA_VOTE 0x45000 +#define APCS_GPLL_ENA_VOTE_GPLL0 BIT(0)
+/* vote reg for blsp1 clock */ +#define APCS_CLOCK_BRANCH_ENA_VOTE 0x45004 +#define APCS_CLOCK_BRANCH_ENA_VOTE_BLSP1 BIT(10)
+/* SDC(n) clock control registers; n=1,2 */
+/* block reset*/
/* block reset */
+#define SDCC_BCR(n) (((n*0x1000)) + 0x41000)
Can you put spaces around operators? (n * 0x1000)
+/* cmd */ +#define SDCC_CMD_RCGR(n) (((n*0x1000)) + 0x41004) +/* cfg */ +#define SDCC_CFG_RCGR(n) (((n*0x1000)) + 0x41008) +/* m */ +#define SDCC_M(n) (((n*0x1000)) + 0x4100C) +/* n */ +#define SDCC_N(n) (((n*0x1000)) + 0x41010) +/* d */ +#define SDCC_D(n) (((n*0x1000)) + 0x41014) +/* branch control */ +#define SDCC_APPS_CBCR(n) (((n*0x1000)) + 0x41018) +#define SDCC_AHB_CBCR(n) (((n*0x1000)) + 0x4101C)
+/* BLSP1 AHB clock (root clock for BLSP) */ +#define BLSP1_AHB_CBCR 0x1008
+/* Uart clock control registers */ +#define BLSP1_UART2_BCR 0x3028 +#define BLSP1_UART2_APPS_CBCR 0x302C +#define BLSP1_UART2_APPS_CMD_RCGR 0x3034 +#define BLSP1_UART2_APPS_CFG_RCGR 0x3038 +#define BLSP1_UART2_APPS_M 0x303C +#define BLSP1_UART2_APPS_N 0x3040 +#define BLSP1_UART2_APPS_D 0x3044
+/* CBCR register fields */ +#define CBCR_BRANCH_ENABLE_BIT BIT(0) +#define CBCR_BRANCH_OFF_BIT BIT(31)
+struct msm_clk_priv {
void *base;
You might consider using a struct to access registers to avoid all the void * but it is up to you - we do both now.
+};
+/* Enable clock controlled by CBC soft macro */ +static void clk_enable_cbc(void *cbcr) +{
uint32_t val = readl(cbcr);
blank line
val |= CBCR_BRANCH_ENABLE_BIT;
writel(val, cbcr);
while (readl(cbcr) & CBCR_BRANCH_OFF_BIT)
;
+}
+/* clock has 800MHz */ +static void clk_enable_gpll0(void *base) +{
uint32_t ena;
if (readl(base + GPLL0_STATUS) & GPLL0_STATUS_ACTIVE)
return; /* clock already enabled */
ena = readl(base + APCS_GPLL_ENA_VOTE);
ena |= APCS_GPLL_ENA_VOTE_GPLL0;
writel(ena, base + APCS_GPLL_ENA_VOTE);
setbits_le32(base + APCS_GPLL_ENA_VOTE, APCS_GPLL_ENA_VOTE_GPLL0)
Similarly below.
while ((readl(base + GPLL0_STATUS) & GPLL0_STATUS_ACTIVE) == 0)
;
+}
+#define APPS_CMD_RGCR_UPDATE BIT(0)
+/* Update clock command via CMD_RGCR */ +static void clk_bcr_update(void *apps_cmd_rgcr) +{
uint32_t cmd;
cmd = readl(apps_cmd_rgcr);
cmd |= APPS_CMD_RGCR_UPDATE;
writel(cmd, apps_cmd_rgcr);
/* Wait for frequency to be updated. */
while (readl(apps_cmd_rgcr) & APPS_CMD_RGCR_UPDATE)
;
+}
+struct bcr_regs {
uintptr_t cfg_rcgr;
uintptr_t cmd_rcgr;
uintptr_t M;
uintptr_t N;
uintptr_t D;
+};
+/* RCGR_CFG register fields */ +#define CFG_MODE_DUAL_EDGE (0x2 << 12) /* Counter mode */
+/* sources */ +#define CFG_CLK_SRC_CXO (0 << 8) +#define CFG_CLK_SRC_GPLL0 (1 << 8) +#define CFG_CLK_SRC_MASK (7 << 8)
+/* Mask for supported fields */ +#define CFG_MASK 0x3FFF
+#define BM(msb, lsb) (((((uint32_t)-1) << (31-msb)) >> (31-msb+lsb)) << lsb) +#define BVAL(msb, lsb, val) (((val) << lsb) & BM(msb, lsb))
Best to avoid these sorts of things if possible, and just use MASK and SHIFT #defines.
+/* root set rate for clocks with half integer and MND divider */ +static void clk_rcg_set_rate_mnd(void *base, const struct bcr_regs *regs,
int div, int m, int n, int source)
+{
uint32_t cfg;
/* This register houses the M value for MND divider. */
uint32_t m_val = m;
/* This register houses the NOT(N-M) value for MND divider. */
uint32_t n_val = ~((n)-(m)) * !!(n);
/* This register houses the NOT 2D value for MND divider. */
uint32_t d_val = ~(n);
/* Program MND values */
writel(m_val, base + regs->M); /* M */
writel(n_val, base + regs->N); /* N */
writel(d_val, base + regs->D); /* D */
Those 3 comments don't seem useful.
/* setup src select and divider */
cfg = readl(base + regs->cfg_rcgr);
cfg &= ~CFG_MASK;
cfg |= source & CFG_CLK_SRC_MASK; /* Select clock source */
cfg |= BVAL(4, 0, (int)(2*(div) - 1)) | BVAL(10, 8, source);
if (n_val)
cfg |= CFG_MODE_DUAL_EDGE;
writel(cfg, base + regs->cfg_rcgr); /* Write new clock configuration */
/* Inform h/w to start using the new config. */
clk_bcr_update(base + regs->cmd_rcgr);
+}
+static const struct bcr_regs sdc_regs[] = {
{
.cfg_rcgr = SDCC_CFG_RCGR(1),
.cmd_rcgr = SDCC_CMD_RCGR(1),
.M = SDCC_M(1),
.N = SDCC_N(1),
.D = SDCC_D(1),
},
{
.cfg_rcgr = SDCC_CFG_RCGR(2),
.cmd_rcgr = SDCC_CMD_RCGR(2),
.M = SDCC_M(2),
.N = SDCC_N(2),
.D = SDCC_D(2),
}
+};
+/* Init clock for SDHCI controller */ +static int clk_init_sdc(struct msm_clk_priv *p, int slot, uint rate) +{
int div = 8; /* 100MHz default */
if (rate == 200000000)
div = 4;
clk_enable_cbc(p->base + SDCC_AHB_CBCR(slot));
/* 800Mhz/div, gpll0 */
clk_rcg_set_rate_mnd(p->base, &sdc_regs[slot], div, 0, 0,
CFG_CLK_SRC_GPLL0);
clk_enable_gpll0(p->base);
clk_enable_cbc(p->base + SDCC_APPS_CBCR(slot));
blank line before return
return rate;
+}
+static const struct bcr_regs uart2_regs = {
.cfg_rcgr = BLSP1_UART2_APPS_CFG_RCGR,
.cmd_rcgr = BLSP1_UART2_APPS_CMD_RCGR,
.M = BLSP1_UART2_APPS_M,
.N = BLSP1_UART2_APPS_N,
.D = BLSP1_UART2_APPS_D,
+};
+/* Init UART clock, 115200 */ +static int clk_init_uart(struct msm_clk_priv *p) +{
/* Enable iface clk */
clk_enable_cbc(p->base + BLSP1_AHB_CBCR);
/* 7372800 uart block clock @ GPLL0 */
clk_rcg_set_rate_mnd(p->base, &uart2_regs, 1, 144, 15625,
CFG_CLK_SRC_GPLL0);
clk_enable_gpll0(p->base);
/* Enable core clk */
clk_enable_cbc(p->base + BLSP1_UART2_APPS_CBCR);
return 0;
+}
+ulong msm_set_periph_rate(struct udevice *dev, int periph, ulong rate) +{
struct msm_clk_priv *priv = dev_get_priv(dev);
switch (periph) {
case 0: /* SDC1 */
return clk_init_sdc(priv, 0, rate);
break;
case 1: /* SDC2 */
return clk_init_sdc(priv, 1, rate);
break;
case 4: /* UART2 */
return clk_init_uart(priv);
break;
default:
return 0;
}
return 0;
+}
+static int msm_clk_probe(struct udevice *dev) +{
struct msm_clk_priv *priv = dev_get_priv(dev);
priv->base = (void *)dev_get_addr(dev);
return 0;
+}
+static struct clk_ops msm_clk_ops = {
.set_periph_rate = msm_set_periph_rate,
+};
+static const struct udevice_id msm_clk_ids[] = {
{ .compatible = "qcom,gcc-msm8916" },
{ }
+};
+U_BOOT_DRIVER(clk_msm) = {
.name = "clk_msm",
.id = UCLASS_CLK,
.of_match = msm_clk_ids,
.ops = &msm_clk_ops,
.priv_auto_alloc_size = sizeof(struct msm_clk_priv),
.probe = msm_clk_probe,
+}; diff --git a/arch/arm/mach-snapdragon/include/mach/gpio.h b/arch/arm/mach-snapdragon/include/mach/gpio.h new file mode 100644 index 0000000..e2ce28b --- /dev/null +++ b/arch/arm/mach-snapdragon/include/mach/gpio.h
Should this go in a different patch? I thought you had a GPIO driver earlier?
@@ -0,0 +1,25 @@ +/*
- Qualcomm GPIO definitions
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- SPDX-License-Identifier: GPL-2.0+
- */
+#ifndef _MACH_GPIO_H +#define _MACH_GPIO_H
+/* Register offsets */ +#define GPIO_CONFIG_OFF(no) ((no)*0x1000) +#define GPIO_IN_OUT_OFF(no) ((no)*0x1000 + 0x4)
+/* OE */ +#define GPIO_OE_DISABLE (0x0 << 9) +#define GPIO_OE_ENABLE (0x1 << 9) +#define GPIO_OE_MASK (0x1 << 9)
+/* GPIO_IN_OUT register shifts. */ +#define GPIO_IN 0 +#define GPIO_OUT 1
+#endif /* _ASM_ARCH_GPIO_H */
diff --git a/arch/arm/mach-snapdragon/include/mach/sysmap-apq8016.h b/arch/arm/mach-snapdragon/include/mach/sysmap-apq8016.h new file mode 100644 index 0000000..32bc761 --- /dev/null +++ b/arch/arm/mach-snapdragon/include/mach/sysmap-apq8016.h @@ -0,0 +1,15 @@ +/*
- Qualcomm APQ8916 sysmap
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- SPDX-License-Identifier: GPL-2.0+
- */
+#ifndef _MACH_APQ8016_SYSMAP_H +#define _MACH_APQ8016_SYSMAP_H
+#define GICD_BASE 0x0b000000 +#define GICC_BASE 0x0a20c000
Do you need these? Perhaps they can go in the device tree?
+#endif
diff --git a/arch/arm/mach-snapdragon/reset.c b/arch/arm/mach-snapdragon/reset.c new file mode 100644 index 0000000..ade3237 --- /dev/null +++ b/arch/arm/mach-snapdragon/reset.c @@ -0,0 +1,40 @@ +/*
- Qualcomm APQ8016 reset controller driver
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <common.h> +#include <dm.h> +#include <errno.h> +#include <reset.h> +#include <asm/io.h>
+DECLARE_GLOBAL_DATA_PTR;
+static int msm_reset_request(struct udevice *dev, enum reset_t type) +{
phys_addr_t addr = dev_get_addr(dev);
if (!addr)
return -EINVAL;
writel(0, addr);
return -EINPROGRESS;
+}
+static struct reset_ops msm_reset_ops = {
.request = msm_reset_request,
+};
+static const struct udevice_id msm_reset_ids[] = {
{ .compatible = "qcom,pshold" },
{ }
+};
+U_BOOT_DRIVER(reset_sandbox) = {
.name = "msm_reset",
.id = UCLASS_RESET,
.of_match = msm_reset_ids,
.ops = &msm_reset_ops,
+};
2.5.0
Regards, Simon

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256
Hi Simon,
On 16.12.2015 23:29, Simon Glass wrote:
Hi Mateusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
[...]
diff --git a/arch/arm/mach-snapdragon/clock-apq8016.c b/arch/arm/mach-snapdragon/clock-apq8016.c new file mode 100644 index 0000000..c8c15b4 --- /dev/null +++ b/arch/arm/mach-snapdragon/clock-apq8016.c
Can this go in drivers/clk?
Yes, it can, but it's very SoC specific (at least for now). That is - it will probably work only on 2 devices (APQ8016 and MSM8916).
[...]
+++ b/arch/arm/mach-snapdragon/include/mach/gpio.h
Should this go in a different patch? I thought you had a GPIO driver earlier?
Ouch... yeah, this should definitely go to msm_gpio
[...]
+#define _MACH_APQ8016_SYSMAP_H
+#define GICD_BASE 0x0b000000 +#define GICC_BASE 0x0a20c000
Do you need these? Perhaps they can go in the device tree?
Nope, at least not for now.
It's required by armv8/start.S so must go either to board file or here :(
Regards, Mateusz

This commit add support for 96Boards Dragonboard410C. It is board based on APQ8016 Qualcomm SoC, complying with 96boards specification. Features (present out of the box): - 4x Cortex A53 (ARMv8) - 2x USB Host port - 1x USB Device port - 4x LEDs - 1x HDMI connector - 1x uSD connector - 3x buttons (Power, Vol+, Vol-/Reset) - WIFI, Bluetooth with integrated antenna - 8GiB eMMC
U-Boot boots chained with fastboot in 64-bit mode. For detailed build instructions see readme.txt in board directory.
This is code is cleaned-up version of code I did last month that is already used by some people: https://github.com/hallor/u-boot
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com ---
arch/arm/dts/Makefile | 2 + arch/arm/dts/dragonboard410c.dts | 157 +++++++++++++++++++ arch/arm/mach-snapdragon/Kconfig | 9 ++ board/qualcomm/dragonboard410c/Kconfig | 15 ++ board/qualcomm/dragonboard410c/Makefile | 8 + board/qualcomm/dragonboard410c/dragonboard410c.c | 111 ++++++++++++++ board/qualcomm/dragonboard410c/head.S | 20 +++ board/qualcomm/dragonboard410c/readme.txt | 40 +++++ board/qualcomm/dragonboard410c/u-boot.lds | 90 +++++++++++ configs/dragonboard410c_defconfig | 29 ++++ include/configs/dragonboard410c.h | 184 +++++++++++++++++++++++ 11 files changed, 665 insertions(+) create mode 100644 arch/arm/dts/dragonboard410c.dts create mode 100644 board/qualcomm/dragonboard410c/Kconfig create mode 100644 board/qualcomm/dragonboard410c/Makefile create mode 100644 board/qualcomm/dragonboard410c/dragonboard410c.c create mode 100644 board/qualcomm/dragonboard410c/head.S create mode 100644 board/qualcomm/dragonboard410c/readme.txt create mode 100644 board/qualcomm/dragonboard410c/u-boot.lds create mode 100644 configs/dragonboard410c_defconfig create mode 100644 include/configs/dragonboard410c.h
diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile index 521aa4c..1154993 100644 --- a/arch/arm/dts/Makefile +++ b/arch/arm/dts/Makefile @@ -95,6 +95,8 @@ dtb-$(CONFIG_FSL_LSCH3) += fsl-ls2080a-qds.dtb \ dtb-$(CONFIG_FSL_LSCH2) += fsl-ls1043a-qds.dtb \ fsl-ls1043a-rdb.dtb
+dtb-$(CONFIG_ARCH_SNAPDRAGON) += dragonboard410c.dtb + dtb-$(CONFIG_MACH_SUN4I) += \ sun4i-a10-a1000.dtb \ sun4i-a10-ba10-tvbox.dtb \ diff --git a/arch/arm/dts/dragonboard410c.dts b/arch/arm/dts/dragonboard410c.dts new file mode 100644 index 0000000..04ad9eb --- /dev/null +++ b/arch/arm/dts/dragonboard410c.dts @@ -0,0 +1,157 @@ +/dts-v1/; + +#include "skeleton64.dtsi" + +/ { + model = "Qualcomm Technologies, Inc. Dragonboard 410c"; + compatible = "qcom,dragonboard", "qcom,apq8016-sbc"; + qcom,msm-id = <0xce 0x0 0xf8 0x0 0xf9 0x0 0xfa 0x0 0xf7 0x0>; + qcom,board-id = <0x10018 0x0>; + #address-cells = <0x2>; + #size-cells = <0x2>; + + memory { + device_type = "memory"; + reg = <0 0x80000000 0 0x3da00000>; + }; + + chosen { + stdout-path = "/soc/serial@78b0000"; + }; + + + soc { + #address-cells = <0x1>; + #size-cells = <0x1>; + ranges = <0x0 0x0 0x0 0xffffffff>; + compatible = "simple-bus"; + + clkc: qcom,gcc@1800000 { + compatible = "qcom,gcc-msm8916"; + reg = <0x1800000 0x80000>; + #address-cells = <0x1>; + #size-cells = <0x0>; + }; + + serial@78b0000 { + compatible = "qcom,msm-uartdm-v1.4"; + reg = <0x78b0000 0x200>; + u-boot,dm-pre-reloc; + clock = <&clkc 4>; + }; + + restart@4ab000 { + compatible = "qcom,pshold"; + reg = <0x4ab000 0x4>; + }; + + soc_gpios: pinctrl@1000000 { + compatible = "qcom,msm8916-pinctrl"; + reg = <0x1000000 0x300000>; + gpio-controller; + gpio-count = <122>; + gpio-bank-name="soc"; + #gpio-cells = <1>; + }; + + ehci@78d9000 { + compatible = "qcom,ehci-host"; + reg = <0x78d9000 0x400>; + }; + + sdhci@07824000 { + compatible = "qcom,sdhci-msm-v4"; + reg = <0x7824900 0x11c 0x7824000 0x800>; + bus-width = <0x8>; + index = <0x0>; + non-removable; + clock = <&clkc 0>; + clock-frequency = <100000000>; + }; + + sdhci@07864000 { + compatible = "qcom,sdhci-msm-v4"; + reg = <0x7864900 0x11c 0x7864000 0x800>; + index = <0x1>; + bus-width = <0x4>; + clock = <&clkc 1>; + clock-frequency = <200000000>; + }; + + spmi@200f000 { + compatible = "qcom,spmi-pmic-arb"; + reg = <0x200f000 0x1000 0x2400000 0x400000 0x2c00000 0x400000 0x3800000 0x200000 0x200a000 0x2100>; + reg-names = "core", "chnls", "obsrvr", "intr", "cnfg"; + #address-cells = <0x1>; + #size-cells = <0x1>; + pm8916@0 { + compatible = "qcom,spmi-pmic"; + reg = <0x0 0x2>; + #address-cells = <0x1>; + #size-cells = <0x1>; + + pmic_pon: pon@800 { + compatible = "qcom,pm8916-pwrkey"; + reg = <0x800 0x96>; + #gpio-cells = <2>; + gpio-controller; + }; + + pmic_gpios: gpios@c000 { + compatible = "qcom,pm8916-gpio"; + reg = <0xc000 0x400>; + gpio-controller; + gpio-count = <4>; + #gpio-cells = <2>; + gpio-bank-name="pmic"; + }; + }; + + pm8916@1 { + compatible = "qcom,spmi-pmic"; + reg = <0x1>; + #address-cells = <0x1>; + #size-cells = <0x0>; + }; + }; + }; + + leds { + compatible = "gpio-leds"; + user1 { + label = "green:user1"; + gpios = <&soc_gpios 21 0>; + }; + + user2 { + label = "green:user2"; + gpios = <&soc_gpios 120 0>; + }; + + user3 { + label = "green:user3"; + gpios = <&pmic_gpios 0 0>; + }; + + user4 { + label = "green:user4"; + gpios = <&pmic_gpios 1 0>; + }; + }; + + usb_hub_reset_n_pm { + gpios = <&pmic_gpios 2 0>; + }; + + usb_sw_sel_pm { + gpios = <&pmic_gpios 3 0>; + }; + + key_vol_down { + gpios = <&pmic_pon 1 0>; + }; + + key_power { + gpios = <&pmic_pon 0 0>; + }; +}; diff --git a/arch/arm/mach-snapdragon/Kconfig b/arch/arm/mach-snapdragon/Kconfig index 156e733..61e0e9e 100644 --- a/arch/arm/mach-snapdragon/Kconfig +++ b/arch/arm/mach-snapdragon/Kconfig @@ -3,4 +3,13 @@ if ARCH_SNAPDRAGON config SYS_SOC default "snapdragon"
+choice + prompt "Snapdragon board select" + +config TARGET_DRAGONBOARD410C + bool "96Boards Dragonboard 410C" +endchoice + +source "board/qualcomm/dragonboard410c/Kconfig" + endif diff --git a/board/qualcomm/dragonboard410c/Kconfig b/board/qualcomm/dragonboard410c/Kconfig new file mode 100644 index 0000000..03bd7ae --- /dev/null +++ b/board/qualcomm/dragonboard410c/Kconfig @@ -0,0 +1,15 @@ +if TARGET_DRAGONBOARD410C + +config SYS_BOARD + default "dragonboard410c" + +config SYS_VENDOR + default "qualcomm" + +config SYS_SOC + default "apq8016" + +config SYS_CONFIG_NAME + default "dragonboard410c" + +endif diff --git a/board/qualcomm/dragonboard410c/Makefile b/board/qualcomm/dragonboard410c/Makefile new file mode 100644 index 0000000..cd67808 --- /dev/null +++ b/board/qualcomm/dragonboard410c/Makefile @@ -0,0 +1,8 @@ +# +# (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com +# +# SPDX-License-Identifier: GPL-2.0+ +# + +obj-y := dragonboard410c.o +extra-y += head.o diff --git a/board/qualcomm/dragonboard410c/dragonboard410c.c b/board/qualcomm/dragonboard410c/dragonboard410c.c new file mode 100644 index 0000000..7057a2c --- /dev/null +++ b/board/qualcomm/dragonboard410c/dragonboard410c.c @@ -0,0 +1,111 @@ +/* + * Board init file for Dragonboard 410C + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <usb.h> +#include <asm/gpio.h> + +DECLARE_GLOBAL_DATA_PTR; + +int dram_init(void) +{ + gd->ram_size = PHYS_SDRAM_1_SIZE; + return 0; +} + +void dram_init_banksize(void) +{ + gd->bd->bi_dram[0].start = PHYS_SDRAM_1; + gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE; +} + +static struct gpio_desc hub_reset, usb_sel; + +int board_prepare_usb(enum usb_init_type type) +{ + int ret = 0, node; + + /* Try to request gpios needed to start usb host on dragonboard */ + if (!dm_gpio_is_valid(&hub_reset)) { + node = fdt_subnode_offset(gd->fdt_blob, 0, + "usb_hub_reset_n_pm"); + if (node < 0) { + printf("Failed to find usb_hub_reset_n_pm dt node.\n"); + return node; + } + ret = gpio_request_by_name_nodev(gd->fdt_blob, node, "gpios", 0, + &hub_reset, 0); + if (ret < 0) { + printf("Failed to request usb_hub_reset_n_pm gpio.\n"); + return ret; + } + } + + if (!dm_gpio_is_valid(&usb_sel)) { + node = fdt_subnode_offset(gd->fdt_blob, 0, "usb_sw_sel_pm"); + if (node < 0) { + printf("Failed to find usb_sw_sel_pm dt node.\n"); + return 0; + } + ret = gpio_request_by_name_nodev(gd->fdt_blob, node, "gpios", 0, + &usb_sel, 0); + if (ret < 0) { + printf("Failed to request usb_sw_sel_pm gpio.\n"); + return ret; + } + } + + if (type == USB_INIT_HOST) { + /* Start USB Hub */ + dm_gpio_set_dir_flags(&hub_reset, + GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE); + mdelay(100); + /* Switch usb to host connectors */ + dm_gpio_set_dir_flags(&usb_sel, + GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE); + mdelay(100); + } else { /* Device */ + /* Disable hub */ + dm_gpio_set_dir_flags(&hub_reset, GPIOD_IS_OUT); + /* Switch back to device connector */ + dm_gpio_set_dir_flags(&usb_sel, GPIOD_IS_OUT); + } + return 0; +} + +int board_init(void) +{ + return 0; +} + +/* Check for vol- button - if pressed - stop autoboot */ +int misc_init_r(void) +{ + int node; + struct gpio_desc resin; + + node = fdt_subnode_offset(gd->fdt_blob, 0, "key_vol_down"); + if (node < 0) { + printf("Failed to find key_vol_down node. Check device tree\n"); + return 0; + } + + if (gpio_request_by_name_nodev(gd->fdt_blob, node, "gpios", 0, &resin, + 0)) { + printf("Failed to request key_vol_down button.\n"); + return 0; + } + + if (dm_gpio_get_value(&resin)) { + setenv("bootdelay", "-1"); + printf("Power button pressed - dropping to console.\n"); + } + + return 0; +} diff --git a/board/qualcomm/dragonboard410c/head.S b/board/qualcomm/dragonboard410c/head.S new file mode 100644 index 0000000..00d6d97 --- /dev/null +++ b/board/qualcomm/dragonboard410c/head.S @@ -0,0 +1,20 @@ +#include <config.h> + +.global _fastboot_header +_fastboot_header: + b _start + add x13, x18, #0x16 + // Image load offset from start of RAM, little-endian + .quad CONFIG_SYS_TEXT_BASE-PHYS_SDRAM_1 + // Effective size of kernel image, little-endian + .quad 0 //0x60000 + // Informative flags, little-endian + .quad 0 + .quad 0 // reserved + .quad 0 // reserved + .quad 0 // reserved + .byte 0x41 // Magic number, "ARM\x64" + .byte 0x52 + .byte 0x4d + .byte 0x64 + .word 0 // reserved diff --git a/board/qualcomm/dragonboard410c/readme.txt b/board/qualcomm/dragonboard410c/readme.txt new file mode 100644 index 0000000..0f575db --- /dev/null +++ b/board/qualcomm/dragonboard410c/readme.txt @@ -0,0 +1,40 @@ +# +# (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com +# +# SPDX-License-Identifier: GPL-2.0+ +# + +Build & Run instructions: + +1) Install mkbootimg from git://codeaurora.org/quic/kernel/skales (15ece94f09 worked for me) +2) Setup CROSS_COMPILE to aarch64 compiler +3) make dragonboard410c_config +4) make +5) generate fake, empty ramdisk (can have 0 bytes) +$ touch rd + +6) generate qualcomm device tree, use dtbTool to generate it +$ dtbTool -o dt.img arch/arm/dts + +7) generate image with mkbootimg: +$ mkbootimg --kernel=u-boot-dtb.bin --output=u-boot.img --dt=dt.img --pagesize 2048 --base 0x80000000 --ramdisk=rd --cmdline="" + +Boot it with fastboot: +fastboot boot u-boot.img +or flash as kernel: +fastboot flash boot u-boot.img +fastboot reboot + + +What is working: +- UART +- GPIO (SoC) +- SD +- eMMC +- Reset +- USB in EHCI mode (usb starts does switch device->host, usb stop does the opposite) +- PMIC GPIOS (but not in generic subsystem) +- PMIC "special" buttons (power, vol-) + +What is not working / known bugs: +- SDHCI is slow (~2.5MiB/s for SD and eMMC) diff --git a/board/qualcomm/dragonboard410c/u-boot.lds b/board/qualcomm/dragonboard410c/u-boot.lds new file mode 100644 index 0000000..3fb4235 --- /dev/null +++ b/board/qualcomm/dragonboard410c/u-boot.lds @@ -0,0 +1,90 @@ +/* + * Override linker script for fastboot-readable images + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + * + * Based on arch/arm/cpu/armv8/u-boot.lds (Just add header) + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64") +OUTPUT_ARCH(aarch64) +ENTRY(_fastboot_header) +SECTIONS +{ + . = 0x00000000; + + . = ALIGN(8); + .text : + { + *(.__image_copy_start) + board/qualcomm/dragonboard410c/head.o (.text*) + CPUDIR/start.o (.text*) + *(.text*) + } + + . = ALIGN(8); + .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } + + . = ALIGN(8); + .data : { + *(.data*) + } + + . = ALIGN(8); + + . = .; + + . = ALIGN(8); + .u_boot_list : { + KEEP(*(SORT(.u_boot_list*))); + } + + . = ALIGN(8); + + .image_copy_end : + { + *(.__image_copy_end) + } + + . = ALIGN(8); + + .rel_dyn_start : + { + *(.__rel_dyn_start) + } + + .rela.dyn : { + *(.rela*) + } + + .rel_dyn_end : + { + *(.__rel_dyn_end) + } + + _end = .; + + . = ALIGN(8); + + .bss_start : { + KEEP(*(.__bss_start)); + } + + .bss : { + *(.bss*) + . = ALIGN(8); + } + + .bss_end : { + KEEP(*(.__bss_end)); + } + + /DISCARD/ : { *(.dynsym) } + /DISCARD/ : { *(.dynstr*) } + /DISCARD/ : { *(.dynamic*) } + /DISCARD/ : { *(.plt*) } + /DISCARD/ : { *(.interp*) } + /DISCARD/ : { *(.gnu*) } +} diff --git a/configs/dragonboard410c_defconfig b/configs/dragonboard410c_defconfig new file mode 100644 index 0000000..7dd9aae --- /dev/null +++ b/configs/dragonboard410c_defconfig @@ -0,0 +1,29 @@ +CONFIG_ARM=y +CONFIG_ARM64=y +CONFIG_ARCH_SNAPDRAGON=y +CONFIG_TARGET_DRAGONBOARD410C=y + +CONFIG_HUSH_PARSER=y +CONFIG_SYS_PROMPT="dragonboard410c => " +CONFIG_CMD_USB=y +# CONFIG_CMD_IMI is not set +# CONFIG_CMD_IMLS is not set +CONFIG_DEFAULT_DEVICE_TREE="dragonboard410c" + +CONFIG_CLK=y +CONFIG_MSM_GPIO=y +CONFIG_PM8916_GPIO=y +CONFIG_LED=y +CONFIG_LED_GPIO=y +CONFIG_RESET=y +CONFIG_DM_MMC=y +CONFIG_MSM_SDHCI=y +CONFIG_DM_PMIC=y +CONFIG_PMIC_PM8916=y +CONFIG_SPMI_MSM=y +CONFIG_MSM_SERIAL=y +CONFIG_USB=y +CONFIG_DM_USB=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_MSM=y +CONFIG_USB_STORAGE=y diff --git a/include/configs/dragonboard410c.h b/include/configs/dragonboard410c.h new file mode 100644 index 0000000..132ed75 --- /dev/null +++ b/include/configs/dragonboard410c.h @@ -0,0 +1,184 @@ +/* + * Board configuration file for Dragonboard 410C + * + * (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef __CONFIGS_DRAGONBOARD410C_H +#define __CONFIGS_DRAGONBOARD410C_H + +#include <linux/sizes.h> +#include <asm/arch/sysmap-apq8016.h> + +#define CONFIG_IDENT_STRING "\nQualcomm-DragonBoard 410C" + +#define CONFIG_MISC_INIT_R /* To stop autoboot */ + +/* Flat Device Tree Definitions */ +#define CONFIG_OF_LIBFDT + +/* Physical Memory Map */ +#define CONFIG_NR_DRAM_BANKS 1 +#define PHYS_SDRAM_1 0x80000000 +/* 1008 MB (the last ~30Mb are secured for TrustZone by ATF*/ +#define PHYS_SDRAM_1_SIZE 0x3da00000 +#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_1 +#define CONFIG_SYS_TEXT_BASE 0x80080000 +#define CONFIG_SYS_INIT_SP_ADDR (CONFIG_SYS_SDRAM_BASE + 0x7fff0) +#define CONFIG_SYS_LOAD_ADDR (CONFIG_SYS_SDRAM_BASE + 0x80000) +#define CONFIG_SYS_BOOTM_LEN 0x1000000 /* 16MB max kernel size */ + +/* UART */ +#define CONFIG_BAUDRATE 115200 + +/* Generic Timer Definitions */ +#define COUNTER_FREQUENCY 19000000 + +/* This are needed to have proper mmc support */ +#define CONFIG_MMC +#define CONFIG_GENERIC_MMC +#define CONFIG_SDHCI + +#define CONFIG_SYS_LDSCRIPT "board/qualcomm/dragonboard410c/u-boot.lds" + +/* Fixup - in init code we switch from device to host mode, + * it has to be done after each HCD reset */ +#define CONFIG_EHCI_HCD_INIT_AFTER_RESET +/* Needed for Host Controller driver */ +#define CONFIG_USB_ULPI_VIEWPORT + +#define CONFIG_USB_HOST_ETHER /* Enable USB Networking */ + +/* Support all possible USB ethernet dongles */ +#define CONFIG_USB_ETHER_DM9601 +#define CONFIG_USB_ETHER_ASIX +#define CONFIG_USB_ETHER_ASIX88179 +#define CONFIG_USB_ETHER_MCS7830 +#define CONFIG_USB_ETHER_SMSC95XX + +/* Libraries */ +#define CONFIG_MD5 + +/* Extra Commands */ +#define CONFIG_CMD_CACHE +#define CONFIG_CMD_DHCP +#define CONFIG_CMD_ENV +#define CONFIG_CMD_FAT /* FAT support */ +#define CONFIG_CMD_GPIO +#define CONFIG_CMD_GPT +#define CONFIG_CMD_MD5SUM +#define CONFIG_CMD_MEMINFO /* meminfo */ +#define CONFIG_CMD_MMC +/* Enable that for switching of boot partitions */ +/* Disabled by default as some sub-commands can brick eMMC */ +/*#define CONFIG_SUPPORT_EMMC_BOOT */ +#define CONFIG_CMD_PART +#define CONFIG_CMD_PING +#define CONFIG_CMD_REGINFO /* Register dump */ +#define CONFIG_CMD_TFTP +#define CONFIG_CMD_TIMER +#define CONFIG_CMD_UNZIP +#define CONFIG_CMD_BOOTZ +#define CONFIG_CMD_BOOTI + +/* Command line configuration */ +#define CONFIG_MENU +#define CONFIG_SYS_LONGHELP + +/* Partition table support */ +#define HAVE_BLOCK_DEVICE /* Needed for partition commands */ +#define CONFIG_DOS_PARTITION +#define CONFIG_EFI_PARTITION +#define CONFIG_PARTITION_UUIDS + +/* BOOTP options */ +#define CONFIG_BOOTP_BOOTFILESIZE + +/* Environment - Boot*/ +#define CONFIG_BOOTDELAY 2 /* autoboot after 2 seconds */ + +#define CONFIG_NFSBOOTCOMMAND "" +#define CONFIG_BOOTCOMMAND "usb start && dhcp && tftp && usb stop && bootm" +#define CONFIG_BOOTARGS "console=ttyMSM0,115200n8" + +/* Does what recovery does */ +#define REFLASH(file, part) \ +"part start mmc 0 "#part" start && "\ +"part size mmc 0 "#part" size && "\ +"tftp $loadaddr "#file" &&" \ +"mmc write $loadaddr $start $size &&" + + +#define CONFIG_ENV_REFLASH \ +"mmc dev 0 &&"\ +"usb start &&"\ +"dhcp &&"\ +"tftp $loadaddr dragonboard/rescue/gpt_both0.bin && "\ +"mmc write $loadaddr 0 43 &&" \ +"mmc rescan &&"\ +REFLASH(dragonboard/rescue/NON-HLOS.bin, 1)\ +REFLASH(dragonboard/rescue/sbl1.mbn, 2)\ +REFLASH(dragonboard/rescue/rpm.mbn, 3)\ +REFLASH(dragonboard/rescue/tz.mbn, 4)\ +REFLASH(dragonboard/rescue/hyp.mbn, 5)\ +REFLASH(dragonboard/rescue/sec.dat, 6)\ +REFLASH(dragonboard/rescue/emmc_appsboot.mbn, 7)\ +REFLASH(dragonboard/u-boot.img, 8)\ +"usb stop &&"\ +"echo Reflash completed" + +#define CONFIG_UBOOT_REFLASH \ +"mmc dev 0 &&"\ +"usb start &&"\ +"dhcp &&"\ +"part start mmc 0 8 start && "\ +"setenv size 0x800 &&"\ +"tftp $loadaddr dragonboard/u-boot.img &&" \ +"mmc write $loadaddr $start $size &&"\ +"usb stop &&"\ +"echo Reflash completed &&" \ +"reset" + +/* Environment */ +#define CONFIG_EXTRA_ENV_SETTINGS \ + "reflash="CONFIG_ENV_REFLASH"\0"\ + "reflash_uboot="CONFIG_UBOOT_REFLASH"\0"\ + "loadaddr=0x81000000\0" \ + "fdt_high=0xffffffffffffffff\0" \ + "initrd_high=0xffffffffffffffff\0" \ + "linux_image=dragonboard/Image\0" \ + "linux_addr=0x81000000\0"\ + "fdt_image=dragonboard/apq8016-sbc.dtb\0" \ + "fdt_addr=0x83000000\0"\ + "ramdisk_addr=0x84000000\0"\ + "ramdisk_image=dragonboard/initrd.img\0" \ + "dl_uboot=tftp $loadaddr dragonboard/u-boot.img\0"\ + "dl_kernel=tftp $linux_addr $linux_image " \ + "&& tftp $fdt_addr $fdt_image\0"\ + "dl_ramdisk=tftp $ramdisk_addr $ramdisk_image\0"\ + "nboot_nord=usb start && run dl_kernel && usb stop && " \ + "booti $linux_addr - $fdt_addr\0"\ + "nboot_rd=usb start && run dl_kernel && run dl_ramdisk && "\ + "booti $linux_addr $ramdisk_addr $fdt_addr\0" + +#define CONFIG_ENV_IS_NOWHERE +#define CONFIG_ENV_SIZE 0x1000 +#define CONFIG_ENV_VARS_UBOOT_CONFIG +#define CONFIG_SYS_NO_FLASH + +/* Size of malloc() pool */ +#define CONFIG_SYS_MALLOC_LEN (CONFIG_ENV_SIZE + SZ_8M) + +/* Monitor Command Prompt */ +#define CONFIG_SYS_CBSIZE 512 /* Console I/O Buffer Size */ +#define CONFIG_SYS_PBSIZE (CONFIG_SYS_CBSIZE + \ + sizeof(CONFIG_SYS_PROMPT) + 16) +#define CONFIG_SYS_BARGSIZE CONFIG_SYS_CBSIZE +#define CONFIG_SYS_LONGHELP +#define CONFIG_CMDLINE_EDITING +#define CONFIG_SYS_MAXARGS 64 /* max command args */ + + +#endif

Hi Mateusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
This commit add support for 96Boards Dragonboard410C. It is board based on APQ8016 Qualcomm SoC, complying with 96boards specification. Features (present out of the box):
- 4x Cortex A53 (ARMv8)
- 2x USB Host port
- 1x USB Device port
- 4x LEDs
- 1x HDMI connector
- 1x uSD connector
- 3x buttons (Power, Vol+, Vol-/Reset)
- WIFI, Bluetooth with integrated antenna
- 8GiB eMMC
U-Boot boots chained with fastboot in 64-bit mode. For detailed build instructions see readme.txt in board directory.
This is code is cleaned-up version of code I did last month that is already used by some people: https://github.com/hallor/u-boot
Signed-off-by: Mateusz Kulikowski mateusz.kulikowski@gmail.com
arch/arm/dts/Makefile | 2 + arch/arm/dts/dragonboard410c.dts | 157 +++++++++++++++++++ arch/arm/mach-snapdragon/Kconfig | 9 ++ board/qualcomm/dragonboard410c/Kconfig | 15 ++ board/qualcomm/dragonboard410c/Makefile | 8 + board/qualcomm/dragonboard410c/dragonboard410c.c | 111 ++++++++++++++ board/qualcomm/dragonboard410c/head.S | 20 +++ board/qualcomm/dragonboard410c/readme.txt | 40 +++++ board/qualcomm/dragonboard410c/u-boot.lds | 90 +++++++++++ configs/dragonboard410c_defconfig | 29 ++++ include/configs/dragonboard410c.h | 184 +++++++++++++++++++++++ 11 files changed, 665 insertions(+) create mode 100644 arch/arm/dts/dragonboard410c.dts create mode 100644 board/qualcomm/dragonboard410c/Kconfig create mode 100644 board/qualcomm/dragonboard410c/Makefile create mode 100644 board/qualcomm/dragonboard410c/dragonboard410c.c create mode 100644 board/qualcomm/dragonboard410c/head.S create mode 100644 board/qualcomm/dragonboard410c/readme.txt create mode 100644 board/qualcomm/dragonboard410c/u-boot.lds create mode 100644 configs/dragonboard410c_defconfig create mode 100644 include/configs/dragonboard410c.h
diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile index 521aa4c..1154993 100644 --- a/arch/arm/dts/Makefile +++ b/arch/arm/dts/Makefile @@ -95,6 +95,8 @@ dtb-$(CONFIG_FSL_LSCH3) += fsl-ls2080a-qds.dtb \ dtb-$(CONFIG_FSL_LSCH2) += fsl-ls1043a-qds.dtb \ fsl-ls1043a-rdb.dtb
+dtb-$(CONFIG_ARCH_SNAPDRAGON) += dragonboard410c.dtb
dtb-$(CONFIG_MACH_SUN4I) += \ sun4i-a10-a1000.dtb \ sun4i-a10-ba10-tvbox.dtb \ diff --git a/arch/arm/dts/dragonboard410c.dts b/arch/arm/dts/dragonboard410c.dts new file mode 100644 index 0000000..04ad9eb --- /dev/null +++ b/arch/arm/dts/dragonboard410c.dts @@ -0,0 +1,157 @@ +/dts-v1/;
+#include "skeleton64.dtsi"
+/ {
model = "Qualcomm Technologies, Inc. Dragonboard 410c";
compatible = "qcom,dragonboard", "qcom,apq8016-sbc";
qcom,msm-id = <0xce 0x0 0xf8 0x0 0xf9 0x0 0xfa 0x0 0xf7 0x0>;
qcom,board-id = <0x10018 0x0>;
#address-cells = <0x2>;
#size-cells = <0x2>;
memory {
device_type = "memory";
reg = <0 0x80000000 0 0x3da00000>;
};
chosen {
stdout-path = "/soc/serial@78b0000";
};
soc {
#address-cells = <0x1>;
#size-cells = <0x1>;
ranges = <0x0 0x0 0x0 0xffffffff>;
compatible = "simple-bus";
clkc: qcom,gcc@1800000 {
compatible = "qcom,gcc-msm8916";
reg = <0x1800000 0x80000>;
#address-cells = <0x1>;
#size-cells = <0x0>;
};
serial@78b0000 {
compatible = "qcom,msm-uartdm-v1.4";
reg = <0x78b0000 0x200>;
u-boot,dm-pre-reloc;
clock = <&clkc 4>;
};
restart@4ab000 {
compatible = "qcom,pshold";
reg = <0x4ab000 0x4>;
};
soc_gpios: pinctrl@1000000 {
compatible = "qcom,msm8916-pinctrl";
reg = <0x1000000 0x300000>;
gpio-controller;
gpio-count = <122>;
gpio-bank-name="soc";
#gpio-cells = <1>;
};
ehci@78d9000 {
compatible = "qcom,ehci-host";
reg = <0x78d9000 0x400>;
};
sdhci@07824000 {
compatible = "qcom,sdhci-msm-v4";
reg = <0x7824900 0x11c 0x7824000 0x800>;
bus-width = <0x8>;
index = <0x0>;
non-removable;
clock = <&clkc 0>;
clock-frequency = <100000000>;
};
sdhci@07864000 {
compatible = "qcom,sdhci-msm-v4";
reg = <0x7864900 0x11c 0x7864000 0x800>;
index = <0x1>;
bus-width = <0x4>;
clock = <&clkc 1>;
clock-frequency = <200000000>;
};
spmi@200f000 {
compatible = "qcom,spmi-pmic-arb";
reg = <0x200f000 0x1000 0x2400000 0x400000 0x2c00000 0x400000 0x3800000 0x200000 0x200a000 0x2100>;
reg-names = "core", "chnls", "obsrvr", "intr", "cnfg";
#address-cells = <0x1>;
#size-cells = <0x1>;
pm8916@0 {
compatible = "qcom,spmi-pmic";
reg = <0x0 0x2>;
#address-cells = <0x1>;
#size-cells = <0x1>;
pmic_pon: pon@800 {
compatible = "qcom,pm8916-pwrkey";
reg = <0x800 0x96>;
#gpio-cells = <2>;
gpio-controller;
};
pmic_gpios: gpios@c000 {
compatible = "qcom,pm8916-gpio";
reg = <0xc000 0x400>;
gpio-controller;
gpio-count = <4>;
#gpio-cells = <2>;
gpio-bank-name="pmic";
};
};
pm8916@1 {
compatible = "qcom,spmi-pmic";
reg = <0x1>;
#address-cells = <0x1>;
#size-cells = <0x0>;
};
};
};
leds {
compatible = "gpio-leds";
user1 {
label = "green:user1";
gpios = <&soc_gpios 21 0>;
};
user2 {
label = "green:user2";
gpios = <&soc_gpios 120 0>;
};
user3 {
label = "green:user3";
gpios = <&pmic_gpios 0 0>;
};
user4 {
label = "green:user4";
gpios = <&pmic_gpios 1 0>;
};
};
usb_hub_reset_n_pm {
gpios = <&pmic_gpios 2 0>;
};
usb_sw_sel_pm {
gpios = <&pmic_gpios 3 0>;
};
key_vol_down {
gpios = <&pmic_pon 1 0>;
};
key_power {
gpios = <&pmic_pon 0 0>;
};
+}; diff --git a/arch/arm/mach-snapdragon/Kconfig b/arch/arm/mach-snapdragon/Kconfig index 156e733..61e0e9e 100644 --- a/arch/arm/mach-snapdragon/Kconfig +++ b/arch/arm/mach-snapdragon/Kconfig @@ -3,4 +3,13 @@ if ARCH_SNAPDRAGON config SYS_SOC default "snapdragon"
+choice
prompt "Snapdragon board select"
+config TARGET_DRAGONBOARD410C
bool "96Boards Dragonboard 410C"
help
Rough description of the board and its peripherals. Should be 3-4 lines.
+endchoice
+source "board/qualcomm/dragonboard410c/Kconfig"
endif diff --git a/board/qualcomm/dragonboard410c/Kconfig b/board/qualcomm/dragonboard410c/Kconfig new file mode 100644 index 0000000..03bd7ae --- /dev/null +++ b/board/qualcomm/dragonboard410c/Kconfig @@ -0,0 +1,15 @@ +if TARGET_DRAGONBOARD410C
+config SYS_BOARD
default "dragonboard410c"
+config SYS_VENDOR
default "qualcomm"
+config SYS_SOC
default "apq8016"
+config SYS_CONFIG_NAME
default "dragonboard410c"
+endif diff --git a/board/qualcomm/dragonboard410c/Makefile b/board/qualcomm/dragonboard410c/Makefile new file mode 100644 index 0000000..cd67808 --- /dev/null +++ b/board/qualcomm/dragonboard410c/Makefile @@ -0,0 +1,8 @@ +# +# (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com +# +# SPDX-License-Identifier: GPL-2.0+ +#
+obj-y := dragonboard410c.o +extra-y += head.o diff --git a/board/qualcomm/dragonboard410c/dragonboard410c.c b/board/qualcomm/dragonboard410c/dragonboard410c.c new file mode 100644 index 0000000..7057a2c --- /dev/null +++ b/board/qualcomm/dragonboard410c/dragonboard410c.c @@ -0,0 +1,111 @@ +/*
- Board init file for Dragonboard 410C
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- SPDX-License-Identifier: GPL-2.0+
- */
+#include <common.h> +#include <dm.h> +#include <usb.h> +#include <asm/gpio.h>
+DECLARE_GLOBAL_DATA_PTR;
+int dram_init(void) +{
gd->ram_size = PHYS_SDRAM_1_SIZE;
return 0;
+}
+void dram_init_banksize(void) +{
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
+}
+static struct gpio_desc hub_reset, usb_sel;
Can these just be local variables in the function below? I doubt the function is called more than once.
+int board_prepare_usb(enum usb_init_type type) +{
int ret = 0, node;
/* Try to request gpios needed to start usb host on dragonboard */
if (!dm_gpio_is_valid(&hub_reset)) {
node = fdt_subnode_offset(gd->fdt_blob, 0,
"usb_hub_reset_n_pm");
Is this really the standard binding? I would expect a USB node with a reset-gpios property.
if (node < 0) {
printf("Failed to find usb_hub_reset_n_pm dt node.\n");
return node;
}
ret = gpio_request_by_name_nodev(gd->fdt_blob, node, "gpios", 0,
&hub_reset, 0);
if (ret < 0) {
printf("Failed to request usb_hub_reset_n_pm gpio.\n");
return ret;
}
}
if (!dm_gpio_is_valid(&usb_sel)) {
node = fdt_subnode_offset(gd->fdt_blob, 0, "usb_sw_sel_pm");
if (node < 0) {
printf("Failed to find usb_sw_sel_pm dt node.\n");
return 0;
}
ret = gpio_request_by_name_nodev(gd->fdt_blob, node, "gpios", 0,
&usb_sel, 0);
if (ret < 0) {
printf("Failed to request usb_sw_sel_pm gpio.\n");
return ret;
}
}
if (type == USB_INIT_HOST) {
/* Start USB Hub */
dm_gpio_set_dir_flags(&hub_reset,
GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE);
mdelay(100);
/* Switch usb to host connectors */
dm_gpio_set_dir_flags(&usb_sel,
GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE);
mdelay(100);
} else { /* Device */
/* Disable hub */
dm_gpio_set_dir_flags(&hub_reset, GPIOD_IS_OUT);
/* Switch back to device connector */
dm_gpio_set_dir_flags(&usb_sel, GPIOD_IS_OUT);
}
return 0;
+}
+int board_init(void) +{
return 0;
+}
+/* Check for vol- button - if pressed - stop autoboot */ +int misc_init_r(void) +{
int node;
struct gpio_desc resin;
node = fdt_subnode_offset(gd->fdt_blob, 0, "key_vol_down");
Should this be a GPIO key?
if (node < 0) {
printf("Failed to find key_vol_down node. Check device tree\n");
return 0;
}
if (gpio_request_by_name_nodev(gd->fdt_blob, node, "gpios", 0, &resin,
0)) {
printf("Failed to request key_vol_down button.\n");
return 0;
}
if (dm_gpio_get_value(&resin)) {
setenv("bootdelay", "-1");
printf("Power button pressed - dropping to console.\n");
}
return 0;
+} diff --git a/board/qualcomm/dragonboard410c/head.S b/board/qualcomm/dragonboard410c/head.S new file mode 100644 index 0000000..00d6d97 --- /dev/null +++ b/board/qualcomm/dragonboard410c/head.S @@ -0,0 +1,20 @@ +#include <config.h>
+.global _fastboot_header +_fastboot_header:
b _start
add x13, x18, #0x16
// Image load offset from start of RAM, little-endian
.quad CONFIG_SYS_TEXT_BASE-PHYS_SDRAM_1
// Effective size of kernel image, little-endian
.quad 0 //0x60000
// Informative flags, little-endian
.quad 0
.quad 0 // reserved
.quad 0 // reserved
.quad 0 // reserved
.byte 0x41 // Magic number, "ARM\x64"
.byte 0x52
.byte 0x4d
.byte 0x64
.word 0 // reserved
diff --git a/board/qualcomm/dragonboard410c/readme.txt b/board/qualcomm/dragonboard410c/readme.txt new file mode 100644 index 0000000..0f575db --- /dev/null +++ b/board/qualcomm/dragonboard410c/readme.txt @@ -0,0 +1,40 @@ +# +# (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com +# +# SPDX-License-Identifier: GPL-2.0+ +#
+Build & Run instructions:
+1) Install mkbootimg from git://codeaurora.org/quic/kernel/skales (15ece94f09 worked for me) +2) Setup CROSS_COMPILE to aarch64 compiler +3) make dragonboard410c_config +4) make +5) generate fake, empty ramdisk (can have 0 bytes) +$ touch rd
+6) generate qualcomm device tree, use dtbTool to generate it +$ dtbTool -o dt.img arch/arm/dts
+7) generate image with mkbootimg: +$ mkbootimg --kernel=u-boot-dtb.bin --output=u-boot.img --dt=dt.img --pagesize 2048 --base 0x80000000 --ramdisk=rd --cmdline=""
+Boot it with fastboot: +fastboot boot u-boot.img +or flash as kernel: +fastboot flash boot u-boot.img +fastboot reboot
+What is working: +- UART +- GPIO (SoC) +- SD +- eMMC +- Reset +- USB in EHCI mode (usb starts does switch device->host, usb stop does the opposite) +- PMIC GPIOS (but not in generic subsystem) +- PMIC "special" buttons (power, vol-)
+What is not working / known bugs: +- SDHCI is slow (~2.5MiB/s for SD and eMMC) diff --git a/board/qualcomm/dragonboard410c/u-boot.lds b/board/qualcomm/dragonboard410c/u-boot.lds new file mode 100644 index 0000000..3fb4235 --- /dev/null +++ b/board/qualcomm/dragonboard410c/u-boot.lds @@ -0,0 +1,90 @@ +/*
- Override linker script for fastboot-readable images
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- Based on arch/arm/cpu/armv8/u-boot.lds (Just add header)
- SPDX-License-Identifier: GPL-2.0+
- */
+OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64") +OUTPUT_ARCH(aarch64) +ENTRY(_fastboot_header)
I wonder if you could use the standard .lds file and amend it to support fastboot?
+SECTIONS +{
. = 0x00000000;
. = ALIGN(8);
.text :
{
*(.__image_copy_start)
board/qualcomm/dragonboard410c/head.o (.text*)
CPUDIR/start.o (.text*)
*(.text*)
}
. = ALIGN(8);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(8);
.data : {
*(.data*)
}
. = ALIGN(8);
. = .;
. = ALIGN(8);
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
}
. = ALIGN(8);
.image_copy_end :
{
*(.__image_copy_end)
}
. = ALIGN(8);
.rel_dyn_start :
{
*(.__rel_dyn_start)
}
.rela.dyn : {
*(.rela*)
}
.rel_dyn_end :
{
*(.__rel_dyn_end)
}
_end = .;
. = ALIGN(8);
.bss_start : {
KEEP(*(.__bss_start));
}
.bss : {
*(.bss*)
. = ALIGN(8);
}
.bss_end : {
KEEP(*(.__bss_end));
}
/DISCARD/ : { *(.dynsym) }
/DISCARD/ : { *(.dynstr*) }
/DISCARD/ : { *(.dynamic*) }
/DISCARD/ : { *(.plt*) }
/DISCARD/ : { *(.interp*) }
/DISCARD/ : { *(.gnu*) }
+} diff --git a/configs/dragonboard410c_defconfig b/configs/dragonboard410c_defconfig new file mode 100644 index 0000000..7dd9aae --- /dev/null +++ b/configs/dragonboard410c_defconfig @@ -0,0 +1,29 @@ +CONFIG_ARM=y +CONFIG_ARM64=y +CONFIG_ARCH_SNAPDRAGON=y +CONFIG_TARGET_DRAGONBOARD410C=y
+CONFIG_HUSH_PARSER=y +CONFIG_SYS_PROMPT="dragonboard410c => " +CONFIG_CMD_USB=y +# CONFIG_CMD_IMI is not set +# CONFIG_CMD_IMLS is not set +CONFIG_DEFAULT_DEVICE_TREE="dragonboard410c"
+CONFIG_CLK=y +CONFIG_MSM_GPIO=y +CONFIG_PM8916_GPIO=y +CONFIG_LED=y +CONFIG_LED_GPIO=y +CONFIG_RESET=y +CONFIG_DM_MMC=y +CONFIG_MSM_SDHCI=y +CONFIG_DM_PMIC=y +CONFIG_PMIC_PM8916=y +CONFIG_SPMI_MSM=y +CONFIG_MSM_SERIAL=y +CONFIG_USB=y +CONFIG_DM_USB=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_MSM=y +CONFIG_USB_STORAGE=y
CONFIG_DM_ETH=y
diff --git a/include/configs/dragonboard410c.h b/include/configs/dragonboard410c.h new file mode 100644 index 0000000..132ed75 --- /dev/null +++ b/include/configs/dragonboard410c.h @@ -0,0 +1,184 @@ +/*
- Board configuration file for Dragonboard 410C
- (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
- SPDX-License-Identifier: GPL-2.0+
- */
+#ifndef __CONFIGS_DRAGONBOARD410C_H +#define __CONFIGS_DRAGONBOARD410C_H
+#include <linux/sizes.h> +#include <asm/arch/sysmap-apq8016.h>
+#define CONFIG_IDENT_STRING "\nQualcomm-DragonBoard 410C"
+#define CONFIG_MISC_INIT_R /* To stop autoboot */
+/* Flat Device Tree Definitions */ +#define CONFIG_OF_LIBFDT
+/* Physical Memory Map */ +#define CONFIG_NR_DRAM_BANKS 1 +#define PHYS_SDRAM_1 0x80000000 +/* 1008 MB (the last ~30Mb are secured for TrustZone by ATF*/ +#define PHYS_SDRAM_1_SIZE 0x3da00000 +#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_1 +#define CONFIG_SYS_TEXT_BASE 0x80080000 +#define CONFIG_SYS_INIT_SP_ADDR (CONFIG_SYS_SDRAM_BASE + 0x7fff0) +#define CONFIG_SYS_LOAD_ADDR (CONFIG_SYS_SDRAM_BASE + 0x80000) +#define CONFIG_SYS_BOOTM_LEN 0x1000000 /* 16MB max kernel size */
+/* UART */ +#define CONFIG_BAUDRATE 115200
+/* Generic Timer Definitions */ +#define COUNTER_FREQUENCY 19000000
+/* This are needed to have proper mmc support */ +#define CONFIG_MMC +#define CONFIG_GENERIC_MMC +#define CONFIG_SDHCI
+#define CONFIG_SYS_LDSCRIPT "board/qualcomm/dragonboard410c/u-boot.lds"
+/* Fixup - in init code we switch from device to host mode,
- it has to be done after each HCD reset */
+#define CONFIG_EHCI_HCD_INIT_AFTER_RESET +/* Needed for Host Controller driver */ +#define CONFIG_USB_ULPI_VIEWPORT
+#define CONFIG_USB_HOST_ETHER /* Enable USB Networking */
+/* Support all possible USB ethernet dongles */ +#define CONFIG_USB_ETHER_DM9601 +#define CONFIG_USB_ETHER_ASIX +#define CONFIG_USB_ETHER_ASIX88179 +#define CONFIG_USB_ETHER_MCS7830 +#define CONFIG_USB_ETHER_SMSC95XX
+/* Libraries */ +#define CONFIG_MD5
+/* Extra Commands */ +#define CONFIG_CMD_CACHE +#define CONFIG_CMD_DHCP +#define CONFIG_CMD_ENV +#define CONFIG_CMD_FAT /* FAT support */ +#define CONFIG_CMD_GPIO +#define CONFIG_CMD_GPT +#define CONFIG_CMD_MD5SUM +#define CONFIG_CMD_MEMINFO /* meminfo */ +#define CONFIG_CMD_MMC +/* Enable that for switching of boot partitions */ +/* Disabled by default as some sub-commands can brick eMMC */ +/*#define CONFIG_SUPPORT_EMMC_BOOT */ +#define CONFIG_CMD_PART +#define CONFIG_CMD_PING +#define CONFIG_CMD_REGINFO /* Register dump */ +#define CONFIG_CMD_TFTP +#define CONFIG_CMD_TIMER +#define CONFIG_CMD_UNZIP +#define CONFIG_CMD_BOOTZ +#define CONFIG_CMD_BOOTI
+/* Command line configuration */ +#define CONFIG_MENU +#define CONFIG_SYS_LONGHELP
+/* Partition table support */ +#define HAVE_BLOCK_DEVICE /* Needed for partition commands */ +#define CONFIG_DOS_PARTITION +#define CONFIG_EFI_PARTITION +#define CONFIG_PARTITION_UUIDS
+/* BOOTP options */ +#define CONFIG_BOOTP_BOOTFILESIZE
+/* Environment - Boot*/ +#define CONFIG_BOOTDELAY 2 /* autoboot after 2 seconds */
+#define CONFIG_NFSBOOTCOMMAND "" +#define CONFIG_BOOTCOMMAND "usb start && dhcp && tftp && usb stop && bootm" +#define CONFIG_BOOTARGS "console=ttyMSM0,115200n8"
+/* Does what recovery does */ +#define REFLASH(file, part) \ +"part start mmc 0 "#part" start && "\ +"part size mmc 0 "#part" size && "\ +"tftp $loadaddr "#file" &&" \ +"mmc write $loadaddr $start $size &&"
+#define CONFIG_ENV_REFLASH \ +"mmc dev 0 &&"\ +"usb start &&"\ +"dhcp &&"\ +"tftp $loadaddr dragonboard/rescue/gpt_both0.bin && "\ +"mmc write $loadaddr 0 43 &&" \ +"mmc rescan &&"\ +REFLASH(dragonboard/rescue/NON-HLOS.bin, 1)\ +REFLASH(dragonboard/rescue/sbl1.mbn, 2)\ +REFLASH(dragonboard/rescue/rpm.mbn, 3)\ +REFLASH(dragonboard/rescue/tz.mbn, 4)\ +REFLASH(dragonboard/rescue/hyp.mbn, 5)\ +REFLASH(dragonboard/rescue/sec.dat, 6)\ +REFLASH(dragonboard/rescue/emmc_appsboot.mbn, 7)\ +REFLASH(dragonboard/u-boot.img, 8)\ +"usb stop &&"\ +"echo Reflash completed"
+#define CONFIG_UBOOT_REFLASH \ +"mmc dev 0 &&"\ +"usb start &&"\ +"dhcp &&"\ +"part start mmc 0 8 start && "\ +"setenv size 0x800 &&"\ +"tftp $loadaddr dragonboard/u-boot.img &&" \ +"mmc write $loadaddr $start $size &&"\ +"usb stop &&"\ +"echo Reflash completed &&" \ +"reset"
+/* Environment */ +#define CONFIG_EXTRA_ENV_SETTINGS \
"reflash="CONFIG_ENV_REFLASH"\0"\
"reflash_uboot="CONFIG_UBOOT_REFLASH"\0"\
"loadaddr=0x81000000\0" \
"fdt_high=0xffffffffffffffff\0" \
"initrd_high=0xffffffffffffffff\0" \
"linux_image=dragonboard/Image\0" \
"linux_addr=0x81000000\0"\
"fdt_image=dragonboard/apq8016-sbc.dtb\0" \
"fdt_addr=0x83000000\0"\
"ramdisk_addr=0x84000000\0"\
"ramdisk_image=dragonboard/initrd.img\0" \
"dl_uboot=tftp $loadaddr dragonboard/u-boot.img\0"\
"dl_kernel=tftp $linux_addr $linux_image " \
"&& tftp $fdt_addr $fdt_image\0"\
"dl_ramdisk=tftp $ramdisk_addr $ramdisk_image\0"\
"nboot_nord=usb start && run dl_kernel && usb stop && " \
"booti $linux_addr - $fdt_addr\0"\
"nboot_rd=usb start && run dl_kernel && run dl_ramdisk && "\
"booti $linux_addr $ramdisk_addr $fdt_addr\0"
+#define CONFIG_ENV_IS_NOWHERE +#define CONFIG_ENV_SIZE 0x1000 +#define CONFIG_ENV_VARS_UBOOT_CONFIG +#define CONFIG_SYS_NO_FLASH
+/* Size of malloc() pool */ +#define CONFIG_SYS_MALLOC_LEN (CONFIG_ENV_SIZE + SZ_8M)
+/* Monitor Command Prompt */ +#define CONFIG_SYS_CBSIZE 512 /* Console I/O Buffer Size */ +#define CONFIG_SYS_PBSIZE (CONFIG_SYS_CBSIZE + \
sizeof(CONFIG_SYS_PROMPT) + 16)
+#define CONFIG_SYS_BARGSIZE CONFIG_SYS_CBSIZE +#define CONFIG_SYS_LONGHELP +#define CONFIG_CMDLINE_EDITING +#define CONFIG_SYS_MAXARGS 64 /* max command args */
+#endif
2.5.0
Regards, Simon

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256
Hi Simon,
On 16.12.2015 23:29, Simon Glass wrote:
Hi Mateusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
[...]
+int board_prepare_usb(enum usb_init_type type) +{
int ret = 0, node;
/* Try to request gpios needed to start usb host on dragonboard */
if (!dm_gpio_is_valid(&hub_reset)) {
node = fdt_subnode_offset(gd->fdt_blob, 0,
"usb_hub_reset_n_pm");
Is this really the standard binding? I would expect a USB node with a reset-gpios property.
That's a good idea.
[...]
+/* Check for vol- button - if pressed - stop autoboot */ +int misc_init_r(void) +{
int node;
struct gpio_desc resin;
node = fdt_subnode_offset(gd->fdt_blob, 0, "key_vol_down");
Should this be a GPIO key?
Yes, but I couldn't find something like that in U-Boot.. did I missed it?
[...]
+OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64") +OUTPUT_ARCH(aarch64) +ENTRY(_fastboot_header)
I wonder if you could use the standard .lds file and amend it to support fastboot?
Perhaps it's a good idea to make it generic option (at least for ARMv8 devices).
I'm not sure is it "generic" fastboot header or only works for Little Kernel on Qualcomm devices though.
@Albert what do you think?
+SECTIONS +{
. = 0x00000000;
. = ALIGN(8);
.text :
{
*(.__image_copy_start)
board/qualcomm/dragonboard410c/head.o (.text*)
CPUDIR/start.o (.text*)
*(.text*)
}
Regards, Mateusz

Hi Mateusz,
On 19 December 2015 at 05:24, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256
Hi Simon,
On 16.12.2015 23:29, Simon Glass wrote:
Hi Mateusz,
On 10 December 2015 at 14:41, Mateusz Kulikowski mateusz.kulikowski@gmail.com wrote:
[...]
+int board_prepare_usb(enum usb_init_type type) +{
int ret = 0, node;
/* Try to request gpios needed to start usb host on dragonboard */
if (!dm_gpio_is_valid(&hub_reset)) {
node = fdt_subnode_offset(gd->fdt_blob, 0,
"usb_hub_reset_n_pm");
Is this really the standard binding? I would expect a USB node with a reset-gpios property.
That's a good idea.
[...]
+/* Check for vol- button - if pressed - stop autoboot */ +int misc_init_r(void) +{
int node;
struct gpio_desc resin;
node = fdt_subnode_offset(gd->fdt_blob, 0, "key_vol_down");
Should this be a GPIO key?
Yes, but I couldn't find something like that in U-Boot.. did I missed it?
[...]
+OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64") +OUTPUT_ARCH(aarch64) +ENTRY(_fastboot_header)
I wonder if you could use the standard .lds file and amend it to support fastboot?
Perhaps it's a good idea to make it generic option (at least for ARMv8 devices).
I'm not sure is it "generic" fastboot header or only works for Little Kernel on Qualcomm devices though.
@Albert what do you think?
I see later that you plan to remove this one day, so perhaps it is fine to keep it as it is.
+SECTIONS +{
. = 0x00000000;
. = ALIGN(8);
.text :
{
*(.__image_copy_start)
board/qualcomm/dragonboard410c/head.o (.text*)
CPUDIR/start.o (.text*)
*(.text*)
}
Regards, Simon

With a slight delay (Sorry Syed) I'm finally ready to show code I've been working recently on.
Thanks for the patches. I will update my git tree for snapdragon_810 with these changes. I only have serial and cache support working on 810 and it boots to u-boot prompt. The arch/arm/cpu/armv8/cache_v8.c/mmu_setup() causes the board to reset. I didn't dig deeper. I switched to use mmu_setup() as implemented in arch/arm/cpu/armv8/zynqmp/cpu.c and everything works fine.
create mode 100644 arch/arm/mach-snapdragon/reset.c
Can you rename this arch/arm/mach-snapdragon/reset-apq8016.c as I have arch/arm/mach-snapdragon/reset-apq8094.c to be added.
Will provide more feedback going forward.
Thanks -syed

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256
On 15.12.2015 19:57, sk.syed2 wrote:
With a slight delay (Sorry Syed) I'm finally ready to show code I've been working recently on.
Thanks for the patches. I will update my git tree for snapdragon_810 with these changes. I only have serial and cache support working on 810 and it boots to u-boot prompt. The arch/arm/cpu/armv8/cache_v8.c/mmu_setup() causes the board to reset. I didn't dig deeper. I switched to use mmu_setup() as implemented in arch/arm/cpu/armv8/zynqmp/cpu.c and everything works fine.
Try to make it able to boot Linux somehow (either via mmc or usb or somehow).
After I'm done with this series for 410C I can take a look at drivers / dts for 810 and try to give you some hints.
create mode 100644 arch/arm/mach-snapdragon/reset.c
Can you rename this arch/arm/mach-snapdragon/reset-apq8016.c as I have arch/arm/mach-snapdragon/reset-apq8094.c to be added.
Sure - will do that.
Will provide more feedback going forward.
Great
Thanks -syed
participants (6)
-
Marek Vasut
-
Masahiro Yamada
-
Mateusz Kulikowski
-
Simon Glass
-
sk.syed2
-
Tom Rini