Implement DT quirks for the am33xx beaglebone boards. A single boot DTB supports both the beaglebone white and the beaglebone black, and on boot time the identifying EEPROM is read and the info is used to modify the live tree to match the board detected. Refer to Documentation/devicetree/bindings/quirks/am33xx-bone-quirk.txt for detailed information. Signed-off-by: Pantelis Antoniou <pantelis.antoniou@xxxxxxxxxxxx> --- .../bindings/quirks/am33xx-bone-quirk.txt | 82 ++++ arch/arm/mach-omap2/Makefile | 5 + arch/arm/mach-omap2/am33xx-dt-quirks.c | 498 +++++++++++++++++++++ arch/arm/mach-omap2/am33xx-dt-quirks.h | 10 + arch/arm/mach-omap2/board-generic.c | 1 + arch/arm/mach-omap2/common.h | 8 + 6 files changed, 604 insertions(+) create mode 100644 Documentation/devicetree/bindings/quirks/am33xx-bone-quirk.txt create mode 100644 arch/arm/mach-omap2/am33xx-dt-quirks.c create mode 100644 arch/arm/mach-omap2/am33xx-dt-quirks.h diff --git a/Documentation/devicetree/bindings/quirks/am33xx-bone-quirk.txt b/Documentation/devicetree/bindings/quirks/am33xx-bone-quirk.txt new file mode 100644 index 0000000..239dd8d --- /dev/null +++ b/Documentation/devicetree/bindings/quirks/am33xx-bone-quirk.txt @@ -0,0 +1,82 @@ +am33xx Beaglebone White/Black Device Tree Bindings +-------------------------------------------------- + +Beaglebone boards that boot with a common device tree blob contain +a DT quirk entry with the following properties: + +Required properties: + +- compatible : should be "ti,am33xx-bone-quirk" + +Required child node: + +- revs : Contains children nodes that identify boards + + Each child node of revs must be of the following form: + + Required properties: + + - board-id : Contain the board ID as it appears in the EEPROM + - board-apply : Should contain the phandle of the quirk to apply + + Optional child node: + + - options : Contain a number of properties that identify options + that can be enabled or disabled by external means. For Linux the + options are selected by parsing the boot command line. + The properties contain a tupple of quirk phandles, the first containing + the enable quirk and the second the disable quirk. If the phandle is zero + then no quirk is applied. + +Any other nodes (like the quirk definitions are ignored). + +The following example defines two revisions (white/black) with +board-id A335BONE for the white and A335BNLT for the black. +For the beaglebone black we also define two options +no-emmc and no-hdmi which when are present in the boot command line +the emmc and hdmi are disabled. + + beaglebone-quirks { + compatible = "ti,am33xx-bone-quirk"; + status = "okay"; + + revs { + white { + board-id = "A335BONE"; + board-apply = <&bonewhite>; + }; + black { + board-id = "A335BNLT"; + board-apply = <&boneblack>; + options { + no-emmc = <0 &black_enable_emmc>; + no-hdmi = <0 &black_enable_hdmi>; + }; + }; + }; + + overlays { + bonewhite: bonewhite { + /* quirk for white */ + ... + }; + + boneblack: boneblack { + /* quirk for black */ + ... + }; + + black_enable_hdmi: black_hdmi { + /* quirk to enable hdmi */ + ... + }; + + black_enable_emmc: black_emmc { + /* quirk to enable emmc */ + ... + }; + }; + }; + + + diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile index 5d27dfd..02129e7 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile @@ -259,6 +259,11 @@ obj-$(CONFIG_MACH_CRANEBOARD) += board-am3517crane.o obj-$(CONFIG_MACH_SBC3530) += board-omap3stalker.o +# DT quirks +ifneq ($(CONFIG_OF_DYNAMIC),) +obj-$(CONFIG_SOC_AM33XX) += am33xx-dt-quirks.o +endif + # Platform specific device init code omap-flash-$(CONFIG_MTD_NAND_OMAP2) := board-flash.o diff --git a/arch/arm/mach-omap2/am33xx-dt-quirks.c b/arch/arm/mach-omap2/am33xx-dt-quirks.c new file mode 100644 index 0000000..668235c --- /dev/null +++ b/arch/arm/mach-omap2/am33xx-dt-quirks.c @@ -0,0 +1,498 @@ +/* + * arch/arm/mach-omap2/am33xx-dt-quirks.c + * + * AM33xx variant DT quirks + * + * Copyright (C) 2015 Konsulko Group + * + * Author: + * Pantelis Antoniou <pantelis.antoniou@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <asm/io.h> +#include <asm/delay.h> + +#include "am33xx-dt-quirks.h" + +/* + * The board IDs for am33xx board are in an I2C EEPROM + * We are very early in the boot process so we have to + * read the EEPROM directly without using the I2C layer. + * + * Note that we rely on the bootloader setting up the muxes + * (which is the case for u-boot). + */ + +/* I2C Status Register (OMAP_I2C_STAT): */ +#define OMAP_I2C_STAT_XDR (1 << 14) /* TX Buffer draining */ +#define OMAP_I2C_STAT_RDR (1 << 13) /* RX Buffer draining */ +#define OMAP_I2C_STAT_BB (1 << 12) /* Bus busy */ +#define OMAP_I2C_STAT_ROVR (1 << 11) /* Receive overrun */ +#define OMAP_I2C_STAT_XUDF (1 << 10) /* Transmit underflow */ +#define OMAP_I2C_STAT_AAS (1 << 9) /* Address as slave */ +#define OMAP_I2C_STAT_BF (1 << 8) /* Bus Free */ +#define OMAP_I2C_STAT_XRDY (1 << 4) /* Transmit data ready */ +#define OMAP_I2C_STAT_RRDY (1 << 3) /* Receive data ready */ +#define OMAP_I2C_STAT_ARDY (1 << 2) /* Register access ready */ +#define OMAP_I2C_STAT_NACK (1 << 1) /* No ack interrupt enable */ +#define OMAP_I2C_STAT_AL (1 << 0) /* Arbitration lost int ena */ + +/* I2C Configuration Register (OMAP_I2C_CON): */ +#define OMAP_I2C_CON_EN (1 << 15) /* I2C module enable */ +#define OMAP_I2C_CON_BE (1 << 14) /* Big endian mode */ +#define OMAP_I2C_CON_OPMODE_HS (1 << 12) /* High Speed support */ +#define OMAP_I2C_CON_STB (1 << 11) /* Start byte mode (master) */ +#define OMAP_I2C_CON_MST (1 << 10) /* Master/slave mode */ +#define OMAP_I2C_CON_TRX (1 << 9) /* TX/RX mode (master only) */ +#define OMAP_I2C_CON_XA (1 << 8) /* Expand address */ +#define OMAP_I2C_CON_RM (1 << 2) /* Repeat mode (master only) */ +#define OMAP_I2C_CON_STP (1 << 1) /* Stop cond (master only) */ +#define OMAP_I2C_CON_STT (1 << 0) /* Start condition (master) */ + +/* register definitions */ +#define I2C_REVNB_LO 0x00 +#define I2C_REVNB_HI 0x04 +#define I2C_SYSC 0x10 +#define I2C_IRQSTATUS_RAW 0x24 +#define I2C_IRQSTATUS 0x28 +#define I2C_IRQENABLE_SET 0x2C +#define I2C_IRQENABLE_CLR 0x30 +#define I2C_WE 0x34 +#define I2C_DMARXENABLE_SET 0x38 +#define I2C_DMATXENABLE_SET 0x3C +#define I2C_DMARXENABLE_CLR 0x40 +#define I2C_DMATXENABLE_CLR 0x44 +#define I2C_DMARXWAKE_EN 0x48 +#define I2C_DMATXWAKE_EN 0x4C +#define I2C_SYSS 0x90 +#define I2C_BUF 0x94 +#define I2C_CNT 0x98 +#define I2C_DATA 0x9C +#define I2C_CON 0xA4 +#define I2C_OA 0xA8 +#define I2C_SA 0xAC +#define I2C_PSC 0xB0 +#define I2C_SCLL 0xB4 +#define I2C_SCLH 0xB8 +#define I2C_SYSTEST 0xBC +#define I2C_BUFSTAT 0xC0 +#define I2C_OA1 0xC4 +#define I2C_OA2 0xC8 +#define I2C_OA3 0xCC +#define I2C_ACTOA 0xD0 +#define I2C_SBLOCK 0xD4 + +static inline void i2c_reg_write(void __iomem *base, unsigned int reg, u16 val) +{ + writew_relaxed(val, base + reg); +} + +static inline u16 i2c_reg_read(void __iomem *base, unsigned int reg) +{ + return readw_relaxed(base + reg); +} + +static void flush_fifo(void __iomem *base) +{ + u16 stat; + + while ((stat = i2c_reg_read(base, I2C_IRQSTATUS_RAW)) + & OMAP_I2C_STAT_RRDY) { + (void)i2c_reg_read(base, I2C_DATA); + i2c_reg_write(base, I2C_IRQSTATUS, OMAP_I2C_STAT_RRDY); + udelay(1000); + }; +} + +static void wait_delay(void __iomem *base) +{ + udelay((10000000 / 100000) * 2); +} + +static int wait_for_bb(void __iomem *base) +{ + int timeout; + u16 stat; + + timeout = 1000; + while (((stat = i2c_reg_read(base, I2C_IRQSTATUS_RAW)) & + OMAP_I2C_STAT_BB) && timeout--) { + i2c_reg_write(base, I2C_IRQSTATUS, stat); + wait_delay(base); + } + + if (timeout <= 0) { + pr_err("%s: Timeout while waiting for bus\n", __func__); + return -1; + } + i2c_reg_write(base, I2C_IRQSTATUS, 0xffff); + return 0; +} + +static u16 wait_for_event(void __iomem *base) +{ + u16 status; + int timeout = 10000; + + do { + wait_delay(base); + status = i2c_reg_read(base, I2C_IRQSTATUS_RAW); + } while (!(status & (OMAP_I2C_STAT_ROVR | OMAP_I2C_STAT_XUDF | + OMAP_I2C_STAT_XRDY | OMAP_I2C_STAT_RRDY | + OMAP_I2C_STAT_ARDY | OMAP_I2C_STAT_NACK | + OMAP_I2C_STAT_AL)) && + timeout--); + + if (timeout <= 0) { + pr_err("%s: Timeout status=%04x\n", __func__, status); + i2c_reg_write(base, I2C_IRQSTATUS, 0xffff); + status = 0; + } + + return status; +} + +static int i2c_init(void __iomem *base, u16 psc, u16 scll, u16 sclh) +{ + int timeout; + + /* init */ + if (i2c_reg_read(base, I2C_CON) & OMAP_I2C_CON_EN) { + i2c_reg_write(base, I2C_CON, 0); + for (timeout = 0; timeout <= 50000; timeout += 1000) + udelay(1000); + } + + /* soft reset */ + i2c_reg_write(base, I2C_SYSC, 0x02); + udelay(1000); + i2c_reg_write(base, I2C_CON, OMAP_I2C_CON_EN); + + timeout = 1000; + while (!(i2c_reg_read(base, I2C_SYSS) & 0x0001) && timeout--) { + if (timeout <= 0) { + pr_err("%s: Timeout in soft reset\n", __func__); + return -ENODEV; + } + udelay(1000); + } + + i2c_reg_write(base, I2C_CON, 0x0000); + i2c_reg_write(base, I2C_PSC, psc); + i2c_reg_write(base, I2C_SCLL, scll); + i2c_reg_write(base, I2C_SCLH, sclh); + i2c_reg_write(base, I2C_CON, 0x8000); + udelay(1000); + + i2c_reg_write(base, I2C_OA, 1); + + /* flush fifo */ + flush_fifo(base); + i2c_reg_write(base, I2C_IRQSTATUS, 0xffff); + + return 0; +} + +static int i2c_read(void __iomem *base, u8 chip, u16 addr, + unsigned int alen, + void *buffer, int len) +{ + u8 *s = buffer; + unsigned int i; + u16 status; + int timeout, ret; + + if (alen < 0 || len < 0 || !buffer || alen > 2 || + (addr + len > 0x10000)) + return -EINVAL; + + if (wait_for_bb(base) != 0) { + pr_err("%s: wait for bb fail\n", __func__); + return -ENODEV; + } + + ret = -EINVAL; + i2c_reg_write(base, I2C_SA, chip); + + if (alen) { + i2c_reg_write(base, I2C_CNT, alen); + + /* Stop - Start (P-S) */ + i2c_reg_write(base, I2C_CON, OMAP_I2C_CON_EN | + OMAP_I2C_CON_MST | OMAP_I2C_CON_STT | + OMAP_I2C_CON_STP | OMAP_I2C_CON_TRX); + while (alen > 0) { + status = wait_for_event(base); + if (status == 0 || (status & OMAP_I2C_STAT_NACK)) { + ret = -ENODEV; + pr_err("%s: error waiting for addr ACK\n", + __func__); + goto out; + } + if (status & OMAP_I2C_STAT_XRDY) { + alen--; + i2c_reg_write(base, I2C_DATA, + (addr >> (8 * alen)) & 0xff); + i2c_reg_write(base, I2C_IRQSTATUS, + OMAP_I2C_STAT_XRDY); + } + } + + /* poll ARDY for last byte going out */ + timeout = 1000; + do { + status = wait_for_event(base); + if (status & OMAP_I2C_STAT_ARDY) + break; + udelay(1000); + } while (timeout--); + + if (timeout <= 0) { + ret = -ENODEV; + pr_err("%s: timeout waiting for ARDY\n", + __func__); + goto out; + } + + i2c_reg_write(base, I2C_IRQSTATUS, OMAP_I2C_STAT_ARDY); + + wait_delay(base); + } + + i2c_reg_write(base, I2C_CNT, len); + + i2c_reg_write(base, I2C_CON, OMAP_I2C_CON_EN | OMAP_I2C_CON_MST | + OMAP_I2C_CON_STT | OMAP_I2C_CON_STP); + + i = 0; + while (i < len) { + status = wait_for_event(base); + if (status == 0 || (status & OMAP_I2C_STAT_NACK)) { + ret = -ENODEV; + pr_err("%s: error waiting for data ACK\n", + __func__); + goto out; + } + if (status & OMAP_I2C_STAT_RRDY) { + *s++ = (u8)i2c_reg_read(base, I2C_DATA); + i2c_reg_write(base, I2C_IRQSTATUS, + OMAP_I2C_STAT_RRDY); + i++; + } + if (status & OMAP_I2C_STAT_ARDY) { + i2c_reg_write(base, I2C_IRQSTATUS, + OMAP_I2C_STAT_ARDY); + break; + } + } + + if (i < len) + pr_err("%s: short read (%u < %u)\n", __func__, i, len); + ret = i; + +out: + flush_fifo(base); + i2c_reg_write(base, I2C_IRQSTATUS, 0xffff); + return ret; +} + +struct am335x_baseboard_id { + u8 magic[4]; + u8 name[8]; + u8 version[4]; + u8 serial[12]; + u8 config[32]; + u8 mac_addr[3][6]; +}; + +static int beaglebone_read_header(struct am335x_baseboard_id *hdr) +{ + void __iomem *base; + u32 magic; + int ret; + + /* map I2C0 */ + base = ioremap((phys_addr_t)0x44E0B000, 0x1000); + if (base == NULL) { + pr_err("%s: failed to ioremap\n", __func__); + return -ENOMEM; + } + + ret = i2c_init(base, 0x0000, 0x00ea, 0x00ea); + if (ret != 0) { + pr_err("%s: i2c_init failed\n", __func__); + return ret; + } + + /* the EEPROM is at 0x50 */ + ret = i2c_read(base, 0x50, 0, 2, hdr, sizeof(*hdr)); + if (ret != sizeof(*hdr)) { + pr_err("%s: Failed to read EEPROM\n", __func__); + ret = ret >= 0 ? -EINVAL : ret; + goto out; + } + + print_hex_dump(KERN_DEBUG, "EEPROM: ", DUMP_PREFIX_OFFSET, + 16, 1, hdr, sizeof(*hdr), true); + + /* magic value is LE */ + magic = ((u32)hdr->magic[3] << 24) | ((u32)hdr->magic[2] << 16) | + ((u32)hdr->magic[1] << 8) | hdr->magic[0]; + if (magic != 0xEE3355AA) { + pr_err("%s: Bad EEPROM (0x%08x) %02x %02x %02x %02x\n", + __func__, magic, + hdr->magic[0], hdr->magic[1], + hdr->magic[2], hdr->magic[3]); + ret = -EINVAL; + goto out; + } + + ret = 0; + +out: + iounmap(base); + return ret; +} + +/* find whether a command line argument exists */ +static const char *command_line_arg(const char *bootargs, const char *what) +{ + const char *s, *e, *p; + int len; + + len = strlen(what); + s = bootargs; + e = bootargs + strlen(bootargs); + for (; s < e && (p = strstr(s, what)) != NULL; s += len) { + + /* if not the first arguments a space must precede */ + if (p > bootargs && p[-1] != ' ') + continue; + + /* skip over what */ + p += len; + + /* if not the last argument a space must follow */ + if (p < e && *p != ' ') + continue; + + return p; + } + + return NULL; +} + +static void __init beaglebone_dt_quirk(void) +{ + struct am335x_baseboard_id header; + struct device_node *np, *revnp, *child, *optnp, *chosen; + struct property *prop; + char name[8 + 1]; + const char *detected_board_id, *board_id, *bootargs; + int i, ret; + phandle ph; + + np = NULL; + revnp = NULL; + optnp = NULL; + chosen = NULL; + + /* beaglebone quirks */ + np = of_find_compatible_node(NULL, NULL, "ti,am33xx-bone-quirk"); + if (!np || !of_device_is_available(np)) + goto out; + + revnp = of_get_child_by_name(np, "revs"); + if (!revnp) { + pr_err("%s: no revs node at %pO\n", __func__, np); + goto out; + } + child = NULL; + optnp = NULL; + chosen = NULL; + + ret = beaglebone_read_header(&header); + if (ret != 0) { + pr_err("%s: Failed to read EEPROM\n", __func__); + goto out; + } + memcpy(name, header.name, sizeof(header.name)); + name[sizeof(header.name)] = '\0'; + + detected_board_id = name; + + pr_debug("%s: Finding quirks for board_id=%s\n", __func__, + detected_board_id); + for_each_child_of_node(revnp, child) { + if (!of_property_read_string(child, "board-id", &board_id) && + !strcmp(board_id, detected_board_id)) + goto found; + } + pr_warn("%s: No quirks for board_id=%s\n", __func__, detected_board_id); + goto out; +found: + pr_debug("%s: Applying quirks for board_id=%s\n", __func__, + board_id); + ret = 0; + for (i = 0; of_property_read_u32_index(child, + "board-apply", i, &ph) == 0; i++) { + + ret = of_quirk_apply_by_phandle(ph); + if (ret != 0) + break; + } + if (ret != 0) { + pr_err("%s: Failed to apply quirk at %pO\n", __func__, child); + goto out; + } + + optnp = of_get_child_by_name(child, "options"); + chosen = of_find_node_by_path("/chosen"); + bootargs = NULL; + if (chosen) { + if (of_property_read_string(chosen, "bootargs", &bootargs)) + bootargs = NULL; + of_node_put(chosen); + } + if (optnp && bootargs) { + /* iterate on properties */ + for_each_property_of_node(optnp, prop) { + if (!strcmp(prop->name, "name")) + continue; + + if (command_line_arg(bootargs, prop->name)) + i = 0; + else + i = 1; + if (of_property_read_u32_index(optnp, + prop->name, i, &ph) != 0) { + pr_err("%s: Failed to get phandle at %pO/%s\n", + __func__, optnp, prop->name); + continue; + } + ret = of_quirk_apply_by_phandle(ph); + if (ret != 0) + break; + } + } + +out: + of_node_put(optnp); + of_node_put(child); + of_node_put(revnp); + of_node_put(np); +} + +void __init am33xx_dt_quirk(void) +{ + /* Manually issue calls for each supported board variant */ + + /* the beaglebone is the one supported board for now */ + beaglebone_dt_quirk(); +} diff --git a/arch/arm/mach-omap2/am33xx-dt-quirks.h b/arch/arm/mach-omap2/am33xx-dt-quirks.h new file mode 100644 index 0000000..8c7dbec --- /dev/null +++ b/arch/arm/mach-omap2/am33xx-dt-quirks.h @@ -0,0 +1,10 @@ +#ifndef AM33XX_DT_QUIRKS_H +#define AM33XX_DT_QUIRKS_H + +#if IS_ENABLED(OF_DYNAMIC) +extern void __init am33xx_dt_quirk(void); +#else +#define am33xx_dt_quirk NULL +#endif + +#endif diff --git a/arch/arm/mach-omap2/board-generic.c b/arch/arm/mach-omap2/board-generic.c index b61c049..9922c61 100644 --- a/arch/arm/mach-omap2/board-generic.c +++ b/arch/arm/mach-omap2/board-generic.c @@ -177,6 +177,7 @@ DT_MACHINE_START(AM33XX_DT, "Generic AM33XX (Flattened Device Tree)") .init_time = omap3_gptimer_timer_init, .dt_compat = am33xx_boards_compat, .restart = am33xx_restart, + .dt_quirk = am33xx_dt_quirk, MACHINE_END #endif diff --git a/arch/arm/mach-omap2/common.h b/arch/arm/mach-omap2/common.h index 64e44d6..6b4d6e0 100644 --- a/arch/arm/mach-omap2/common.h +++ b/arch/arm/mach-omap2/common.h @@ -155,6 +155,14 @@ static inline void am33xx_restart(enum reboot_mode mode, const char *cmd) } #endif +#ifdef CONFIG_SOC_AM33XX +void am33xx_dt_quirk(void); +#else +static inline void am33xx_dt_quirk(void) +{ +} +#endif + #ifdef CONFIG_ARCH_OMAP3 void omap3xxx_restart(enum reboot_mode mode, const char *cmd); #else -- 1.7.12 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html