xref: /openbmc/linux/drivers/net/pcs/pcs-rzn1-miic.c (revision dbc6fc7e)
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