1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2017 Pengutronix, Juergen Borleis <kernel@pengutronix.de> |
4 | * |
5 | * Partially based on a patch from |
6 | * Copyright (c) 2014 Stefan Roese <sr@denx.de> |
7 | */ |
8 | #include <linux/kernel.h> |
9 | #include <linux/module.h> |
10 | #include <linux/mdio.h> |
11 | #include <linux/phy.h> |
12 | #include <linux/of.h> |
13 | |
14 | #include "lan9303.h" |
15 | |
16 | /* Generate phy-addr and -reg from the input address */ |
17 | #define PHY_ADDR(x) ((((x) >> 6) + 0x10) & 0x1f) |
18 | #define PHY_REG(x) (((x) >> 1) & 0x1f) |
19 | |
20 | struct lan9303_mdio { |
21 | struct mdio_device *device; |
22 | struct lan9303 chip; |
23 | }; |
24 | |
25 | static void lan9303_mdio_real_write(struct mdio_device *mdio, int reg, u16 val) |
26 | { |
27 | mdio->bus->write(mdio->bus, PHY_ADDR(reg), PHY_REG(reg), val); |
28 | } |
29 | |
30 | static int lan9303_mdio_write(void *ctx, uint32_t reg, uint32_t val) |
31 | { |
32 | struct lan9303_mdio *sw_dev = (struct lan9303_mdio *)ctx; |
33 | |
34 | reg <<= 2; /* reg num to offset */ |
35 | mutex_lock(&sw_dev->device->bus->mdio_lock); |
36 | lan9303_mdio_real_write(mdio: sw_dev->device, reg, val: val & 0xffff); |
37 | lan9303_mdio_real_write(mdio: sw_dev->device, reg: reg + 2, val: (val >> 16) & 0xffff); |
38 | mutex_unlock(lock: &sw_dev->device->bus->mdio_lock); |
39 | |
40 | return 0; |
41 | } |
42 | |
43 | static u16 lan9303_mdio_real_read(struct mdio_device *mdio, int reg) |
44 | { |
45 | return mdio->bus->read(mdio->bus, PHY_ADDR(reg), PHY_REG(reg)); |
46 | } |
47 | |
48 | static int lan9303_mdio_read(void *ctx, uint32_t reg, uint32_t *val) |
49 | { |
50 | struct lan9303_mdio *sw_dev = (struct lan9303_mdio *)ctx; |
51 | |
52 | reg <<= 2; /* reg num to offset */ |
53 | mutex_lock(&sw_dev->device->bus->mdio_lock); |
54 | *val = lan9303_mdio_real_read(mdio: sw_dev->device, reg); |
55 | *val |= (lan9303_mdio_real_read(mdio: sw_dev->device, reg: reg + 2) << 16); |
56 | mutex_unlock(lock: &sw_dev->device->bus->mdio_lock); |
57 | |
58 | return 0; |
59 | } |
60 | |
61 | static int lan9303_mdio_phy_write(struct lan9303 *chip, int phy, int reg, |
62 | u16 val) |
63 | { |
64 | struct lan9303_mdio *sw_dev = dev_get_drvdata(dev: chip->dev); |
65 | |
66 | return mdiobus_write_nested(bus: sw_dev->device->bus, addr: phy, regnum: reg, val); |
67 | } |
68 | |
69 | static int lan9303_mdio_phy_read(struct lan9303 *chip, int phy, int reg) |
70 | { |
71 | struct lan9303_mdio *sw_dev = dev_get_drvdata(dev: chip->dev); |
72 | |
73 | return mdiobus_read_nested(bus: sw_dev->device->bus, addr: phy, regnum: reg); |
74 | } |
75 | |
76 | static const struct lan9303_phy_ops lan9303_mdio_phy_ops = { |
77 | .phy_read = lan9303_mdio_phy_read, |
78 | .phy_write = lan9303_mdio_phy_write, |
79 | }; |
80 | |
81 | static const struct regmap_config lan9303_mdio_regmap_config = { |
82 | .reg_bits = 8, |
83 | .val_bits = 32, |
84 | .reg_stride = 1, |
85 | .can_multi_write = true, |
86 | .max_register = 0x0ff, /* address bits 0..1 are not used */ |
87 | .reg_format_endian = REGMAP_ENDIAN_LITTLE, |
88 | |
89 | .volatile_table = &lan9303_register_set, |
90 | .wr_table = &lan9303_register_set, |
91 | .rd_table = &lan9303_register_set, |
92 | |
93 | .reg_read = lan9303_mdio_read, |
94 | .reg_write = lan9303_mdio_write, |
95 | |
96 | .cache_type = REGCACHE_NONE, |
97 | }; |
98 | |
99 | static int lan9303_mdio_probe(struct mdio_device *mdiodev) |
100 | { |
101 | struct lan9303_mdio *sw_dev; |
102 | int ret; |
103 | |
104 | sw_dev = devm_kzalloc(dev: &mdiodev->dev, size: sizeof(struct lan9303_mdio), |
105 | GFP_KERNEL); |
106 | if (!sw_dev) |
107 | return -ENOMEM; |
108 | |
109 | sw_dev->chip.regmap = devm_regmap_init(&mdiodev->dev, NULL, sw_dev, |
110 | &lan9303_mdio_regmap_config); |
111 | if (IS_ERR(ptr: sw_dev->chip.regmap)) { |
112 | ret = PTR_ERR(ptr: sw_dev->chip.regmap); |
113 | dev_err(&mdiodev->dev, "regmap init failed: %d\n" , ret); |
114 | return ret; |
115 | } |
116 | |
117 | /* link forward and backward */ |
118 | sw_dev->device = mdiodev; |
119 | dev_set_drvdata(dev: &mdiodev->dev, data: sw_dev); |
120 | sw_dev->chip.dev = &mdiodev->dev; |
121 | |
122 | sw_dev->chip.ops = &lan9303_mdio_phy_ops; |
123 | |
124 | ret = lan9303_probe(chip: &sw_dev->chip, np: mdiodev->dev.of_node); |
125 | if (ret != 0) |
126 | return ret; |
127 | |
128 | dev_info(&mdiodev->dev, "LAN9303 MDIO driver loaded successfully\n" ); |
129 | |
130 | return 0; |
131 | } |
132 | |
133 | static void lan9303_mdio_remove(struct mdio_device *mdiodev) |
134 | { |
135 | struct lan9303_mdio *sw_dev = dev_get_drvdata(dev: &mdiodev->dev); |
136 | |
137 | if (!sw_dev) |
138 | return; |
139 | |
140 | lan9303_remove(chip: &sw_dev->chip); |
141 | } |
142 | |
143 | static void lan9303_mdio_shutdown(struct mdio_device *mdiodev) |
144 | { |
145 | struct lan9303_mdio *sw_dev = dev_get_drvdata(dev: &mdiodev->dev); |
146 | |
147 | if (!sw_dev) |
148 | return; |
149 | |
150 | lan9303_shutdown(chip: &sw_dev->chip); |
151 | |
152 | dev_set_drvdata(dev: &mdiodev->dev, NULL); |
153 | } |
154 | |
155 | /*-------------------------------------------------------------------------*/ |
156 | |
157 | static const struct of_device_id lan9303_mdio_of_match[] = { |
158 | { .compatible = "smsc,lan9303-mdio" }, |
159 | { .compatible = "microchip,lan9354-mdio" }, |
160 | { /* sentinel */ }, |
161 | }; |
162 | MODULE_DEVICE_TABLE(of, lan9303_mdio_of_match); |
163 | |
164 | static struct mdio_driver lan9303_mdio_driver = { |
165 | .mdiodrv.driver = { |
166 | .name = "LAN9303_MDIO" , |
167 | .of_match_table = lan9303_mdio_of_match, |
168 | }, |
169 | .probe = lan9303_mdio_probe, |
170 | .remove = lan9303_mdio_remove, |
171 | .shutdown = lan9303_mdio_shutdown, |
172 | }; |
173 | mdio_module_driver(lan9303_mdio_driver); |
174 | |
175 | MODULE_AUTHOR("Stefan Roese <sr@denx.de>, Juergen Borleis <kernel@pengutronix.de>" ); |
176 | MODULE_DESCRIPTION("Driver for SMSC/Microchip LAN9303 three port ethernet switch in MDIO managed mode" ); |
177 | MODULE_LICENSE("GPL v2" ); |
178 | |