1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Microchip KSZ8863 series register access through SMI |
4 | * |
5 | * Copyright (C) 2019 Pengutronix, Michael Grzeschik <kernel@pengutronix.de> |
6 | */ |
7 | |
8 | #include <linux/mod_devicetable.h> |
9 | #include <linux/property.h> |
10 | |
11 | #include "ksz8.h" |
12 | #include "ksz_common.h" |
13 | |
14 | /* Serial Management Interface (SMI) uses the following frame format: |
15 | * |
16 | * preamble|start|Read/Write| PHY | REG |TA| Data bits | Idle |
17 | * |frame| OP code |address |address| | | |
18 | * read | 32x1´s | 01 | 00 | 1xRRR | RRRRR |Z0| 00000000DDDDDDDD | Z |
19 | * write| 32x1´s | 01 | 00 | 0xRRR | RRRRR |10| xxxxxxxxDDDDDDDD | Z |
20 | * |
21 | */ |
22 | |
23 | #define SMI_KSZ88XX_READ_PHY BIT(4) |
24 | |
25 | static int ksz8863_mdio_read(void *ctx, const void *reg_buf, size_t reg_len, |
26 | void *val_buf, size_t val_len) |
27 | { |
28 | struct ksz_device *dev = ctx; |
29 | struct mdio_device *mdev; |
30 | u8 reg = *(u8 *)reg_buf; |
31 | u8 *val = val_buf; |
32 | int i, ret = 0; |
33 | |
34 | mdev = dev->priv; |
35 | |
36 | mutex_lock_nested(lock: &mdev->bus->mdio_lock, subclass: MDIO_MUTEX_NESTED); |
37 | for (i = 0; i < val_len; i++) { |
38 | int tmp = reg + i; |
39 | |
40 | ret = __mdiobus_read(bus: mdev->bus, addr: ((tmp & 0xE0) >> 5) | |
41 | SMI_KSZ88XX_READ_PHY, regnum: tmp); |
42 | if (ret < 0) |
43 | goto out; |
44 | |
45 | val[i] = ret; |
46 | } |
47 | ret = 0; |
48 | |
49 | out: |
50 | mutex_unlock(lock: &mdev->bus->mdio_lock); |
51 | |
52 | return ret; |
53 | } |
54 | |
55 | static int ksz8863_mdio_write(void *ctx, const void *data, size_t count) |
56 | { |
57 | struct ksz_device *dev = ctx; |
58 | struct mdio_device *mdev; |
59 | int i, ret = 0; |
60 | u32 reg; |
61 | u8 *val; |
62 | |
63 | mdev = dev->priv; |
64 | |
65 | val = (u8 *)(data + 4); |
66 | reg = *(u32 *)data; |
67 | |
68 | mutex_lock_nested(lock: &mdev->bus->mdio_lock, subclass: MDIO_MUTEX_NESTED); |
69 | for (i = 0; i < (count - 4); i++) { |
70 | int tmp = reg + i; |
71 | |
72 | ret = __mdiobus_write(bus: mdev->bus, addr: ((tmp & 0xE0) >> 5), |
73 | regnum: tmp, val: val[i]); |
74 | if (ret < 0) |
75 | goto out; |
76 | } |
77 | |
78 | out: |
79 | mutex_unlock(lock: &mdev->bus->mdio_lock); |
80 | |
81 | return ret; |
82 | } |
83 | |
84 | static const struct regmap_bus regmap_smi[] = { |
85 | { |
86 | .read = ksz8863_mdio_read, |
87 | .write = ksz8863_mdio_write, |
88 | }, |
89 | { |
90 | .read = ksz8863_mdio_read, |
91 | .write = ksz8863_mdio_write, |
92 | .val_format_endian_default = REGMAP_ENDIAN_BIG, |
93 | }, |
94 | { |
95 | .read = ksz8863_mdio_read, |
96 | .write = ksz8863_mdio_write, |
97 | .val_format_endian_default = REGMAP_ENDIAN_BIG, |
98 | } |
99 | }; |
100 | |
101 | static const struct regmap_config ksz8863_regmap_config[] = { |
102 | { |
103 | .name = "#8" , |
104 | .reg_bits = 8, |
105 | .pad_bits = 24, |
106 | .val_bits = 8, |
107 | .cache_type = REGCACHE_NONE, |
108 | .lock = ksz_regmap_lock, |
109 | .unlock = ksz_regmap_unlock, |
110 | .max_register = U8_MAX, |
111 | }, |
112 | { |
113 | .name = "#16" , |
114 | .reg_bits = 8, |
115 | .pad_bits = 24, |
116 | .val_bits = 16, |
117 | .cache_type = REGCACHE_NONE, |
118 | .lock = ksz_regmap_lock, |
119 | .unlock = ksz_regmap_unlock, |
120 | .max_register = U8_MAX, |
121 | }, |
122 | { |
123 | .name = "#32" , |
124 | .reg_bits = 8, |
125 | .pad_bits = 24, |
126 | .val_bits = 32, |
127 | .cache_type = REGCACHE_NONE, |
128 | .lock = ksz_regmap_lock, |
129 | .unlock = ksz_regmap_unlock, |
130 | .max_register = U8_MAX, |
131 | } |
132 | }; |
133 | |
134 | static int ksz8863_smi_probe(struct mdio_device *mdiodev) |
135 | { |
136 | struct device *ddev = &mdiodev->dev; |
137 | const struct ksz_chip_data *chip; |
138 | struct regmap_config rc; |
139 | struct ksz_device *dev; |
140 | int ret; |
141 | int i; |
142 | |
143 | dev = ksz_switch_alloc(base: &mdiodev->dev, priv: mdiodev); |
144 | if (!dev) |
145 | return -ENOMEM; |
146 | |
147 | chip = device_get_match_data(dev: ddev); |
148 | if (!chip) |
149 | return -EINVAL; |
150 | |
151 | for (i = 0; i < __KSZ_NUM_REGMAPS; i++) { |
152 | rc = ksz8863_regmap_config[i]; |
153 | rc.lock_arg = &dev->regmap_mutex; |
154 | rc.wr_table = chip->wr_table; |
155 | rc.rd_table = chip->rd_table; |
156 | dev->regmap[i] = devm_regmap_init(&mdiodev->dev, |
157 | ®map_smi[i], dev, |
158 | &rc); |
159 | if (IS_ERR(ptr: dev->regmap[i])) { |
160 | return dev_err_probe(dev: &mdiodev->dev, |
161 | err: PTR_ERR(ptr: dev->regmap[i]), |
162 | fmt: "Failed to initialize regmap%i\n" , |
163 | ksz8863_regmap_config[i].val_bits); |
164 | } |
165 | } |
166 | |
167 | if (mdiodev->dev.platform_data) |
168 | dev->pdata = mdiodev->dev.platform_data; |
169 | |
170 | ret = ksz_switch_register(dev); |
171 | |
172 | /* Main DSA driver may not be started yet. */ |
173 | if (ret) |
174 | return ret; |
175 | |
176 | dev_set_drvdata(dev: &mdiodev->dev, data: dev); |
177 | |
178 | return 0; |
179 | } |
180 | |
181 | static void ksz8863_smi_remove(struct mdio_device *mdiodev) |
182 | { |
183 | struct ksz_device *dev = dev_get_drvdata(dev: &mdiodev->dev); |
184 | |
185 | if (dev) |
186 | ksz_switch_remove(dev); |
187 | } |
188 | |
189 | static void ksz8863_smi_shutdown(struct mdio_device *mdiodev) |
190 | { |
191 | struct ksz_device *dev = dev_get_drvdata(dev: &mdiodev->dev); |
192 | |
193 | if (dev) |
194 | dsa_switch_shutdown(ds: dev->ds); |
195 | |
196 | dev_set_drvdata(dev: &mdiodev->dev, NULL); |
197 | } |
198 | |
199 | static const struct of_device_id ksz8863_dt_ids[] = { |
200 | { |
201 | .compatible = "microchip,ksz8863" , |
202 | .data = &ksz_switch_chips[KSZ8830] |
203 | }, |
204 | { |
205 | .compatible = "microchip,ksz8873" , |
206 | .data = &ksz_switch_chips[KSZ8830] |
207 | }, |
208 | { }, |
209 | }; |
210 | MODULE_DEVICE_TABLE(of, ksz8863_dt_ids); |
211 | |
212 | static struct mdio_driver ksz8863_driver = { |
213 | .probe = ksz8863_smi_probe, |
214 | .remove = ksz8863_smi_remove, |
215 | .shutdown = ksz8863_smi_shutdown, |
216 | .mdiodrv.driver = { |
217 | .name = "ksz8863-switch" , |
218 | .of_match_table = ksz8863_dt_ids, |
219 | }, |
220 | }; |
221 | |
222 | mdio_module_driver(ksz8863_driver); |
223 | |
224 | MODULE_AUTHOR("Michael Grzeschik <m.grzeschik@pengutronix.de>" ); |
225 | MODULE_DESCRIPTION("Microchip KSZ8863 SMI Switch driver" ); |
226 | MODULE_LICENSE("GPL v2" ); |
227 | |