162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2022 Schneider Electric 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Clément Léger <clement.leger@bootlin.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/clk.h> 962306a36Sopenharmony_ci#include <linux/device.h> 1062306a36Sopenharmony_ci#include <linux/mdio.h> 1162306a36Sopenharmony_ci#include <linux/of.h> 1262306a36Sopenharmony_ci#include <linux/of_platform.h> 1362306a36Sopenharmony_ci#include <linux/pcs-rzn1-miic.h> 1462306a36Sopenharmony_ci#include <linux/phylink.h> 1562306a36Sopenharmony_ci#include <linux/platform_device.h> 1662306a36Sopenharmony_ci#include <linux/pm_runtime.h> 1762306a36Sopenharmony_ci#include <dt-bindings/net/pcs-rzn1-miic.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#define MIIC_PRCMD 0x0 2062306a36Sopenharmony_ci#define MIIC_ESID_CODE 0x4 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define MIIC_MODCTRL 0x20 2362306a36Sopenharmony_ci#define MIIC_MODCTRL_SW_MODE GENMASK(4, 0) 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define MIIC_CONVCTRL(port) (0x100 + (port) * 4) 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define MIIC_CONVCTRL_CONV_SPEED GENMASK(1, 0) 2862306a36Sopenharmony_ci#define CONV_MODE_10MBPS 0 2962306a36Sopenharmony_ci#define CONV_MODE_100MBPS 1 3062306a36Sopenharmony_ci#define CONV_MODE_1000MBPS 2 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#define MIIC_CONVCTRL_CONV_MODE GENMASK(3, 2) 3362306a36Sopenharmony_ci#define CONV_MODE_MII 0 3462306a36Sopenharmony_ci#define CONV_MODE_RMII 1 3562306a36Sopenharmony_ci#define CONV_MODE_RGMII 2 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#define MIIC_CONVCTRL_FULLD BIT(8) 3862306a36Sopenharmony_ci#define MIIC_CONVCTRL_RGMII_LINK BIT(12) 3962306a36Sopenharmony_ci#define MIIC_CONVCTRL_RGMII_DUPLEX BIT(13) 4062306a36Sopenharmony_ci#define MIIC_CONVCTRL_RGMII_SPEED GENMASK(15, 14) 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define MIIC_CONVRST 0x114 4362306a36Sopenharmony_ci#define MIIC_CONVRST_PHYIF_RST(port) BIT(port) 4462306a36Sopenharmony_ci#define MIIC_CONVRST_PHYIF_RST_MASK GENMASK(4, 0) 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci#define MIIC_SWCTRL 0x304 4762306a36Sopenharmony_ci#define MIIC_SWDUPC 0x308 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci#define MIIC_MAX_NR_PORTS 5 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci#define MIIC_MODCTRL_CONF_CONV_NUM 6 5262306a36Sopenharmony_ci#define MIIC_MODCTRL_CONF_NONE -1 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci/** 5562306a36Sopenharmony_ci * struct modctrl_match - Matching table entry for convctrl configuration 5662306a36Sopenharmony_ci * See section 8.2.1 of manual. 5762306a36Sopenharmony_ci * @mode_cfg: Configuration value for convctrl 5862306a36Sopenharmony_ci * @conv: Configuration of ethernet port muxes. First index is SWITCH_PORTIN, 5962306a36Sopenharmony_ci * then index 1 - 5 are CONV1 - CONV5. 6062306a36Sopenharmony_ci */ 6162306a36Sopenharmony_cistruct modctrl_match { 6262306a36Sopenharmony_ci u32 mode_cfg; 6362306a36Sopenharmony_ci u8 conv[MIIC_MODCTRL_CONF_CONV_NUM]; 6462306a36Sopenharmony_ci}; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic struct modctrl_match modctrl_match_table[] = { 6762306a36Sopenharmony_ci {0x0, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 6862306a36Sopenharmony_ci MIIC_SWITCH_PORTC, MIIC_SERCOS_PORTB, MIIC_SERCOS_PORTA}}, 6962306a36Sopenharmony_ci {0x1, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 7062306a36Sopenharmony_ci MIIC_SWITCH_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, 7162306a36Sopenharmony_ci {0x2, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 7262306a36Sopenharmony_ci MIIC_ETHERCAT_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, 7362306a36Sopenharmony_ci {0x3, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 7462306a36Sopenharmony_ci MIIC_SWITCH_PORTC, MIIC_SWITCH_PORTB, MIIC_SWITCH_PORTA}}, 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci {0x8, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 7762306a36Sopenharmony_ci MIIC_SWITCH_PORTC, MIIC_SERCOS_PORTB, MIIC_SERCOS_PORTA}}, 7862306a36Sopenharmony_ci {0x9, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 7962306a36Sopenharmony_ci MIIC_SWITCH_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, 8062306a36Sopenharmony_ci {0xA, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 8162306a36Sopenharmony_ci MIIC_ETHERCAT_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, 8262306a36Sopenharmony_ci {0xB, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 8362306a36Sopenharmony_ci MIIC_SWITCH_PORTC, MIIC_SWITCH_PORTB, MIIC_SWITCH_PORTA}}, 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci {0x10, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 8662306a36Sopenharmony_ci MIIC_SWITCH_PORTC, MIIC_SERCOS_PORTB, MIIC_SERCOS_PORTA}}, 8762306a36Sopenharmony_ci {0x11, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 8862306a36Sopenharmony_ci MIIC_SWITCH_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, 8962306a36Sopenharmony_ci {0x12, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 9062306a36Sopenharmony_ci MIIC_ETHERCAT_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, 9162306a36Sopenharmony_ci {0x13, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, 9262306a36Sopenharmony_ci MIIC_SWITCH_PORTC, MIIC_SWITCH_PORTB, MIIC_SWITCH_PORTA}} 9362306a36Sopenharmony_ci}; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic const char * const conf_to_string[] = { 9662306a36Sopenharmony_ci [MIIC_GMAC1_PORT] = "GMAC1_PORT", 9762306a36Sopenharmony_ci [MIIC_GMAC2_PORT] = "GMAC2_PORT", 9862306a36Sopenharmony_ci [MIIC_RTOS_PORT] = "RTOS_PORT", 9962306a36Sopenharmony_ci [MIIC_SERCOS_PORTA] = "SERCOS_PORTA", 10062306a36Sopenharmony_ci [MIIC_SERCOS_PORTB] = "SERCOS_PORTB", 10162306a36Sopenharmony_ci [MIIC_ETHERCAT_PORTA] = "ETHERCAT_PORTA", 10262306a36Sopenharmony_ci [MIIC_ETHERCAT_PORTB] = "ETHERCAT_PORTB", 10362306a36Sopenharmony_ci [MIIC_ETHERCAT_PORTC] = "ETHERCAT_PORTC", 10462306a36Sopenharmony_ci [MIIC_SWITCH_PORTA] = "SWITCH_PORTA", 10562306a36Sopenharmony_ci [MIIC_SWITCH_PORTB] = "SWITCH_PORTB", 10662306a36Sopenharmony_ci [MIIC_SWITCH_PORTC] = "SWITCH_PORTC", 10762306a36Sopenharmony_ci [MIIC_SWITCH_PORTD] = "SWITCH_PORTD", 10862306a36Sopenharmony_ci [MIIC_HSR_PORTA] = "HSR_PORTA", 10962306a36Sopenharmony_ci [MIIC_HSR_PORTB] = "HSR_PORTB", 11062306a36Sopenharmony_ci}; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistatic const char *index_to_string[MIIC_MODCTRL_CONF_CONV_NUM] = { 11362306a36Sopenharmony_ci "SWITCH_PORTIN", 11462306a36Sopenharmony_ci "CONV1", 11562306a36Sopenharmony_ci "CONV2", 11662306a36Sopenharmony_ci "CONV3", 11762306a36Sopenharmony_ci "CONV4", 11862306a36Sopenharmony_ci "CONV5", 11962306a36Sopenharmony_ci}; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci/** 12262306a36Sopenharmony_ci * struct miic - MII converter structure 12362306a36Sopenharmony_ci * @base: base address of the MII converter 12462306a36Sopenharmony_ci * @dev: Device associated to the MII converter 12562306a36Sopenharmony_ci * @lock: Lock used for read-modify-write access 12662306a36Sopenharmony_ci */ 12762306a36Sopenharmony_cistruct miic { 12862306a36Sopenharmony_ci void __iomem *base; 12962306a36Sopenharmony_ci struct device *dev; 13062306a36Sopenharmony_ci spinlock_t lock; 13162306a36Sopenharmony_ci}; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci/** 13462306a36Sopenharmony_ci * struct miic_port - Per port MII converter struct 13562306a36Sopenharmony_ci * @miic: backiling to MII converter structure 13662306a36Sopenharmony_ci * @pcs: PCS structure associated to the port 13762306a36Sopenharmony_ci * @port: port number 13862306a36Sopenharmony_ci * @interface: interface mode of the port 13962306a36Sopenharmony_ci */ 14062306a36Sopenharmony_cistruct miic_port { 14162306a36Sopenharmony_ci struct miic *miic; 14262306a36Sopenharmony_ci struct phylink_pcs pcs; 14362306a36Sopenharmony_ci int port; 14462306a36Sopenharmony_ci phy_interface_t interface; 14562306a36Sopenharmony_ci}; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic struct miic_port *phylink_pcs_to_miic_port(struct phylink_pcs *pcs) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci return container_of(pcs, struct miic_port, pcs); 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic void miic_reg_writel(struct miic *miic, int offset, u32 value) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci writel(value, miic->base + offset); 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cistatic u32 miic_reg_readl(struct miic *miic, int offset) 15862306a36Sopenharmony_ci{ 15962306a36Sopenharmony_ci return readl(miic->base + offset); 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_cistatic void miic_reg_rmw(struct miic *miic, int offset, u32 mask, u32 val) 16362306a36Sopenharmony_ci{ 16462306a36Sopenharmony_ci u32 reg; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci spin_lock(&miic->lock); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci reg = miic_reg_readl(miic, offset); 16962306a36Sopenharmony_ci reg &= ~mask; 17062306a36Sopenharmony_ci reg |= val; 17162306a36Sopenharmony_ci miic_reg_writel(miic, offset, reg); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci spin_unlock(&miic->lock); 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic void miic_converter_enable(struct miic *miic, int port, int enable) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci u32 val = 0; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci if (enable) 18162306a36Sopenharmony_ci val = MIIC_CONVRST_PHYIF_RST(port); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci miic_reg_rmw(miic, MIIC_CONVRST, MIIC_CONVRST_PHYIF_RST(port), val); 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_cistatic int miic_config(struct phylink_pcs *pcs, unsigned int mode, 18762306a36Sopenharmony_ci phy_interface_t interface, 18862306a36Sopenharmony_ci const unsigned long *advertising, bool permit) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci struct miic_port *miic_port = phylink_pcs_to_miic_port(pcs); 19162306a36Sopenharmony_ci struct miic *miic = miic_port->miic; 19262306a36Sopenharmony_ci u32 speed, conv_mode, val, mask; 19362306a36Sopenharmony_ci int port = miic_port->port; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci switch (interface) { 19662306a36Sopenharmony_ci case PHY_INTERFACE_MODE_RMII: 19762306a36Sopenharmony_ci conv_mode = CONV_MODE_RMII; 19862306a36Sopenharmony_ci speed = CONV_MODE_100MBPS; 19962306a36Sopenharmony_ci break; 20062306a36Sopenharmony_ci case PHY_INTERFACE_MODE_RGMII: 20162306a36Sopenharmony_ci case PHY_INTERFACE_MODE_RGMII_ID: 20262306a36Sopenharmony_ci case PHY_INTERFACE_MODE_RGMII_TXID: 20362306a36Sopenharmony_ci case PHY_INTERFACE_MODE_RGMII_RXID: 20462306a36Sopenharmony_ci conv_mode = CONV_MODE_RGMII; 20562306a36Sopenharmony_ci speed = CONV_MODE_1000MBPS; 20662306a36Sopenharmony_ci break; 20762306a36Sopenharmony_ci case PHY_INTERFACE_MODE_MII: 20862306a36Sopenharmony_ci conv_mode = CONV_MODE_MII; 20962306a36Sopenharmony_ci /* When in MII mode, speed should be set to 0 (which is actually 21062306a36Sopenharmony_ci * CONV_MODE_10MBPS) 21162306a36Sopenharmony_ci */ 21262306a36Sopenharmony_ci speed = CONV_MODE_10MBPS; 21362306a36Sopenharmony_ci break; 21462306a36Sopenharmony_ci default: 21562306a36Sopenharmony_ci return -EOPNOTSUPP; 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci val = FIELD_PREP(MIIC_CONVCTRL_CONV_MODE, conv_mode); 21962306a36Sopenharmony_ci mask = MIIC_CONVCTRL_CONV_MODE; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci /* Update speed only if we are going to change the interface because 22262306a36Sopenharmony_ci * the link might already be up and it would break it if the speed is 22362306a36Sopenharmony_ci * changed. 22462306a36Sopenharmony_ci */ 22562306a36Sopenharmony_ci if (interface != miic_port->interface) { 22662306a36Sopenharmony_ci val |= FIELD_PREP(MIIC_CONVCTRL_CONV_SPEED, speed); 22762306a36Sopenharmony_ci mask |= MIIC_CONVCTRL_CONV_SPEED; 22862306a36Sopenharmony_ci miic_port->interface = interface; 22962306a36Sopenharmony_ci } 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci miic_reg_rmw(miic, MIIC_CONVCTRL(port), mask, val); 23262306a36Sopenharmony_ci miic_converter_enable(miic, miic_port->port, 1); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci return 0; 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cistatic void miic_link_up(struct phylink_pcs *pcs, unsigned int mode, 23862306a36Sopenharmony_ci phy_interface_t interface, int speed, int duplex) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci struct miic_port *miic_port = phylink_pcs_to_miic_port(pcs); 24162306a36Sopenharmony_ci struct miic *miic = miic_port->miic; 24262306a36Sopenharmony_ci u32 conv_speed = 0, val = 0; 24362306a36Sopenharmony_ci int port = miic_port->port; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci if (duplex == DUPLEX_FULL) 24662306a36Sopenharmony_ci val |= MIIC_CONVCTRL_FULLD; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci /* No speed in MII through-mode */ 24962306a36Sopenharmony_ci if (interface != PHY_INTERFACE_MODE_MII) { 25062306a36Sopenharmony_ci switch (speed) { 25162306a36Sopenharmony_ci case SPEED_1000: 25262306a36Sopenharmony_ci conv_speed = CONV_MODE_1000MBPS; 25362306a36Sopenharmony_ci break; 25462306a36Sopenharmony_ci case SPEED_100: 25562306a36Sopenharmony_ci conv_speed = CONV_MODE_100MBPS; 25662306a36Sopenharmony_ci break; 25762306a36Sopenharmony_ci case SPEED_10: 25862306a36Sopenharmony_ci conv_speed = CONV_MODE_10MBPS; 25962306a36Sopenharmony_ci break; 26062306a36Sopenharmony_ci default: 26162306a36Sopenharmony_ci return; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci } 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci val |= FIELD_PREP(MIIC_CONVCTRL_CONV_SPEED, conv_speed); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci miic_reg_rmw(miic, MIIC_CONVCTRL(port), 26862306a36Sopenharmony_ci (MIIC_CONVCTRL_CONV_SPEED | MIIC_CONVCTRL_FULLD), val); 26962306a36Sopenharmony_ci} 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_cistatic int miic_validate(struct phylink_pcs *pcs, unsigned long *supported, 27262306a36Sopenharmony_ci const struct phylink_link_state *state) 27362306a36Sopenharmony_ci{ 27462306a36Sopenharmony_ci if (phy_interface_mode_is_rgmii(state->interface) || 27562306a36Sopenharmony_ci state->interface == PHY_INTERFACE_MODE_RMII || 27662306a36Sopenharmony_ci state->interface == PHY_INTERFACE_MODE_MII) 27762306a36Sopenharmony_ci return 1; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci return -EINVAL; 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_cistatic const struct phylink_pcs_ops miic_phylink_ops = { 28362306a36Sopenharmony_ci .pcs_validate = miic_validate, 28462306a36Sopenharmony_ci .pcs_config = miic_config, 28562306a36Sopenharmony_ci .pcs_link_up = miic_link_up, 28662306a36Sopenharmony_ci}; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_cistruct phylink_pcs *miic_create(struct device *dev, struct device_node *np) 28962306a36Sopenharmony_ci{ 29062306a36Sopenharmony_ci struct platform_device *pdev; 29162306a36Sopenharmony_ci struct miic_port *miic_port; 29262306a36Sopenharmony_ci struct device_node *pcs_np; 29362306a36Sopenharmony_ci struct miic *miic; 29462306a36Sopenharmony_ci u32 port; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci if (!of_device_is_available(np)) 29762306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci if (of_property_read_u32(np, "reg", &port)) 30062306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci if (port > MIIC_MAX_NR_PORTS || port < 1) 30362306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci /* The PCS pdev is attached to the parent node */ 30662306a36Sopenharmony_ci pcs_np = of_get_parent(np); 30762306a36Sopenharmony_ci if (!pcs_np) 30862306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci if (!of_device_is_available(pcs_np)) { 31162306a36Sopenharmony_ci of_node_put(pcs_np); 31262306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 31362306a36Sopenharmony_ci } 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci pdev = of_find_device_by_node(pcs_np); 31662306a36Sopenharmony_ci of_node_put(pcs_np); 31762306a36Sopenharmony_ci if (!pdev || !platform_get_drvdata(pdev)) { 31862306a36Sopenharmony_ci if (pdev) 31962306a36Sopenharmony_ci put_device(&pdev->dev); 32062306a36Sopenharmony_ci return ERR_PTR(-EPROBE_DEFER); 32162306a36Sopenharmony_ci } 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci miic_port = kzalloc(sizeof(*miic_port), GFP_KERNEL); 32462306a36Sopenharmony_ci if (!miic_port) { 32562306a36Sopenharmony_ci put_device(&pdev->dev); 32662306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 32762306a36Sopenharmony_ci } 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci miic = platform_get_drvdata(pdev); 33062306a36Sopenharmony_ci device_link_add(dev, miic->dev, DL_FLAG_AUTOREMOVE_CONSUMER); 33162306a36Sopenharmony_ci put_device(&pdev->dev); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci miic_port->miic = miic; 33462306a36Sopenharmony_ci miic_port->port = port - 1; 33562306a36Sopenharmony_ci miic_port->pcs.ops = &miic_phylink_ops; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci return &miic_port->pcs; 33862306a36Sopenharmony_ci} 33962306a36Sopenharmony_ciEXPORT_SYMBOL(miic_create); 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_civoid miic_destroy(struct phylink_pcs *pcs) 34262306a36Sopenharmony_ci{ 34362306a36Sopenharmony_ci struct miic_port *miic_port = phylink_pcs_to_miic_port(pcs); 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci miic_converter_enable(miic_port->miic, miic_port->port, 0); 34662306a36Sopenharmony_ci kfree(miic_port); 34762306a36Sopenharmony_ci} 34862306a36Sopenharmony_ciEXPORT_SYMBOL(miic_destroy); 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_cistatic int miic_init_hw(struct miic *miic, u32 cfg_mode) 35162306a36Sopenharmony_ci{ 35262306a36Sopenharmony_ci int port; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci /* Unlock write access to accessory registers (cf datasheet). If this 35562306a36Sopenharmony_ci * is going to be used in conjunction with the Cortex-M3, this sequence 35662306a36Sopenharmony_ci * will have to be moved in register write 35762306a36Sopenharmony_ci */ 35862306a36Sopenharmony_ci miic_reg_writel(miic, MIIC_PRCMD, 0x00A5); 35962306a36Sopenharmony_ci miic_reg_writel(miic, MIIC_PRCMD, 0x0001); 36062306a36Sopenharmony_ci miic_reg_writel(miic, MIIC_PRCMD, 0xFFFE); 36162306a36Sopenharmony_ci miic_reg_writel(miic, MIIC_PRCMD, 0x0001); 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci miic_reg_writel(miic, MIIC_MODCTRL, 36462306a36Sopenharmony_ci FIELD_PREP(MIIC_MODCTRL_SW_MODE, cfg_mode)); 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci for (port = 0; port < MIIC_MAX_NR_PORTS; port++) { 36762306a36Sopenharmony_ci miic_converter_enable(miic, port, 0); 36862306a36Sopenharmony_ci /* Disable speed/duplex control from these registers, datasheet 36962306a36Sopenharmony_ci * says switch registers should be used to setup switch port 37062306a36Sopenharmony_ci * speed and duplex. 37162306a36Sopenharmony_ci */ 37262306a36Sopenharmony_ci miic_reg_writel(miic, MIIC_SWCTRL, 0x0); 37362306a36Sopenharmony_ci miic_reg_writel(miic, MIIC_SWDUPC, 0x0); 37462306a36Sopenharmony_ci } 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci return 0; 37762306a36Sopenharmony_ci} 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_cistatic bool miic_modctrl_match(s8 table_val[MIIC_MODCTRL_CONF_CONV_NUM], 38062306a36Sopenharmony_ci s8 dt_val[MIIC_MODCTRL_CONF_CONV_NUM]) 38162306a36Sopenharmony_ci{ 38262306a36Sopenharmony_ci int i; 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci for (i = 0; i < MIIC_MODCTRL_CONF_CONV_NUM; i++) { 38562306a36Sopenharmony_ci if (dt_val[i] == MIIC_MODCTRL_CONF_NONE) 38662306a36Sopenharmony_ci continue; 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci if (dt_val[i] != table_val[i]) 38962306a36Sopenharmony_ci return false; 39062306a36Sopenharmony_ci } 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci return true; 39362306a36Sopenharmony_ci} 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_cistatic void miic_dump_conf(struct device *dev, 39662306a36Sopenharmony_ci s8 conf[MIIC_MODCTRL_CONF_CONV_NUM]) 39762306a36Sopenharmony_ci{ 39862306a36Sopenharmony_ci const char *conf_name; 39962306a36Sopenharmony_ci int i; 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci for (i = 0; i < MIIC_MODCTRL_CONF_CONV_NUM; i++) { 40262306a36Sopenharmony_ci if (conf[i] != MIIC_MODCTRL_CONF_NONE) 40362306a36Sopenharmony_ci conf_name = conf_to_string[conf[i]]; 40462306a36Sopenharmony_ci else 40562306a36Sopenharmony_ci conf_name = "NONE"; 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci dev_err(dev, "%s: %s\n", index_to_string[i], conf_name); 40862306a36Sopenharmony_ci } 40962306a36Sopenharmony_ci} 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_cistatic int miic_match_dt_conf(struct device *dev, 41262306a36Sopenharmony_ci s8 dt_val[MIIC_MODCTRL_CONF_CONV_NUM], 41362306a36Sopenharmony_ci u32 *mode_cfg) 41462306a36Sopenharmony_ci{ 41562306a36Sopenharmony_ci struct modctrl_match *table_entry; 41662306a36Sopenharmony_ci int i; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(modctrl_match_table); i++) { 41962306a36Sopenharmony_ci table_entry = &modctrl_match_table[i]; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci if (miic_modctrl_match(table_entry->conv, dt_val)) { 42262306a36Sopenharmony_ci *mode_cfg = table_entry->mode_cfg; 42362306a36Sopenharmony_ci return 0; 42462306a36Sopenharmony_ci } 42562306a36Sopenharmony_ci } 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci dev_err(dev, "Failed to apply requested configuration\n"); 42862306a36Sopenharmony_ci miic_dump_conf(dev, dt_val); 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci return -EINVAL; 43162306a36Sopenharmony_ci} 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_cistatic int miic_parse_dt(struct device *dev, u32 *mode_cfg) 43462306a36Sopenharmony_ci{ 43562306a36Sopenharmony_ci s8 dt_val[MIIC_MODCTRL_CONF_CONV_NUM]; 43662306a36Sopenharmony_ci struct device_node *np = dev->of_node; 43762306a36Sopenharmony_ci struct device_node *conv; 43862306a36Sopenharmony_ci u32 conf; 43962306a36Sopenharmony_ci int port; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci memset(dt_val, MIIC_MODCTRL_CONF_NONE, sizeof(dt_val)); 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci if (of_property_read_u32(np, "renesas,miic-switch-portin", &conf) == 0) 44462306a36Sopenharmony_ci dt_val[0] = conf; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci for_each_child_of_node(np, conv) { 44762306a36Sopenharmony_ci if (of_property_read_u32(conv, "reg", &port)) 44862306a36Sopenharmony_ci continue; 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci if (!of_device_is_available(conv)) 45162306a36Sopenharmony_ci continue; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci if (of_property_read_u32(conv, "renesas,miic-input", &conf) == 0) 45462306a36Sopenharmony_ci dt_val[port] = conf; 45562306a36Sopenharmony_ci } 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci return miic_match_dt_conf(dev, dt_val, mode_cfg); 45862306a36Sopenharmony_ci} 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_cistatic int miic_probe(struct platform_device *pdev) 46162306a36Sopenharmony_ci{ 46262306a36Sopenharmony_ci struct device *dev = &pdev->dev; 46362306a36Sopenharmony_ci struct miic *miic; 46462306a36Sopenharmony_ci u32 mode_cfg; 46562306a36Sopenharmony_ci int ret; 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci ret = miic_parse_dt(dev, &mode_cfg); 46862306a36Sopenharmony_ci if (ret < 0) 46962306a36Sopenharmony_ci return ret; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci miic = devm_kzalloc(dev, sizeof(*miic), GFP_KERNEL); 47262306a36Sopenharmony_ci if (!miic) 47362306a36Sopenharmony_ci return -ENOMEM; 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci spin_lock_init(&miic->lock); 47662306a36Sopenharmony_ci miic->dev = dev; 47762306a36Sopenharmony_ci miic->base = devm_platform_ioremap_resource(pdev, 0); 47862306a36Sopenharmony_ci if (IS_ERR(miic->base)) 47962306a36Sopenharmony_ci return PTR_ERR(miic->base); 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci ret = devm_pm_runtime_enable(dev); 48262306a36Sopenharmony_ci if (ret < 0) 48362306a36Sopenharmony_ci return ret; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci ret = pm_runtime_resume_and_get(dev); 48662306a36Sopenharmony_ci if (ret < 0) 48762306a36Sopenharmony_ci return ret; 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci ret = miic_init_hw(miic, mode_cfg); 49062306a36Sopenharmony_ci if (ret) 49162306a36Sopenharmony_ci goto disable_runtime_pm; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci /* miic_create() relies on that fact that data are attached to the 49462306a36Sopenharmony_ci * platform device to determine if the driver is ready so this needs to 49562306a36Sopenharmony_ci * be the last thing to be done after everything is initialized 49662306a36Sopenharmony_ci * properly. 49762306a36Sopenharmony_ci */ 49862306a36Sopenharmony_ci platform_set_drvdata(pdev, miic); 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci return 0; 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_cidisable_runtime_pm: 50362306a36Sopenharmony_ci pm_runtime_put(dev); 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci return ret; 50662306a36Sopenharmony_ci} 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_cistatic int miic_remove(struct platform_device *pdev) 50962306a36Sopenharmony_ci{ 51062306a36Sopenharmony_ci pm_runtime_put(&pdev->dev); 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci return 0; 51362306a36Sopenharmony_ci} 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_cistatic const struct of_device_id miic_of_mtable[] = { 51662306a36Sopenharmony_ci { .compatible = "renesas,rzn1-miic" }, 51762306a36Sopenharmony_ci { /* sentinel */ }, 51862306a36Sopenharmony_ci}; 51962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, miic_of_mtable); 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_cistatic struct platform_driver miic_driver = { 52262306a36Sopenharmony_ci .driver = { 52362306a36Sopenharmony_ci .name = "rzn1_miic", 52462306a36Sopenharmony_ci .suppress_bind_attrs = true, 52562306a36Sopenharmony_ci .of_match_table = miic_of_mtable, 52662306a36Sopenharmony_ci }, 52762306a36Sopenharmony_ci .probe = miic_probe, 52862306a36Sopenharmony_ci .remove = miic_remove, 52962306a36Sopenharmony_ci}; 53062306a36Sopenharmony_cimodule_platform_driver(miic_driver); 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 53362306a36Sopenharmony_ciMODULE_DESCRIPTION("Renesas MII converter PCS driver"); 53462306a36Sopenharmony_ciMODULE_AUTHOR("Clément Léger <clement.leger@bootlin.com>"); 535