17dc54d3bSClément Léger // SPDX-License-Identifier: GPL-2.0 27dc54d3bSClément Léger /* 37dc54d3bSClément Léger * Copyright (C) 2022 Schneider Electric 47dc54d3bSClément Léger * 57dc54d3bSClément Léger * Clément Léger <clement.leger@bootlin.com> 67dc54d3bSClément Léger */ 77dc54d3bSClément Léger 87dc54d3bSClément Léger #include <linux/clk.h> 97dc54d3bSClément Léger #include <linux/device.h> 107dc54d3bSClément Léger #include <linux/mdio.h> 117dc54d3bSClément Léger #include <linux/of.h> 127dc54d3bSClément Léger #include <linux/of_platform.h> 137dc54d3bSClément Léger #include <linux/pcs-rzn1-miic.h> 147dc54d3bSClément Léger #include <linux/phylink.h> 157dc54d3bSClément Léger #include <linux/pm_runtime.h> 167dc54d3bSClément Léger #include <dt-bindings/net/pcs-rzn1-miic.h> 177dc54d3bSClément Léger 187dc54d3bSClément Léger #define MIIC_PRCMD 0x0 197dc54d3bSClément Léger #define MIIC_ESID_CODE 0x4 207dc54d3bSClément Léger 217dc54d3bSClément Léger #define MIIC_MODCTRL 0x20 227dc54d3bSClément Léger #define MIIC_MODCTRL_SW_MODE GENMASK(4, 0) 237dc54d3bSClément Léger 247dc54d3bSClément Léger #define MIIC_CONVCTRL(port) (0x100 + (port) * 4) 257dc54d3bSClément Léger 267dc54d3bSClément Léger #define MIIC_CONVCTRL_CONV_SPEED GENMASK(1, 0) 277dc54d3bSClément Léger #define CONV_MODE_10MBPS 0 287dc54d3bSClément Léger #define CONV_MODE_100MBPS 1 297dc54d3bSClément Léger #define CONV_MODE_1000MBPS 2 307dc54d3bSClément Léger 317dc54d3bSClément Léger #define MIIC_CONVCTRL_CONV_MODE GENMASK(3, 2) 327dc54d3bSClément Léger #define CONV_MODE_MII 0 337dc54d3bSClément Léger #define CONV_MODE_RMII 1 347dc54d3bSClément Léger #define CONV_MODE_RGMII 2 357dc54d3bSClément Léger 367dc54d3bSClément Léger #define MIIC_CONVCTRL_FULLD BIT(8) 377dc54d3bSClément Léger #define MIIC_CONVCTRL_RGMII_LINK BIT(12) 387dc54d3bSClément Léger #define MIIC_CONVCTRL_RGMII_DUPLEX BIT(13) 397dc54d3bSClément Léger #define MIIC_CONVCTRL_RGMII_SPEED GENMASK(15, 14) 407dc54d3bSClément Léger 417dc54d3bSClément Léger #define MIIC_CONVRST 0x114 427dc54d3bSClément Léger #define MIIC_CONVRST_PHYIF_RST(port) BIT(port) 437dc54d3bSClément Léger #define MIIC_CONVRST_PHYIF_RST_MASK GENMASK(4, 0) 447dc54d3bSClément Léger 457dc54d3bSClément Léger #define MIIC_SWCTRL 0x304 467dc54d3bSClément Léger #define MIIC_SWDUPC 0x308 477dc54d3bSClément Léger 487dc54d3bSClément Léger #define MIIC_MAX_NR_PORTS 5 497dc54d3bSClément Léger 507dc54d3bSClément Léger #define MIIC_MODCTRL_CONF_CONV_NUM 6 517dc54d3bSClément Léger #define MIIC_MODCTRL_CONF_NONE -1 527dc54d3bSClément Léger 537dc54d3bSClément Léger /** 547dc54d3bSClément Léger * struct modctrl_match - Matching table entry for convctrl configuration 557dc54d3bSClément Léger * See section 8.2.1 of manual. 567dc54d3bSClément Léger * @mode_cfg: Configuration value for convctrl 577dc54d3bSClément Léger * @conv: Configuration of ethernet port muxes. First index is SWITCH_PORTIN, 587dc54d3bSClément Léger * then index 1 - 5 are CONV1 - CONV5. 597dc54d3bSClément Léger */ 607dc54d3bSClément Léger struct modctrl_match { 617dc54d3bSClément Léger u32 mode_cfg; 627dc54d3bSClément Léger u8 conv[MIIC_MODCTRL_CONF_CONV_NUM]; 637dc54d3bSClément Léger }; 647dc54d3bSClément Léger 657dc54d3bSClément Léger static struct modctrl_match modctrl_match_table[] = { 667dc54d3bSClément Léger {0x0, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 677dc54d3bSClément Léger MIIC_SWITCH_PORTC, MIIC_SERCOS_PORTB, MIIC_SERCOS_PORTA}}, 687dc54d3bSClément Léger {0x1, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 697dc54d3bSClément Léger MIIC_SWITCH_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, 707dc54d3bSClément Léger {0x2, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 717dc54d3bSClément Léger MIIC_ETHERCAT_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, 727dc54d3bSClément Léger {0x3, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 737dc54d3bSClément Léger MIIC_SWITCH_PORTC, MIIC_SWITCH_PORTB, MIIC_SWITCH_PORTA}}, 747dc54d3bSClément Léger 757dc54d3bSClément Léger {0x8, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 767dc54d3bSClément Léger MIIC_SWITCH_PORTC, MIIC_SERCOS_PORTB, MIIC_SERCOS_PORTA}}, 777dc54d3bSClément Léger {0x9, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 787dc54d3bSClément Léger MIIC_SWITCH_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, 797dc54d3bSClément Léger {0xA, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 807dc54d3bSClément Léger MIIC_ETHERCAT_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, 817dc54d3bSClément Léger {0xB, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 827dc54d3bSClément Léger MIIC_SWITCH_PORTC, MIIC_SWITCH_PORTB, MIIC_SWITCH_PORTA}}, 837dc54d3bSClément Léger 847dc54d3bSClément Léger {0x10, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 857dc54d3bSClément Léger MIIC_SWITCH_PORTC, MIIC_SERCOS_PORTB, MIIC_SERCOS_PORTA}}, 867dc54d3bSClément Léger {0x11, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 877dc54d3bSClément Léger MIIC_SWITCH_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, 887dc54d3bSClément Léger {0x12, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 897dc54d3bSClément Léger MIIC_ETHERCAT_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, 907dc54d3bSClément Léger {0x13, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 917dc54d3bSClément Léger MIIC_SWITCH_PORTC, MIIC_SWITCH_PORTB, MIIC_SWITCH_PORTA}} 927dc54d3bSClément Léger }; 937dc54d3bSClément Léger 947dc54d3bSClément Léger static const char * const conf_to_string[] = { 957dc54d3bSClément Léger [MIIC_GMAC1_PORT] = "GMAC1_PORT", 967dc54d3bSClément Léger [MIIC_GMAC2_PORT] = "GMAC2_PORT", 977dc54d3bSClément Léger [MIIC_RTOS_PORT] = "RTOS_PORT", 987dc54d3bSClément Léger [MIIC_SERCOS_PORTA] = "SERCOS_PORTA", 997dc54d3bSClément Léger [MIIC_SERCOS_PORTB] = "SERCOS_PORTB", 1007dc54d3bSClément Léger [MIIC_ETHERCAT_PORTA] = "ETHERCAT_PORTA", 1017dc54d3bSClément Léger [MIIC_ETHERCAT_PORTB] = "ETHERCAT_PORTB", 1027dc54d3bSClément Léger [MIIC_ETHERCAT_PORTC] = "ETHERCAT_PORTC", 1037dc54d3bSClément Léger [MIIC_SWITCH_PORTA] = "SWITCH_PORTA", 1047dc54d3bSClément Léger [MIIC_SWITCH_PORTB] = "SWITCH_PORTB", 1057dc54d3bSClément Léger [MIIC_SWITCH_PORTC] = "SWITCH_PORTC", 1067dc54d3bSClément Léger [MIIC_SWITCH_PORTD] = "SWITCH_PORTD", 1077dc54d3bSClément Léger [MIIC_HSR_PORTA] = "HSR_PORTA", 1087dc54d3bSClément Léger [MIIC_HSR_PORTB] = "HSR_PORTB", 1097dc54d3bSClément Léger }; 1107dc54d3bSClément Léger 1117dc54d3bSClément Léger static const char *index_to_string[MIIC_MODCTRL_CONF_CONV_NUM] = { 1127dc54d3bSClément Léger "SWITCH_PORTIN", 1137dc54d3bSClément Léger "CONV1", 1147dc54d3bSClément Léger "CONV2", 1157dc54d3bSClément Léger "CONV3", 1167dc54d3bSClément Léger "CONV4", 1177dc54d3bSClément Léger "CONV5", 1187dc54d3bSClément Léger }; 1197dc54d3bSClément Léger 1207dc54d3bSClément Léger /** 1217dc54d3bSClément Léger * struct miic - MII converter structure 1227dc54d3bSClément Léger * @base: base address of the MII converter 1237dc54d3bSClément Léger * @dev: Device associated to the MII converter 1247dc54d3bSClément Léger * @clks: Clocks used for this device 1257dc54d3bSClément Léger * @nclk: Number of clocks 1267dc54d3bSClément Léger * @lock: Lock used for read-modify-write access 1277dc54d3bSClément Léger */ 1287dc54d3bSClément Léger struct miic { 1297dc54d3bSClément Léger void __iomem *base; 1307dc54d3bSClément Léger struct device *dev; 1317dc54d3bSClément Léger struct clk_bulk_data *clks; 1327dc54d3bSClément Léger int nclk; 1337dc54d3bSClément Léger spinlock_t lock; 1347dc54d3bSClément Léger }; 1357dc54d3bSClément Léger 1367dc54d3bSClément Léger /** 1377dc54d3bSClément Léger * struct miic_port - Per port MII converter struct 1387dc54d3bSClément Léger * @miic: backiling to MII converter structure 1397dc54d3bSClément Léger * @pcs: PCS structure associated to the port 1407dc54d3bSClément Léger * @port: port number 1417dc54d3bSClément Léger */ 1427dc54d3bSClément Léger struct miic_port { 1437dc54d3bSClément Léger struct miic *miic; 1447dc54d3bSClément Léger struct phylink_pcs pcs; 1457dc54d3bSClément Léger int port; 1467dc54d3bSClément Léger }; 1477dc54d3bSClément Léger 1487dc54d3bSClément Léger static struct miic_port *phylink_pcs_to_miic_port(struct phylink_pcs *pcs) 1497dc54d3bSClément Léger { 1507dc54d3bSClément Léger return container_of(pcs, struct miic_port, pcs); 1517dc54d3bSClément Léger } 1527dc54d3bSClément Léger 1537dc54d3bSClément Léger static void miic_reg_writel(struct miic *miic, int offset, u32 value) 1547dc54d3bSClément Léger { 1557dc54d3bSClément Léger writel(value, miic->base + offset); 1567dc54d3bSClément Léger } 1577dc54d3bSClément Léger 1587dc54d3bSClément Léger static u32 miic_reg_readl(struct miic *miic, int offset) 1597dc54d3bSClément Léger { 1607dc54d3bSClément Léger return readl(miic->base + offset); 1617dc54d3bSClément Léger } 1627dc54d3bSClément Léger 1637dc54d3bSClément Léger static void miic_reg_rmw(struct miic *miic, int offset, u32 mask, u32 val) 1647dc54d3bSClément Léger { 1657dc54d3bSClément Léger u32 reg; 1667dc54d3bSClément Léger 1677dc54d3bSClément Léger spin_lock(&miic->lock); 1687dc54d3bSClément Léger 1697dc54d3bSClément Léger reg = miic_reg_readl(miic, offset); 1707dc54d3bSClément Léger reg &= ~mask; 1717dc54d3bSClément Léger reg |= val; 1727dc54d3bSClément Léger miic_reg_writel(miic, offset, reg); 1737dc54d3bSClément Léger 1747dc54d3bSClément Léger spin_unlock(&miic->lock); 1757dc54d3bSClément Léger } 1767dc54d3bSClément Léger 1777dc54d3bSClément Léger static void miic_converter_enable(struct miic *miic, int port, int enable) 1787dc54d3bSClément Léger { 1797dc54d3bSClément Léger u32 val = 0; 1807dc54d3bSClément Léger 1817dc54d3bSClément Léger if (enable) 1827dc54d3bSClément Léger val = MIIC_CONVRST_PHYIF_RST(port); 1837dc54d3bSClément Léger 1847dc54d3bSClément Léger miic_reg_rmw(miic, MIIC_CONVRST, MIIC_CONVRST_PHYIF_RST(port), val); 1857dc54d3bSClément Léger } 1867dc54d3bSClément Léger 1877dc54d3bSClément Léger static int miic_config(struct phylink_pcs *pcs, unsigned int mode, 1887dc54d3bSClément Léger phy_interface_t interface, 1897dc54d3bSClément Léger const unsigned long *advertising, bool permit) 1907dc54d3bSClément Léger { 1917dc54d3bSClément Léger struct miic_port *miic_port = phylink_pcs_to_miic_port(pcs); 1927dc54d3bSClément Léger struct miic *miic = miic_port->miic; 1937dc54d3bSClément Léger int port = miic_port->port; 1947dc54d3bSClément Léger u32 speed, conv_mode, val; 1957dc54d3bSClément Léger 1967dc54d3bSClément Léger switch (interface) { 1977dc54d3bSClément Léger case PHY_INTERFACE_MODE_RMII: 1987dc54d3bSClément Léger conv_mode = CONV_MODE_RMII; 1997dc54d3bSClément Léger speed = CONV_MODE_100MBPS; 2007dc54d3bSClément Léger break; 2017dc54d3bSClément Léger case PHY_INTERFACE_MODE_RGMII: 2027dc54d3bSClément Léger case PHY_INTERFACE_MODE_RGMII_ID: 2037dc54d3bSClément Léger case PHY_INTERFACE_MODE_RGMII_TXID: 2047dc54d3bSClément Léger case PHY_INTERFACE_MODE_RGMII_RXID: 2057dc54d3bSClément Léger conv_mode = CONV_MODE_RGMII; 2067dc54d3bSClément Léger speed = CONV_MODE_1000MBPS; 2077dc54d3bSClément Léger break; 2087dc54d3bSClément Léger case PHY_INTERFACE_MODE_MII: 2097dc54d3bSClément Léger conv_mode = CONV_MODE_MII; 2107dc54d3bSClément Léger /* When in MII mode, speed should be set to 0 (which is actually 2117dc54d3bSClément Léger * CONV_MODE_10MBPS) 2127dc54d3bSClément Léger */ 2137dc54d3bSClément Léger speed = CONV_MODE_10MBPS; 2147dc54d3bSClément Léger break; 2157dc54d3bSClément Léger default: 2167dc54d3bSClément Léger return -EOPNOTSUPP; 2177dc54d3bSClément Léger } 2187dc54d3bSClément Léger 2197dc54d3bSClément Léger val = FIELD_PREP(MIIC_CONVCTRL_CONV_MODE, conv_mode) | 2207dc54d3bSClément Léger FIELD_PREP(MIIC_CONVCTRL_CONV_SPEED, speed); 2217dc54d3bSClément Léger 2227dc54d3bSClément Léger miic_reg_rmw(miic, MIIC_CONVCTRL(port), 2237dc54d3bSClément Léger MIIC_CONVCTRL_CONV_MODE | MIIC_CONVCTRL_CONV_SPEED, val); 2247dc54d3bSClément Léger miic_converter_enable(miic_port->miic, miic_port->port, 1); 2257dc54d3bSClément Léger 2267dc54d3bSClément Léger return 0; 2277dc54d3bSClément Léger } 2287dc54d3bSClément Léger 2297dc54d3bSClément Léger static void miic_link_up(struct phylink_pcs *pcs, unsigned int mode, 2307dc54d3bSClément Léger phy_interface_t interface, int speed, int duplex) 2317dc54d3bSClément Léger { 2327dc54d3bSClément Léger struct miic_port *miic_port = phylink_pcs_to_miic_port(pcs); 2337dc54d3bSClément Léger struct miic *miic = miic_port->miic; 2347dc54d3bSClément Léger u32 conv_speed = 0, val = 0; 2357dc54d3bSClément Léger int port = miic_port->port; 2367dc54d3bSClément Léger 2377dc54d3bSClément Léger if (duplex == DUPLEX_FULL) 2387dc54d3bSClément Léger val |= MIIC_CONVCTRL_FULLD; 2397dc54d3bSClément Léger 2407dc54d3bSClément Léger /* No speed in MII through-mode */ 2417dc54d3bSClément Léger if (interface != PHY_INTERFACE_MODE_MII) { 2427dc54d3bSClément Léger switch (speed) { 2437dc54d3bSClément Léger case SPEED_1000: 2447dc54d3bSClément Léger conv_speed = CONV_MODE_1000MBPS; 2457dc54d3bSClément Léger break; 2467dc54d3bSClément Léger case SPEED_100: 2477dc54d3bSClément Léger conv_speed = CONV_MODE_100MBPS; 2487dc54d3bSClément Léger break; 2497dc54d3bSClément Léger case SPEED_10: 2507dc54d3bSClément Léger conv_speed = CONV_MODE_10MBPS; 2517dc54d3bSClément Léger break; 2527dc54d3bSClément Léger default: 2537dc54d3bSClément Léger return; 2547dc54d3bSClément Léger } 2557dc54d3bSClément Léger } 2567dc54d3bSClément Léger 2577dc54d3bSClément Léger val |= FIELD_PREP(MIIC_CONVCTRL_CONV_SPEED, conv_speed); 2587dc54d3bSClément Léger 2597dc54d3bSClément Léger miic_reg_rmw(miic, MIIC_CONVCTRL(port), 2607dc54d3bSClément Léger (MIIC_CONVCTRL_CONV_SPEED | MIIC_CONVCTRL_FULLD), val); 2617dc54d3bSClément Léger } 2627dc54d3bSClément Léger 2637dc54d3bSClément Léger static int miic_validate(struct phylink_pcs *pcs, unsigned long *supported, 2647dc54d3bSClément Léger const struct phylink_link_state *state) 2657dc54d3bSClément Léger { 2667dc54d3bSClément Léger if (phy_interface_mode_is_rgmii(state->interface) || 2677dc54d3bSClément Léger state->interface == PHY_INTERFACE_MODE_RMII || 2687dc54d3bSClément Léger state->interface == PHY_INTERFACE_MODE_MII) 2697dc54d3bSClément Léger return 1; 2707dc54d3bSClément Léger 2717dc54d3bSClément Léger return -EINVAL; 2727dc54d3bSClément Léger } 2737dc54d3bSClément Léger 2747dc54d3bSClément Léger static const struct phylink_pcs_ops miic_phylink_ops = { 2757dc54d3bSClément Léger .pcs_validate = miic_validate, 2767dc54d3bSClément Léger .pcs_config = miic_config, 2777dc54d3bSClément Léger .pcs_link_up = miic_link_up, 2787dc54d3bSClément Léger }; 2797dc54d3bSClément Léger 2807dc54d3bSClément Léger struct phylink_pcs *miic_create(struct device *dev, struct device_node *np) 2817dc54d3bSClément Léger { 2827dc54d3bSClément Léger struct platform_device *pdev; 2837dc54d3bSClément Léger struct miic_port *miic_port; 2847dc54d3bSClément Léger struct device_node *pcs_np; 2857dc54d3bSClément Léger struct miic *miic; 2867dc54d3bSClément Léger u32 port; 2877dc54d3bSClément Léger 2887dc54d3bSClément Léger if (!of_device_is_available(np)) 2897dc54d3bSClément Léger return ERR_PTR(-ENODEV); 2907dc54d3bSClément Léger 2917dc54d3bSClément Léger if (of_property_read_u32(np, "reg", &port)) 2927dc54d3bSClément Léger return ERR_PTR(-EINVAL); 2937dc54d3bSClément Léger 2947dc54d3bSClément Léger if (port > MIIC_MAX_NR_PORTS || port < 1) 2957dc54d3bSClément Léger return ERR_PTR(-EINVAL); 2967dc54d3bSClément Léger 2977dc54d3bSClément Léger /* The PCS pdev is attached to the parent node */ 2987dc54d3bSClément Léger pcs_np = of_get_parent(np); 2997dc54d3bSClément Léger if (!pcs_np) 3007dc54d3bSClément Léger return ERR_PTR(-ENODEV); 3017dc54d3bSClément Léger 3027dc54d3bSClément Léger if (!of_device_is_available(pcs_np)) { 3037dc54d3bSClément Léger of_node_put(pcs_np); 3047dc54d3bSClément Léger return ERR_PTR(-ENODEV); 3057dc54d3bSClément Léger } 3067dc54d3bSClément Léger 3077dc54d3bSClément Léger pdev = of_find_device_by_node(pcs_np); 3087dc54d3bSClément Léger of_node_put(pcs_np); 3097dc54d3bSClément Léger if (!pdev || !platform_get_drvdata(pdev)) 3107dc54d3bSClément Léger return ERR_PTR(-EPROBE_DEFER); 3117dc54d3bSClément Léger 3127dc54d3bSClément Léger miic_port = kzalloc(sizeof(*miic_port), GFP_KERNEL); 3137dc54d3bSClément Léger if (!miic_port) 3147dc54d3bSClément Léger return ERR_PTR(-ENOMEM); 3157dc54d3bSClément Léger 3167dc54d3bSClément Léger miic = platform_get_drvdata(pdev); 3177dc54d3bSClément Léger device_link_add(dev, miic->dev, DL_FLAG_AUTOREMOVE_CONSUMER); 3187dc54d3bSClément Léger 3197dc54d3bSClément Léger miic_port->miic = miic; 3207dc54d3bSClément Léger miic_port->port = port - 1; 3217dc54d3bSClément Léger miic_port->pcs.ops = &miic_phylink_ops; 3227dc54d3bSClément Léger 3237dc54d3bSClément Léger return &miic_port->pcs; 3247dc54d3bSClément Léger } 3257dc54d3bSClément Léger EXPORT_SYMBOL(miic_create); 3267dc54d3bSClément Léger 3277dc54d3bSClément Léger void miic_destroy(struct phylink_pcs *pcs) 3287dc54d3bSClément Léger { 3297dc54d3bSClément Léger struct miic_port *miic_port = phylink_pcs_to_miic_port(pcs); 3307dc54d3bSClément Léger 3317dc54d3bSClément Léger miic_converter_enable(miic_port->miic, miic_port->port, 0); 3327dc54d3bSClément Léger kfree(miic_port); 3337dc54d3bSClément Léger } 3347dc54d3bSClément Léger EXPORT_SYMBOL(miic_destroy); 3357dc54d3bSClément Léger 3367dc54d3bSClément Léger static int miic_init_hw(struct miic *miic, u32 cfg_mode) 3377dc54d3bSClément Léger { 3387dc54d3bSClément Léger int port; 3397dc54d3bSClément Léger 3407dc54d3bSClément Léger /* Unlock write access to accessory registers (cf datasheet). If this 3417dc54d3bSClément Léger * is going to be used in conjunction with the Cortex-M3, this sequence 3427dc54d3bSClément Léger * will have to be moved in register write 3437dc54d3bSClément Léger */ 3447dc54d3bSClément Léger miic_reg_writel(miic, MIIC_PRCMD, 0x00A5); 3457dc54d3bSClément Léger miic_reg_writel(miic, MIIC_PRCMD, 0x0001); 3467dc54d3bSClément Léger miic_reg_writel(miic, MIIC_PRCMD, 0xFFFE); 3477dc54d3bSClément Léger miic_reg_writel(miic, MIIC_PRCMD, 0x0001); 3487dc54d3bSClément Léger 3497dc54d3bSClément Léger miic_reg_writel(miic, MIIC_MODCTRL, 3507dc54d3bSClément Léger FIELD_PREP(MIIC_MODCTRL_SW_MODE, cfg_mode)); 3517dc54d3bSClément Léger 3527dc54d3bSClément Léger for (port = 0; port < MIIC_MAX_NR_PORTS; port++) { 3537dc54d3bSClément Léger miic_converter_enable(miic, port, 0); 3547dc54d3bSClément Léger /* Disable speed/duplex control from these registers, datasheet 3557dc54d3bSClément Léger * says switch registers should be used to setup switch port 3567dc54d3bSClément Léger * speed and duplex. 3577dc54d3bSClément Léger */ 3587dc54d3bSClément Léger miic_reg_writel(miic, MIIC_SWCTRL, 0x0); 3597dc54d3bSClément Léger miic_reg_writel(miic, MIIC_SWDUPC, 0x0); 3607dc54d3bSClément Léger } 3617dc54d3bSClément Léger 3627dc54d3bSClément Léger return 0; 3637dc54d3bSClément Léger } 3647dc54d3bSClément Léger 3657dc54d3bSClément Léger static bool miic_modctrl_match(s8 table_val[MIIC_MODCTRL_CONF_CONV_NUM], 3667dc54d3bSClément Léger s8 dt_val[MIIC_MODCTRL_CONF_CONV_NUM]) 3677dc54d3bSClément Léger { 3687dc54d3bSClément Léger int i; 3697dc54d3bSClément Léger 3707dc54d3bSClément Léger for (i = 0; i < MIIC_MODCTRL_CONF_CONV_NUM; i++) { 3717dc54d3bSClément Léger if (dt_val[i] == MIIC_MODCTRL_CONF_NONE) 3727dc54d3bSClément Léger continue; 3737dc54d3bSClément Léger 3747dc54d3bSClément Léger if (dt_val[i] != table_val[i]) 3757dc54d3bSClément Léger return false; 3767dc54d3bSClément Léger } 3777dc54d3bSClément Léger 3787dc54d3bSClément Léger return true; 3797dc54d3bSClément Léger } 3807dc54d3bSClément Léger 3817dc54d3bSClément Léger static void miic_dump_conf(struct device *dev, 3827dc54d3bSClément Léger s8 conf[MIIC_MODCTRL_CONF_CONV_NUM]) 3837dc54d3bSClément Léger { 3847dc54d3bSClément Léger const char *conf_name; 3857dc54d3bSClément Léger int i; 3867dc54d3bSClément Léger 3877dc54d3bSClément Léger for (i = 0; i < MIIC_MODCTRL_CONF_CONV_NUM; i++) { 3887dc54d3bSClément Léger if (conf[i] != MIIC_MODCTRL_CONF_NONE) 3897dc54d3bSClément Léger conf_name = conf_to_string[conf[i]]; 3907dc54d3bSClément Léger else 3917dc54d3bSClément Léger conf_name = "NONE"; 3927dc54d3bSClément Léger 3937dc54d3bSClément Léger dev_err(dev, "%s: %s\n", index_to_string[i], conf_name); 3947dc54d3bSClément Léger } 3957dc54d3bSClément Léger } 3967dc54d3bSClément Léger 3977dc54d3bSClément Léger static int miic_match_dt_conf(struct device *dev, 3987dc54d3bSClément Léger s8 dt_val[MIIC_MODCTRL_CONF_CONV_NUM], 3997dc54d3bSClément Léger u32 *mode_cfg) 4007dc54d3bSClément Léger { 4017dc54d3bSClément Léger struct modctrl_match *table_entry; 4027dc54d3bSClément Léger int i; 4037dc54d3bSClément Léger 4047dc54d3bSClément Léger for (i = 0; i < ARRAY_SIZE(modctrl_match_table); i++) { 4057dc54d3bSClément Léger table_entry = &modctrl_match_table[i]; 4067dc54d3bSClément Léger 4077dc54d3bSClément Léger if (miic_modctrl_match(table_entry->conv, dt_val)) { 4087dc54d3bSClément Léger *mode_cfg = table_entry->mode_cfg; 4097dc54d3bSClément Léger return 0; 4107dc54d3bSClément Léger } 4117dc54d3bSClément Léger } 4127dc54d3bSClément Léger 4137dc54d3bSClément Léger dev_err(dev, "Failed to apply requested configuration\n"); 4147dc54d3bSClément Léger miic_dump_conf(dev, dt_val); 4157dc54d3bSClément Léger 4167dc54d3bSClément Léger return -EINVAL; 4177dc54d3bSClément Léger } 4187dc54d3bSClément Léger 4197dc54d3bSClément Léger static int miic_parse_dt(struct device *dev, u32 *mode_cfg) 4207dc54d3bSClément Léger { 4217dc54d3bSClément Léger s8 dt_val[MIIC_MODCTRL_CONF_CONV_NUM]; 4227dc54d3bSClément Léger struct device_node *np = dev->of_node; 4237dc54d3bSClément Léger struct device_node *conv; 4247dc54d3bSClément Léger u32 conf; 4257dc54d3bSClément Léger int port; 4267dc54d3bSClément Léger 4277dc54d3bSClément Léger memset(dt_val, MIIC_MODCTRL_CONF_NONE, sizeof(dt_val)); 4287dc54d3bSClément Léger 4297dc54d3bSClément Léger if (of_property_read_u32(np, "renesas,miic-switch-portin", &conf) == 0) 4307dc54d3bSClément Léger dt_val[0] = conf; 4317dc54d3bSClément Léger 4327dc54d3bSClément Léger for_each_child_of_node(np, conv) { 4337dc54d3bSClément Léger if (of_property_read_u32(conv, "reg", &port)) 4347dc54d3bSClément Léger continue; 4357dc54d3bSClément Léger 4367dc54d3bSClément Léger if (!of_device_is_available(conv)) 4377dc54d3bSClément Léger continue; 4387dc54d3bSClément Léger 4397dc54d3bSClément Léger if (of_property_read_u32(conv, "renesas,miic-input", &conf) == 0) 4407dc54d3bSClément Léger dt_val[port] = conf; 4417dc54d3bSClément Léger } 4427dc54d3bSClément Léger 4437dc54d3bSClément Léger return miic_match_dt_conf(dev, dt_val, mode_cfg); 4447dc54d3bSClément Léger } 4457dc54d3bSClément Léger 4467dc54d3bSClément Léger static int miic_probe(struct platform_device *pdev) 4477dc54d3bSClément Léger { 4487dc54d3bSClément Léger struct device *dev = &pdev->dev; 4497dc54d3bSClément Léger struct miic *miic; 4507dc54d3bSClément Léger u32 mode_cfg; 4517dc54d3bSClément Léger int ret; 4527dc54d3bSClément Léger 4537dc54d3bSClément Léger ret = miic_parse_dt(dev, &mode_cfg); 4547dc54d3bSClément Léger if (ret < 0) 4557dc54d3bSClément Léger return ret; 4567dc54d3bSClément Léger 4577dc54d3bSClément Léger miic = devm_kzalloc(dev, sizeof(*miic), GFP_KERNEL); 4587dc54d3bSClément Léger if (!miic) 4597dc54d3bSClément Léger return -ENOMEM; 4607dc54d3bSClément Léger 4617dc54d3bSClément Léger spin_lock_init(&miic->lock); 4627dc54d3bSClément Léger miic->dev = dev; 4637dc54d3bSClément Léger miic->base = devm_platform_ioremap_resource(pdev, 0); 464*dbc6fc7eSYang Yingliang if (IS_ERR(miic->base)) 465*dbc6fc7eSYang Yingliang return PTR_ERR(miic->base); 4667dc54d3bSClément Léger 4677dc54d3bSClément Léger ret = devm_pm_runtime_enable(dev); 4687dc54d3bSClément Léger if (ret < 0) 4697dc54d3bSClément Léger return ret; 4707dc54d3bSClément Léger 4717dc54d3bSClément Léger ret = pm_runtime_resume_and_get(dev); 4727dc54d3bSClément Léger if (ret < 0) 4737dc54d3bSClément Léger return ret; 4747dc54d3bSClément Léger 4757dc54d3bSClément Léger ret = miic_init_hw(miic, mode_cfg); 4767dc54d3bSClément Léger if (ret) 4777dc54d3bSClément Léger goto disable_runtime_pm; 4787dc54d3bSClément Léger 4797dc54d3bSClément Léger /* miic_create() relies on that fact that data are attached to the 4807dc54d3bSClément Léger * platform device to determine if the driver is ready so this needs to 4817dc54d3bSClément Léger * be the last thing to be done after everything is initialized 4827dc54d3bSClément Léger * properly. 4837dc54d3bSClément Léger */ 4847dc54d3bSClément Léger platform_set_drvdata(pdev, miic); 4857dc54d3bSClément Léger 4867dc54d3bSClément Léger return 0; 4877dc54d3bSClément Léger 4887dc54d3bSClément Léger disable_runtime_pm: 4897dc54d3bSClément Léger pm_runtime_put(dev); 4907dc54d3bSClément Léger 4917dc54d3bSClément Léger return ret; 4927dc54d3bSClément Léger } 4937dc54d3bSClément Léger 4947dc54d3bSClément Léger static int miic_remove(struct platform_device *pdev) 4957dc54d3bSClément Léger { 4967dc54d3bSClément Léger pm_runtime_put(&pdev->dev); 4977dc54d3bSClément Léger 4987dc54d3bSClément Léger return 0; 4997dc54d3bSClément Léger } 5007dc54d3bSClément Léger 5017dc54d3bSClément Léger static const struct of_device_id miic_of_mtable[] = { 5027dc54d3bSClément Léger { .compatible = "renesas,rzn1-miic" }, 5037dc54d3bSClément Léger { /* sentinel */ }, 5047dc54d3bSClément Léger }; 5057dc54d3bSClément Léger MODULE_DEVICE_TABLE(of, miic_of_mtable); 5067dc54d3bSClément Léger 5077dc54d3bSClément Léger static struct platform_driver miic_driver = { 5087dc54d3bSClément Léger .driver = { 5097dc54d3bSClément Léger .name = "rzn1_miic", 5107dc54d3bSClément Léger .suppress_bind_attrs = true, 5117dc54d3bSClément Léger .of_match_table = miic_of_mtable, 5127dc54d3bSClément Léger }, 5137dc54d3bSClément Léger .probe = miic_probe, 5147dc54d3bSClément Léger .remove = miic_remove, 5157dc54d3bSClément Léger }; 5167dc54d3bSClément Léger module_platform_driver(miic_driver); 5177dc54d3bSClément Léger 5187dc54d3bSClément Léger MODULE_LICENSE("GPL"); 5197dc54d3bSClément Léger MODULE_DESCRIPTION("Renesas MII converter PCS driver"); 5207dc54d3bSClément Léger MODULE_AUTHOR("Clément Léger <clement.leger@bootlin.com>"); 521