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