Commit | Line | Data |
---|---|---|
d50736a8 RL |
1 | /* |
2 | * Driver for Microsemi VSC85xx PHYs | |
3 | * | |
4 | * Author: Nagaraju Lakkaraju | |
5 | * License: Dual MIT/GPL | |
6 | * Copyright (c) 2016 Microsemi Corporation | |
7 | */ | |
8 | ||
9 | #include <linux/kernel.h> | |
10 | #include <linux/module.h> | |
11 | #include <linux/mdio.h> | |
12 | #include <linux/mii.h> | |
13 | #include <linux/phy.h> | |
14 | ||
15 | enum rgmii_rx_clock_delay { | |
4ffd03f5 RL |
16 | RGMII_RX_CLK_DELAY_0_2_NS = 0, |
17 | RGMII_RX_CLK_DELAY_0_8_NS = 1, | |
18 | RGMII_RX_CLK_DELAY_1_1_NS = 2, | |
19 | RGMII_RX_CLK_DELAY_1_7_NS = 3, | |
20 | RGMII_RX_CLK_DELAY_2_0_NS = 4, | |
21 | RGMII_RX_CLK_DELAY_2_3_NS = 5, | |
22 | RGMII_RX_CLK_DELAY_2_6_NS = 6, | |
23 | RGMII_RX_CLK_DELAY_3_4_NS = 7 | |
d50736a8 RL |
24 | }; |
25 | ||
4ffd03f5 RL |
26 | #define MII_VSC85XX_INT_MASK 25 |
27 | #define MII_VSC85XX_INT_MASK_MASK 0xa000 | |
28 | #define MII_VSC85XX_INT_STATUS 26 | |
d50736a8 | 29 | |
4ffd03f5 RL |
30 | #define MSCC_EXT_PAGE_ACCESS 31 |
31 | #define MSCC_PHY_PAGE_STANDARD 0x0000 /* Standard registers */ | |
32 | #define MSCC_PHY_PAGE_EXTENDED_2 0x0002 /* Extended reg - page 2 */ | |
d50736a8 RL |
33 | |
34 | /* Extended Page 2 Registers */ | |
4ffd03f5 RL |
35 | #define MSCC_PHY_RGMII_CNTL 20 |
36 | #define RGMII_RX_CLK_DELAY_MASK 0x0070 | |
37 | #define RGMII_RX_CLK_DELAY_POS 4 | |
d50736a8 RL |
38 | |
39 | /* Microsemi PHY ID's */ | |
4ffd03f5 RL |
40 | #define PHY_ID_VSC8531 0x00070570 |
41 | #define PHY_ID_VSC8541 0x00070770 | |
d50736a8 RL |
42 | |
43 | static int vsc85xx_phy_page_set(struct phy_device *phydev, u8 page) | |
44 | { | |
4ffd03f5 | 45 | int rc; |
d50736a8 | 46 | |
4ffd03f5 RL |
47 | rc = phy_write(phydev, MSCC_EXT_PAGE_ACCESS, page); |
48 | return rc; | |
d50736a8 RL |
49 | } |
50 | ||
51 | static int vsc85xx_default_config(struct phy_device *phydev) | |
52 | { | |
4ffd03f5 RL |
53 | int rc; |
54 | u16 reg_val; | |
d50736a8 | 55 | |
4ffd03f5 RL |
56 | mutex_lock(&phydev->lock); |
57 | rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_EXTENDED_2); | |
58 | if (rc != 0) | |
59 | goto out_unlock; | |
d50736a8 | 60 | |
4ffd03f5 RL |
61 | reg_val = phy_read(phydev, MSCC_PHY_RGMII_CNTL); |
62 | reg_val &= ~(RGMII_RX_CLK_DELAY_MASK); | |
63 | reg_val |= (RGMII_RX_CLK_DELAY_1_1_NS << RGMII_RX_CLK_DELAY_POS); | |
64 | phy_write(phydev, MSCC_PHY_RGMII_CNTL, reg_val); | |
65 | rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_STANDARD); | |
d50736a8 RL |
66 | |
67 | out_unlock: | |
4ffd03f5 | 68 | mutex_unlock(&phydev->lock); |
d50736a8 | 69 | |
4ffd03f5 | 70 | return rc; |
d50736a8 RL |
71 | } |
72 | ||
73 | static int vsc85xx_config_init(struct phy_device *phydev) | |
74 | { | |
4ffd03f5 | 75 | int rc; |
d50736a8 | 76 | |
4ffd03f5 RL |
77 | rc = vsc85xx_default_config(phydev); |
78 | if (rc) | |
79 | return rc; | |
80 | rc = genphy_config_init(phydev); | |
d50736a8 | 81 | |
4ffd03f5 | 82 | return rc; |
d50736a8 RL |
83 | } |
84 | ||
85 | static int vsc85xx_ack_interrupt(struct phy_device *phydev) | |
86 | { | |
4ffd03f5 | 87 | int rc = 0; |
d50736a8 | 88 | |
4ffd03f5 RL |
89 | if (phydev->interrupts == PHY_INTERRUPT_ENABLED) |
90 | rc = phy_read(phydev, MII_VSC85XX_INT_STATUS); | |
d50736a8 | 91 | |
4ffd03f5 | 92 | return (rc < 0) ? rc : 0; |
d50736a8 RL |
93 | } |
94 | ||
95 | static int vsc85xx_config_intr(struct phy_device *phydev) | |
96 | { | |
4ffd03f5 RL |
97 | int rc; |
98 | ||
99 | if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { | |
100 | rc = phy_write(phydev, MII_VSC85XX_INT_MASK, | |
101 | MII_VSC85XX_INT_MASK_MASK); | |
102 | } else { | |
103 | rc = phy_write(phydev, MII_VSC85XX_INT_MASK, 0); | |
104 | if (rc < 0) | |
105 | return rc; | |
106 | rc = phy_read(phydev, MII_VSC85XX_INT_STATUS); | |
107 | } | |
108 | ||
109 | return rc; | |
d50736a8 RL |
110 | } |
111 | ||
112 | /* Microsemi VSC85xx PHYs */ | |
113 | static struct phy_driver vsc85xx_driver[] = { | |
114 | { | |
4ffd03f5 RL |
115 | .phy_id = PHY_ID_VSC8531, |
116 | .name = "Microsemi VSC8531", | |
117 | .phy_id_mask = 0xfffffff0, | |
118 | .features = PHY_GBIT_FEATURES, | |
119 | .flags = PHY_HAS_INTERRUPT, | |
120 | .soft_reset = &genphy_soft_reset, | |
121 | .config_init = &vsc85xx_config_init, | |
122 | .config_aneg = &genphy_config_aneg, | |
123 | .aneg_done = &genphy_aneg_done, | |
124 | .read_status = &genphy_read_status, | |
125 | .ack_interrupt = &vsc85xx_ack_interrupt, | |
126 | .config_intr = &vsc85xx_config_intr, | |
127 | .suspend = &genphy_suspend, | |
128 | .resume = &genphy_resume, | |
d50736a8 RL |
129 | }, |
130 | { | |
4ffd03f5 RL |
131 | .phy_id = PHY_ID_VSC8541, |
132 | .name = "Microsemi VSC8541 SyncE", | |
133 | .phy_id_mask = 0xfffffff0, | |
134 | .features = PHY_GBIT_FEATURES, | |
135 | .flags = PHY_HAS_INTERRUPT, | |
136 | .soft_reset = &genphy_soft_reset, | |
137 | .config_init = &vsc85xx_config_init, | |
138 | .config_aneg = &genphy_config_aneg, | |
139 | .aneg_done = &genphy_aneg_done, | |
140 | .read_status = &genphy_read_status, | |
141 | .ack_interrupt = &vsc85xx_ack_interrupt, | |
142 | .config_intr = &vsc85xx_config_intr, | |
143 | .suspend = &genphy_suspend, | |
144 | .resume = &genphy_resume, | |
d50736a8 RL |
145 | } |
146 | ||
147 | }; | |
148 | ||
149 | module_phy_driver(vsc85xx_driver); | |
150 | ||
151 | static struct mdio_device_id __maybe_unused vsc85xx_tbl[] = { | |
4ffd03f5 RL |
152 | { PHY_ID_VSC8531, 0xfffffff0, }, |
153 | { PHY_ID_VSC8541, 0xfffffff0, }, | |
154 | { } | |
d50736a8 RL |
155 | }; |
156 | ||
157 | MODULE_DEVICE_TABLE(mdio, vsc85xx_tbl); | |
158 | ||
159 | MODULE_DESCRIPTION("Microsemi VSC85xx PHY driver"); | |
160 | MODULE_AUTHOR("Nagaraju Lakkaraju"); | |
161 | MODULE_LICENSE("Dual MIT/GPL"); |