| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * V3MSK board CPLD access support |
| * |
| * Copyright (C) 2019 Renesas Electronics Corporation |
| * Copyright (C) 2019 Cogent Embedded, Inc. |
| * |
| */ |
| |
| #include <asm/gpio.h> |
| #include <asm/io.h> |
| #include <dm.h> |
| #include <errno.h> |
| #include <linux/err.h> |
| #include <sysreset.h> |
| #include <linux/delay.h> |
| #include <command.h> |
| |
| #define CPLD_ADDR_PRODUCT_L 0x000 /* R */ |
| #define CPLD_ADDR_PRODUCT_H 0x001 /* R */ |
| #define CPLD_ADDR_CPLD_VERSION_D 0x002 /* R */ |
| #define CPLD_ADDR_CPLD_VERSION_Y 0x003 /* R */ |
| #define CPLD_ADDR_MODE_SET_L 0x004 /* R/W */ |
| #define CPLD_ADDR_MODE_SET_H 0x005 /* R/W */ |
| #define CPLD_ADDR_MODE_APPLIED_L 0x006 /* R */ |
| #define CPLD_ADDR_MODE_APPLIED_H 0x007 /* R */ |
| #define CPLD_ADDR_DIPSW 0x008 /* R */ |
| #define CPLD_ADDR_RESET 0x00A /* R/W */ |
| #define CPLD_ADDR_POWER_CFG 0x00B /* R/W */ |
| #define CPLD_ADDR_PERI_CFG1 0x00C /* R/W */ |
| #define CPLD_ADDR_PERI_CFG2 0x00D /* R/W */ |
| #define CPLD_ADDR_LEDS 0x00E /* R/W */ |
| #define CPLD_ADDR_PCB_VERSION 0x300 /* R */ |
| #define CPLD_ADDR_SOC_VERSION 0x301 /* R */ |
| #define CPLD_ADDR_PCB_SN_L 0x302 /* R */ |
| #define CPLD_ADDR_PCB_SN_H 0x303 /* R */ |
| |
| #define MDIO_DELAY 10 /* microseconds */ |
| |
| #define CPLD_MAX_GPIOS 2 |
| |
| struct renesas_v3msk_sysreset_priv { |
| struct gpio_desc miso; |
| struct gpio_desc mosi; |
| struct gpio_desc mdc; |
| struct gpio_desc enablez; |
| /* |
| * V3MSK Videobox Mini board has CANFD PHY connected |
| * we must shutdown this chip to use bb pins |
| */ |
| struct gpio_desc gpios[CPLD_MAX_GPIOS]; |
| }; |
| |
| static void mdio_bb_active_mdio(struct renesas_v3msk_sysreset_priv *priv) |
| { |
| dm_gpio_set_dir_flags(&priv->mosi, GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE); |
| } |
| |
| static void mdio_bb_tristate_mdio(struct renesas_v3msk_sysreset_priv *priv) |
| { |
| dm_gpio_set_dir_flags(&priv->mosi, GPIOD_IS_IN); |
| } |
| |
| static void mdio_bb_set_mdio(struct renesas_v3msk_sysreset_priv *priv, int val) |
| { |
| dm_gpio_set_value(&priv->mosi, val); |
| } |
| |
| static int mdio_bb_get_mdio(struct renesas_v3msk_sysreset_priv *priv) |
| { |
| return dm_gpio_get_value(&priv->miso); |
| } |
| |
| static void mdio_bb_set_mdc(struct renesas_v3msk_sysreset_priv *priv, int val) |
| { |
| dm_gpio_set_value(&priv->mdc, val); |
| } |
| |
| static void mdio_bb_delay(void) |
| { |
| udelay(MDIO_DELAY); |
| } |
| |
| /* Send the preamble, address, and register (common to read and write) */ |
| static void mdio_bb_pre(struct renesas_v3msk_sysreset_priv *priv, |
| u8 op, u8 addr, u8 reg) |
| { |
| int i; |
| |
| /* 32-bit preamble */ |
| mdio_bb_active_mdio(priv); |
| mdio_bb_set_mdio(priv, 1); |
| for (i = 0; i < 32; i++) { |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| } |
| /* send the ST (2-bits of '01') */ |
| mdio_bb_set_mdio(priv, 0); |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| mdio_bb_set_mdio(priv, 1); |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| /* send the OP (2-bits of Opcode: '10'-read, '01'-write) */ |
| mdio_bb_set_mdio(priv, op >> 1); |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| mdio_bb_set_mdio(priv, op & 1); |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| /* send the PA5 (5-bits of PHY address) */ |
| for (i = 0; i < 5; i++) { |
| mdio_bb_set_mdio(priv, addr & 0x10); /* MSB first */ |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| addr <<= 1; |
| } |
| /* send the RA5 (5-bits of register address) */ |
| for (i = 0; i < 5; i++) { |
| mdio_bb_set_mdio(priv, reg & 0x10); /* MSB first */ |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| reg <<= 1; |
| } |
| } |
| |
| static int mdio_bb_read(struct renesas_v3msk_sysreset_priv *priv, |
| u8 addr, u8 reg) |
| { |
| int i; |
| u16 data = 0; |
| |
| mdio_bb_pre(priv, 2, addr, reg); |
| /* tri-state MDIO */ |
| mdio_bb_tristate_mdio(priv); |
| /* read TA (2-bits of turn-around, last bit must be '0') */ |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| /* check the turnaround bit: the PHY should drive line to zero */ |
| if (mdio_bb_get_mdio(priv) != 0) { |
| printf("PHY didn't drive TA low\n"); |
| for (i = 0; i < 32; i++) { |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| } |
| /* There is no PHY, set value to 0xFFFF */ |
| return 0xFFFF; |
| } |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| /* read 16-bits of data */ |
| for (i = 0; i < 16; i++) { |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| data <<= 1; |
| data |= mdio_bb_get_mdio(priv); |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| } |
| |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| |
| debug("cpld_read(0x%x) @ 0x%x = 0x%04x\n", reg, addr, data); |
| |
| return data; |
| } |
| |
| static void mdio_bb_write(struct renesas_v3msk_sysreset_priv *priv, |
| u8 addr, u8 reg, u16 val) |
| { |
| int i; |
| |
| mdio_bb_pre(priv, 1, addr, reg); |
| /* send the TA (2-bits of turn-around '10') */ |
| mdio_bb_set_mdio(priv, 1); |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| mdio_bb_set_mdio(priv, 0); |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| /* write 16-bits of data */ |
| for (i = 0; i < 16; i++) { |
| mdio_bb_set_mdio(priv, val & 0x8000); /* MSB first */ |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| val <<= 1; |
| } |
| /* tri-state MDIO */ |
| mdio_bb_tristate_mdio(priv); |
| mdio_bb_set_mdc(priv, 0); |
| mdio_bb_delay(); |
| mdio_bb_set_mdc(priv, 1); |
| mdio_bb_delay(); |
| } |
| |
| static u16 cpld_read(struct udevice *dev, u16 addr) |
| { |
| struct renesas_v3msk_sysreset_priv *priv = dev_get_priv(dev); |
| |
| /* random flash reads require 2 reads: first read is unreliable */ |
| if (addr >= CPLD_ADDR_PCB_VERSION) |
| mdio_bb_read(priv, addr >> 5, addr & 0x1f); |
| |
| return mdio_bb_read(priv, addr >> 5, addr & 0x1f); |
| } |
| |
| static void cpld_write(struct udevice *dev, u16 addr, u16 data) |
| { |
| struct renesas_v3msk_sysreset_priv *priv = dev_get_priv(dev); |
| |
| mdio_bb_write(priv, addr >> 5, addr & 0x1f, data); |
| } |
| |
| static int do_cpld(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) |
| { |
| struct udevice *dev; |
| u16 addr, val; |
| int ret; |
| |
| ret = uclass_get_device_by_driver(UCLASS_SYSRESET, |
| DM_DRIVER_GET(sysreset_renesas_v3msk), |
| &dev); |
| if (ret) |
| return ret; |
| |
| if (argc == 2 && strcmp(argv[1], "info") == 0) { |
| printf("Product: 0x%08x\n", |
| (cpld_read(dev, CPLD_ADDR_PRODUCT_H) << 16) | |
| cpld_read(dev, CPLD_ADDR_PRODUCT_L)); |
| printf("CPLD version: 0x%08x\n", |
| (cpld_read(dev, CPLD_ADDR_CPLD_VERSION_Y) << 16) | |
| cpld_read(dev, CPLD_ADDR_CPLD_VERSION_D)); |
| printf("Mode setting (MD0..26): 0x%08x\n", |
| (cpld_read(dev, CPLD_ADDR_MODE_APPLIED_H) << 16) | |
| cpld_read(dev, CPLD_ADDR_MODE_APPLIED_L)); |
| printf("DIPSW (SW4, SW5): 0x%02x, 0x%x\n", |
| (cpld_read(dev, CPLD_ADDR_DIPSW) & 0xff) ^ 0xff, |
| (cpld_read(dev, CPLD_ADDR_DIPSW) >> 8) ^ 0xf); |
| printf("Power config: 0x%08x\n", |
| cpld_read(dev, CPLD_ADDR_POWER_CFG)); |
| printf("Periferals config: 0x%08x\n", |
| (cpld_read(dev, CPLD_ADDR_PERI_CFG2) << 16) | |
| cpld_read(dev, CPLD_ADDR_PERI_CFG1)); |
| printf("PCB version: %d.%d\n", |
| cpld_read(dev, CPLD_ADDR_PCB_VERSION) >> 8, |
| cpld_read(dev, CPLD_ADDR_PCB_VERSION) & 0xff); |
| printf("SOC version: %d.%d\n", |
| cpld_read(dev, CPLD_ADDR_SOC_VERSION) >> 8, |
| cpld_read(dev, CPLD_ADDR_SOC_VERSION) & 0xff); |
| printf("PCB S/N: %d\n", |
| (cpld_read(dev, CPLD_ADDR_PCB_SN_H) << 16) | |
| cpld_read(dev, CPLD_ADDR_PCB_SN_L)); |
| return 0; |
| } |
| |
| if (argc < 3) |
| return CMD_RET_USAGE; |
| |
| addr = simple_strtoul(argv[2], NULL, 16); |
| if (!(addr >= CPLD_ADDR_PRODUCT_L && addr <= CPLD_ADDR_LEDS)) { |
| printf("cpld invalid addr\n"); |
| return CMD_RET_USAGE; |
| } |
| |
| if (argc == 3 && strcmp(argv[1], "read") == 0) { |
| printf("0x%x\n", cpld_read(dev, addr)); |
| } else if (argc == 4 && strcmp(argv[1], "write") == 0) { |
| val = simple_strtoul(argv[3], NULL, 16); |
| cpld_write(dev, addr, val); |
| } |
| |
| return 0; |
| } |
| |
| U_BOOT_CMD(cpld, 4, 1, do_cpld, |
| "CPLD access", |
| "info\n" |
| "cpld read addr\n" |
| "cpld write addr val\n" |
| ); |
| |
| static int renesas_v3msk_sysreset_request(struct udevice *dev, enum sysreset_t type) |
| { |
| cpld_write(dev, CPLD_ADDR_RESET, 1); |
| |
| return -EINPROGRESS; |
| } |
| |
| static int renesas_v3msk_sysreset_probe(struct udevice *dev) |
| { |
| struct renesas_v3msk_sysreset_priv *priv = dev_get_priv(dev); |
| |
| if (gpio_request_by_name(dev, "gpio-miso", 0, &priv->miso, |
| GPIOD_IS_IN)) |
| return -EINVAL; |
| |
| if (gpio_request_by_name(dev, "gpio-mosi", 0, &priv->mosi, |
| GPIOD_IS_OUT)) |
| return -EINVAL; |
| |
| if (gpio_request_by_name(dev, "gpio-mdc", 0, &priv->mdc, |
| GPIOD_IS_OUT)) |
| return -EINVAL; |
| |
| if (gpio_request_by_name(dev, "gpio-enablez", 0, &priv->enablez, |
| GPIOD_IS_OUT)) |
| return -EINVAL; |
| |
| /* V3MSK Videobox Mini board has CANFD PHY connected |
| * we must shutdown this chip to use bb pins |
| */ |
| gpio_request_list_by_name(dev, "gpios", priv->gpios, CPLD_MAX_GPIOS, |
| GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE); |
| |
| return 0; |
| } |
| |
| static struct sysreset_ops renesas_v3msk_sysreset = { |
| .request = renesas_v3msk_sysreset_request, |
| }; |
| |
| static const struct udevice_id renesas_v3msk_sysreset_ids[] = { |
| { .compatible = "renesas,v3msk-cpld" }, |
| { /* sentinel */ } |
| }; |
| |
| U_BOOT_DRIVER(sysreset_renesas_v3msk) = { |
| .name = "renesas_v3msk_sysreset", |
| .id = UCLASS_SYSRESET, |
| .ops = &renesas_v3msk_sysreset, |
| .probe = renesas_v3msk_sysreset_probe, |
| .of_match = renesas_v3msk_sysreset_ids, |
| .priv_auto = sizeof(struct renesas_v3msk_sysreset_priv), |
| }; |