[U-Boot] [PATCH] w1: Add driver for i.MX bus master controller

Two variants of controllers are supported: V1 (bitwise only) found in i.MX21, i.MX27, i.MX31, i.MX51 V2 (byte operations) found in i.MX25, i.MX35, i.MX50, i.MX53
Only tested on i.MX53 hardware but in both modes (by modifying the device tree).
Signed-off-by: Martin Fuzzey martin.fuzzey@flowbird.group --- doc/device-tree-bindings/w1/mxc-w1.txt | 37 ++++++ drivers/w1/Kconfig | 14 ++ drivers/w1/Makefile | 1 + drivers/w1/mxc_w1.c | 232 +++++++++++++++++++++++++++++++++ 4 files changed, 284 insertions(+) create mode 100644 doc/device-tree-bindings/w1/mxc-w1.txt create mode 100644 drivers/w1/mxc_w1.c
diff --git a/doc/device-tree-bindings/w1/mxc-w1.txt b/doc/device-tree-bindings/w1/mxc-w1.txt new file mode 100644 index 0000000..1fb49cc --- /dev/null +++ b/doc/device-tree-bindings/w1/mxc-w1.txt @@ -0,0 +1,37 @@ +NXP i.MX (MXC) One wire bus master controller +======================= + +Child nodes are required in device tree. The driver will detect +the devices serial number and then search in the child nodes in the device tree +for the proper node and try to match it with the device. + +Also check doc/device-tree-bindings/w1-eeprom for possible child nodes drivers + +Driver: +- drivers/w1/mxc_w1.c + +Required properties: +- compatible : should be one of + "fsl,imx21-owire", "fsl,imx27-owire", "fsl,imx31-owire", "fsl,imx25-owire" + "fsl,imx25-owire", "fsl,imx35-owire", "fsl,imx50-owire", "fsl,imx53-owire" + +- reg : Address and length of the register set for the device + +Optional: +* none + +Example: + onewire { + compatible = "fsl,imx53-owire"; + reg = <0x63fa4000 0x4000>; + }; + +Example with child: + onewire { + compatible = "fsl,imx53-owire"; + reg = <0x63fa4000 0x4000>; + + eeprom1: eeprom@0 { + compatible = "maxim,ds24xxx"; + }; + }; diff --git a/drivers/w1/Kconfig b/drivers/w1/Kconfig index d6e0457..031bab2 100644 --- a/drivers/w1/Kconfig +++ b/drivers/w1/Kconfig @@ -20,6 +20,20 @@ config W1_GPIO help Emulate a 1-wire bus using a GPIO.
+config W1_MXC + bool "Enable 1-wire controller on i.MX processors" + default no + depends on ARCH_MX25 || ARCH_MX31 || ARCH_MX5 + help + Support the one wire controller found in some members of the NXP + i.MX SoC family. + There are currently two silicon variants: + V1: i.MX21, i.MX27, i.MX31, i.MX51 + V2: i.MX25, i.MX35, i.MX50, i.MX53 + Newer i.MX SoCs such as the i.MX6 do not have one wire controllers. + + The driver supports both silicon variants. + endif
endmenu diff --git a/drivers/w1/Makefile b/drivers/w1/Makefile index 7fd8697..9825187 100644 --- a/drivers/w1/Makefile +++ b/drivers/w1/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_W1) += w1-uclass.o
obj-$(CONFIG_W1_GPIO) += w1-gpio.o +obj-$(CONFIG_W1_MXC) += mxc_w1.o diff --git a/drivers/w1/mxc_w1.c b/drivers/w1/mxc_w1.c new file mode 100644 index 0000000..9279ba3 --- /dev/null +++ b/drivers/w1/mxc_w1.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for one wire controller in some i.MX Socs + * + * There are currently two silicon variants: + * V1: i.MX21, i.MX27, i.MX31, i.MX51 + * V2: i.MX25, i.MX35, i.MX50, i.MX53 + * Newer i.MX SoCs such as the i.MX6 do not have one wire controllers. + * + * The V1 controller only supports single bit operations. + * The V2 controller is backwards compatible on the register level but adds + * byte size operations and a "search ROM accelerator mode" + * + * This driver does not currently support the search ROM accelerator + * + * Copyright (c) 2018 Flowbird + * Martin Fuzzey martin.fuzzey@flowbird.group + */ + +#include <asm/arch/clock.h> +#include <common.h> +#include <dm.h> +#include <linux/io.h> +#include <w1.h> + +struct mxc_w1_regs { + u16 control; +#define MXC_W1_CONTROL_RPP BIT(7) +#define MXC_W1_CONTROL_PST BIT(6) +#define MXC_W1_CONTROL_WR(x) BIT(5 - (x)) +#define MXC_W1_CONTROL_RDST BIT(3) + + u16 time_divider; + u16 reset; + + /* Registers below on V2 silicon only */ + u16 command; + u16 tx_rx; + u16 interrupt; +#define MXC_W1_INTERRUPT_TBE BIT(2) +#define MXC_W1_INTERRUPT_TSRE BIT(3) +#define MXC_W1_INTERRUPT_RBF BIT(4) +#define MXC_W1_INTERRUPT_RSRF BIT(5) + + u16 interrupt_en; +}; + +struct mxc_w1_pdata { + struct mxc_w1_regs *regs; +}; + +/* + * this is the low level routine to read/write a bit on the One Wire + * interface on the hardware. It does write 0 if parameter bit is set + * to 0, otherwise a write 1/read. + */ +static u8 mxc_w1_touch_bit(struct mxc_w1_pdata *pdata, u8 bit) +{ + u16 *ctrl_addr = &pdata->regs->control; + u16 mask = MXC_W1_CONTROL_WR(bit); + unsigned int timeout_cnt = 400; /* Takes max. 120us according to + * datasheet. + */ + + writew(mask, ctrl_addr); + + while (timeout_cnt--) { + if (!(readw(ctrl_addr) & mask)) + break; + + udelay(1); + } + + return (readw(ctrl_addr) & MXC_W1_CONTROL_RDST) ? 1 : 0; +} + +static u8 mxc_w1_read_byte(struct udevice *dev) +{ + struct mxc_w1_pdata *pdata = dev_get_platdata(dev); + struct mxc_w1_regs *regs = pdata->regs; + u16 status; + + if (dev_get_driver_data(dev) < 2) { + int i; + u8 ret = 0; + + for (i = 0; i < 8; i++) + ret |= (mxc_w1_touch_bit(pdata, 1) << i); + + return ret; + } + + readw(®s->tx_rx); + writew(0xFF, ®s->tx_rx); + + do { + udelay(1); /* Without this bytes are sometimes duplicated... */ + status = readw(®s->interrupt); + } while (!(status & MXC_W1_INTERRUPT_RBF)); + + return (u8)readw(®s->tx_rx); +} + +static void mxc_w1_write_byte(struct udevice *dev, u8 byte) +{ + struct mxc_w1_pdata *pdata = dev_get_platdata(dev); + struct mxc_w1_regs *regs = pdata->regs; + u16 status; + + if (dev_get_driver_data(dev) < 2) { + int i; + + for (i = 0; i < 8; i++) + mxc_w1_touch_bit(pdata, (byte >> i) & 0x1); + + return; + } + + readw(®s->tx_rx); + writew(byte, ®s->tx_rx); + + do { + udelay(1); + status = readw(®s->interrupt); + } while (!(status & MXC_W1_INTERRUPT_TSRE)); +} + +static bool mxc_w1_reset(struct udevice *dev) +{ + struct mxc_w1_pdata *pdata = dev_get_platdata(dev); + u16 reg_val; + + writew(MXC_W1_CONTROL_RPP, &pdata->regs->control); + + do { + reg_val = readw(&pdata->regs->control); + } while (reg_val & MXC_W1_CONTROL_RPP); + + return !(reg_val & MXC_W1_CONTROL_PST); +} + +static u8 mxc_w1_triplet(struct udevice *dev, bool bdir) +{ + struct mxc_w1_pdata *pdata = dev_get_platdata(dev); + u8 id_bit = mxc_w1_touch_bit(pdata, 1); + u8 comp_bit = mxc_w1_touch_bit(pdata, 1); + u8 retval; + + if (id_bit && comp_bit) + return 0x03; /* error */ + + if (!id_bit && !comp_bit) { + /* Both bits are valid, take the direction given */ + retval = bdir ? 0x04 : 0; + } else { + /* Only one bit is valid, take that direction */ + bdir = id_bit; + retval = id_bit ? 0x05 : 0x02; + } + + mxc_w1_touch_bit(pdata, bdir); + + return retval; +} + +static int mxc_w1_ofdata_to_platdata(struct udevice *dev) +{ + struct mxc_w1_pdata *pdata = dev_get_platdata(dev); + fdt_addr_t addr; + + addr = devfdt_get_addr(dev); + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; + + pdata->regs = (struct mxc_w1_regs *)addr; + + return 0; +}; + +static int mxc_w1_probe(struct udevice *dev) +{ + struct mxc_w1_pdata *pdata = dev_get_platdata(dev); + unsigned int clkrate = mxc_get_clock(MXC_IPG_PERCLK); + unsigned int clkdiv; + + if (clkrate < 10000000) { + dev_err(dev, "input clock frequency (%u Hz) too low\n", + clkrate); + return -EINVAL; + } + + clkdiv = clkrate / 1000000; + clkrate /= clkdiv; + if (clkrate < 980000 || clkrate > 1020000) { + dev_err(dev, "Incorrect time base frequency %u Hz\n", clkrate); + return -EINVAL; + } + + writew(clkdiv - 1, &pdata->regs->time_divider); + + return 0; +} + +static const struct w1_ops mxc_w1_ops = { + .read_byte = mxc_w1_read_byte, + .reset = mxc_w1_reset, + .triplet = mxc_w1_triplet, + .write_byte = mxc_w1_write_byte, +}; + +static const struct udevice_id mxc_w1_id[] = { + { .compatible = "fsl,imx21-owire", .data = 1 }, + { .compatible = "fsl,imx27-owire", .data = 1 }, + { .compatible = "fsl,imx31-owire", .data = 1 }, + { .compatible = "fsl,imx51-owire", .data = 1 }, + + { .compatible = "fsl,imx25-owire", .data = 2 }, + { .compatible = "fsl,imx35-owire", .data = 2 }, + { .compatible = "fsl,imx50-owire", .data = 2 }, + { .compatible = "fsl,imx53-owire", .data = 2 }, + { }, +}; + +U_BOOT_DRIVER(mxc_w1_drv) = { + .id = UCLASS_W1, + .name = "mxc_w1_drv", + .of_match = mxc_w1_id, + .ofdata_to_platdata = mxc_w1_ofdata_to_platdata, + .ops = &mxc_w1_ops, + .platdata_auto_alloc_size = sizeof(struct mxc_w1_pdata), + .probe = mxc_w1_probe, +};

Signed-off-by: Martin Fuzzey martin.fuzzey@flowbird.group --- doc/device-tree-bindings/w1-eeprom/ds2502.txt | 33 ++++ drivers/w1-eeprom/Kconfig | 13 ++ drivers/w1-eeprom/Makefile | 1 + drivers/w1-eeprom/ds2502.c | 244 ++++++++++++++++++++++++++ include/w1.h | 1 + 5 files changed, 292 insertions(+) create mode 100644 doc/device-tree-bindings/w1-eeprom/ds2502.txt create mode 100644 drivers/w1-eeprom/ds2502.c
diff --git a/doc/device-tree-bindings/w1-eeprom/ds2502.txt b/doc/device-tree-bindings/w1-eeprom/ds2502.txt new file mode 100644 index 0000000..7f05fc4 --- /dev/null +++ b/doc/device-tree-bindings/w1-eeprom/ds2502.txt @@ -0,0 +1,33 @@ +Maxim DS2502 driver device binding - one wire protocol add only memory from Maxim +======================= + +This memory needs to be connected to a onewire bus, as a child node. +The bus will read the device serial number and match this node with a found +device on the bus +Also check doc/device-tree-bindings/w1 for onewire bus drivers + +Driver: +- drivers/w1-eeprom/ds2502.c + +Ds2502 device-tree node properties: +Required: +* compatible = "maxim,ds2502" + +Optional: +* none + +Example: + eeprom1: eeprom@0 { + compatible = "maxim,ds2502"; + }; + +Example with parent bus: + onewire { + compatible = "fsl,imx53-owire"; + reg = <0x63fa4000 0x4000>; + + eeprom1: eeprom@0 { + compatible = "maxim,ds2502"; + }; + }; + diff --git a/drivers/w1-eeprom/Kconfig b/drivers/w1-eeprom/Kconfig index 4b7f3c4..34aca4b 100644 --- a/drivers/w1-eeprom/Kconfig +++ b/drivers/w1-eeprom/Kconfig @@ -18,6 +18,19 @@ config W1_EEPROM_DS24XXX help Maxim DS24 EEPROMs 1-Wire EEPROM support
+config W1_EEPROM_DS2502 + bool "Enable Maxim DS2502 Add-Only Memory support" + depends on W1 + help + Maxim DS2502 1-Wire add-only memory support. + This device has 128 bytes of data memory, organized as 4 pages of + 32 bytes and 8 out of band status bytes that may be used to redirect + pages, allowing data to be modified up to 4 times (by external + programming). + + The device may be seen as a 32 byte memory, using the page redirection + or as a 128 byte memory, ignoring the page redirection. + config W1_EEPROM_SANDBOX bool "Enable sandbox onewire EEPROM driver" depends on W1 diff --git a/drivers/w1-eeprom/Makefile b/drivers/w1-eeprom/Makefile index 03cc4c8..83f4008 100644 --- a/drivers/w1-eeprom/Makefile +++ b/drivers/w1-eeprom/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_W1_EEPROM) += w1-eeprom-uclass.o
obj-$(CONFIG_W1_EEPROM_DS24XXX) += ds24xxx.o +obj-$(CONFIG_W1_EEPROM_DS2502) += ds2502.o
obj-$(CONFIG_W1_EEPROM_SANDBOX) += eep_sandbox.o diff --git a/drivers/w1-eeprom/ds2502.c b/drivers/w1-eeprom/ds2502.c new file mode 100644 index 0000000..76ca460 --- /dev/null +++ b/drivers/w1-eeprom/ds2502.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for DS-2502 One wire "Add only Memory". + * + * The chip has 4 pages of 32 bytes. + * In addition it has 8 out of band status bytes that are used, by software, + * as page redirection bytes by an algorithm described in the data sheet. + * This is useful since data cannot be erased once written but it can be + * "patched" up to four times by switching pages. + * + * So, when a read request is entirely in the first page automatically + * apply the page redirection bytes (which allows the device to be seen as + * a 32 byte PROM, writable 4 times). + * + * If the read request is outside of or larger than the first page then read + * the raw data (which allows the device to be seen as a 128 byte PROM, + * writable once). + * + * Copyright (c) 2018 Flowbird + * Martin Fuzzey martin.fuzzey@flowbird.group + */ + +#include <common.h> +#include <dm.h> +#include <linux/err.h> +#include <w1-eeprom.h> +#include <w1.h> + +#define DS2502_PAGE_SIZE 32 +#define DS2502_PAGE_COUNT 4 +#define DS2502_STATUS_SIZE 8 + +#define DS2502_CMD_READ_STATUS 0xAA +#define DS2502_CMD_READ_GEN_CRC 0xC3 + +/* u-boot crc8() is CCITT CRC8, we need x^8 + x^5 + x^4 + 1 LSB first */ +static unsigned int ds2502_crc8(const u8 *buf, int len) +{ + static const u8 poly = 0x8C; /* (1 + x^4 + x^5) + x^8 */ + u8 crc = 0; + int i; + + for (i = 0; i < len; i++) { + u8 data = buf[i]; + int j; + + for (j = 0; j < 8; j++) { + u8 mix = (crc ^ data) & 1; + + crc >>= 1; + if (mix) + crc ^= poly; + data >>= 1; + } + } + return crc; +} + +static int ds2502_read(struct udevice *dev, u8 cmd, + int bytes_in_page, int pos, + u8 *buf, int bytes_for_user) +{ + int retry; + int ret = 0; + + for (retry = 0; retry < 3; retry++) { + u8 pagebuf[DS2502_PAGE_SIZE + 1]; /* 1 byte for CRC8 */ + u8 crc; + int i; + + ret = w1_reset_select(dev); + if (ret) + return ret; + + /* send read to end of page and generate CRC command */ + pagebuf[0] = cmd; + pagebuf[1] = pos & 0xff; + pagebuf[2] = pos >> 8; + crc = ds2502_crc8(pagebuf, 3); + for (i = 0; i < 3; i++) + w1_write_byte(dev, pagebuf[i]); + + /* Check command CRC */ + ret = w1_read_byte(dev); + if (ret < 0) { + dev_dbg(dev, "Error %d reading command CRC\n", ret); + continue; + } + + if (ret != crc) { + dev_dbg(dev, + "bad CRC8 for cmd %02x got=%02X exp=%02X\n", + cmd, ret, crc); + ret = -EIO; + continue; + } + + /* read data and check CRC */ + ret = w1_read_buf(dev, pagebuf, bytes_in_page + 1); + if (ret < 0) { + dev_dbg(dev, "Error %d reading data\n", ret); + continue; + } + + crc = ds2502_crc8(pagebuf, bytes_in_page); + if (crc == pagebuf[bytes_in_page]) { + memcpy(buf, pagebuf, bytes_for_user); + ret = 0; + break; + } + dev_dbg(dev, "Bad CRC8 got=%02X exp=%02X pos=%04X\n", + pagebuf[bytes_in_page], crc, pos); + ret = -EIO; + } + + return ret; +} + +static inline int ds2502_read_status_bytes(struct udevice *dev, u8 *buf) +{ + return ds2502_read(dev, DS2502_CMD_READ_STATUS, + DS2502_STATUS_SIZE, 0, + buf, DS2502_STATUS_SIZE); +} + +/* + * Status bytes (from index 1) contain 1's complement page indirection + * So for N writes: + * N=1: ff ff ff ff ff ff ff 00 + * N=2: ff fe ff ff ff ff ff 00 + * N=3: ff fe fd ff ff ff ff 00 + * N=4: ff fe fd fc ff ff ff 00 + */ +static int ds2502_indirect_page(struct udevice *dev, u8 *status, int page) +{ + int page_seen = 0; + + do { + u8 sb = status[page + 1]; + + if (sb == 0xff) + break; + + page = ~sb & 0xff; + + if (page >= DS2502_PAGE_COUNT) { + dev_err(dev, + "Illegal page redirection status byte %02x\n", + sb); + return -EINVAL; + } + + if (page_seen & (1 << page)) { + dev_err(dev, "Infinite loop in page redirection\n"); + return -EINVAL; + } + + page_seen |= (1 << page); + } while (1); + + return page; +} + +static int ds2502_read_buf(struct udevice *dev, unsigned int offset, + u8 *buf, unsigned int count) +{ + unsigned int min_page = offset / DS2502_PAGE_SIZE; + unsigned int max_page = (offset + count - 1) / DS2502_PAGE_SIZE; + int xfered = 0; + u8 status_bytes[DS2502_STATUS_SIZE]; + int i; + int ret; + + if (min_page >= DS2502_PAGE_COUNT || max_page >= DS2502_PAGE_COUNT) + return -EINVAL; + + if (min_page == 0 && max_page == 0) { + ret = ds2502_read_status_bytes(dev, status_bytes); + if (ret) + return ret; + } else { + /* Dummy one to one page redirection */ + memset(status_bytes, 0xff, sizeof(status_bytes)); + } + + for (i = min_page; i <= max_page; i++) { + int page; + int pos; + int bytes_in_page; + int bytes_for_user; + + page = ds2502_indirect_page(dev, status_bytes, i); + if (page < 0) + return page; + dev_dbg(dev, "page logical %d => physical %d\n", i, page); + + pos = page * DS2502_PAGE_SIZE; + if (i == min_page) + pos += offset % DS2502_PAGE_SIZE; + + bytes_in_page = DS2502_PAGE_SIZE - (pos % DS2502_PAGE_SIZE); + + if (i == max_page) + bytes_for_user = count - xfered; + else + bytes_for_user = bytes_in_page; + + ret = ds2502_read(dev, DS2502_CMD_READ_GEN_CRC, + bytes_in_page, pos, + &buf[xfered], bytes_for_user); + if (ret < 0) + return ret; + + xfered += bytes_for_user; + } + + return 0; +} + +static int ds2502_probe(struct udevice *dev) +{ + struct w1_device *w1; + + w1 = dev_get_parent_platdata(dev); + w1->id = 0; + return 0; +} + +static const struct w1_eeprom_ops ds2502_ops = { + .read_buf = ds2502_read_buf, +}; + +static const struct udevice_id ds2502_id[] = { + { .compatible = "maxim,ds2502", .data = W1_FAMILY_DS2502 }, + { }, +}; + +U_BOOT_DRIVER(ds2502) = { + .name = "ds2502", + .id = UCLASS_W1_EEPROM, + .of_match = ds2502_id, + .ops = &ds2502_ops, + .probe = ds2502_probe, +}; diff --git a/include/w1.h b/include/w1.h index 399177a..b958b1c 100644 --- a/include/w1.h +++ b/include/w1.h @@ -12,6 +12,7 @@
#define W1_FAMILY_DS24B33 0x23 #define W1_FAMILY_DS2431 0x2d +#define W1_FAMILY_DS2502 0x09 #define W1_FAMILY_EEP_SANDBOX 0xfe
struct w1_device {

On Wed, Oct 24, 2018 at 10:21:19AM +0200, Martin Fuzzey wrote:
Signed-off-by: Martin Fuzzey martin.fuzzey@flowbird.group
Applied to u-boot/master, thanks!

On 24/10/18 10:21, Martin Fuzzey wrote:
Two variants of controllers are supported: V1 (bitwise only) found in i.MX21, i.MX27, i.MX31, i.MX51 V2 (byte operations) found in i.MX25, i.MX35, i.MX50, i.MX53
Only tested on i.MX53 hardware but in both modes (by modifying the device tree).
Signed-off-by: Martin Fuzzey martin.fuzzey@flowbird.group
Applied to u-boot-imx, master, thanks !
Best regards, Stefano Babic
participants (3)
-
Martin Fuzzey
-
Stefano Babic
-
Tom Rini