1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* DSA driver for: |
3 | * Vitesse VSC7385 SparX-G5 5+1-port Integrated Gigabit Ethernet Switch |
4 | * Vitesse VSC7388 SparX-G8 8-port Integrated Gigabit Ethernet Switch |
5 | * Vitesse VSC7395 SparX-G5e 5+1-port Integrated Gigabit Ethernet Switch |
6 | * Vitesse VSC7398 SparX-G8e 8-port Integrated Gigabit Ethernet Switch |
7 | * |
8 | * This driver takes control of the switch chip connected over CPU-attached |
9 | * address bus and configures it to route packages around when connected to |
10 | * a CPU port. |
11 | * |
12 | * Copyright (C) 2019 Pawel Dembicki <paweldembicki@gmail.com> |
13 | * Based on vitesse-vsc-spi.c by: |
14 | * Copyright (C) 2018 Linus Wallej <linus.walleij@linaro.org> |
15 | * Includes portions of code from the firmware uploader by: |
16 | * Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org> |
17 | */ |
18 | #include <linux/kernel.h> |
19 | #include <linux/module.h> |
20 | #include <linux/of.h> |
21 | #include <linux/platform_device.h> |
22 | |
23 | #include "vitesse-vsc73xx.h" |
24 | |
25 | #define VSC73XX_CMD_PLATFORM_BLOCK_SHIFT 14 |
26 | #define VSC73XX_CMD_PLATFORM_BLOCK_MASK 0x7 |
27 | #define VSC73XX_CMD_PLATFORM_SUBBLOCK_SHIFT 10 |
28 | #define VSC73XX_CMD_PLATFORM_SUBBLOCK_MASK 0xf |
29 | #define VSC73XX_CMD_PLATFORM_REGISTER_SHIFT 2 |
30 | |
31 | /* |
32 | * struct vsc73xx_platform - VSC73xx Platform state container |
33 | */ |
34 | struct vsc73xx_platform { |
35 | struct platform_device *pdev; |
36 | void __iomem *base_addr; |
37 | struct vsc73xx vsc; |
38 | }; |
39 | |
40 | static const struct vsc73xx_ops vsc73xx_platform_ops; |
41 | |
42 | static u32 vsc73xx_make_addr(u8 block, u8 subblock, u8 reg) |
43 | { |
44 | u32 ret; |
45 | |
46 | ret = (block & VSC73XX_CMD_PLATFORM_BLOCK_MASK) |
47 | << VSC73XX_CMD_PLATFORM_BLOCK_SHIFT; |
48 | ret |= (subblock & VSC73XX_CMD_PLATFORM_SUBBLOCK_MASK) |
49 | << VSC73XX_CMD_PLATFORM_SUBBLOCK_SHIFT; |
50 | ret |= reg << VSC73XX_CMD_PLATFORM_REGISTER_SHIFT; |
51 | |
52 | return ret; |
53 | } |
54 | |
55 | static int vsc73xx_platform_read(struct vsc73xx *vsc, u8 block, u8 subblock, |
56 | u8 reg, u32 *val) |
57 | { |
58 | struct vsc73xx_platform *vsc_platform = vsc->priv; |
59 | u32 offset; |
60 | |
61 | if (!vsc73xx_is_addr_valid(block, subblock)) |
62 | return -EINVAL; |
63 | |
64 | offset = vsc73xx_make_addr(block, subblock, reg); |
65 | /* By default vsc73xx running in big-endian mode. |
66 | * (See "Register Addressing" section 5.5.3 in the VSC7385 manual.) |
67 | */ |
68 | *val = ioread32be(vsc_platform->base_addr + offset); |
69 | |
70 | return 0; |
71 | } |
72 | |
73 | static int vsc73xx_platform_write(struct vsc73xx *vsc, u8 block, u8 subblock, |
74 | u8 reg, u32 val) |
75 | { |
76 | struct vsc73xx_platform *vsc_platform = vsc->priv; |
77 | u32 offset; |
78 | |
79 | if (!vsc73xx_is_addr_valid(block, subblock)) |
80 | return -EINVAL; |
81 | |
82 | offset = vsc73xx_make_addr(block, subblock, reg); |
83 | iowrite32be(val, vsc_platform->base_addr + offset); |
84 | |
85 | return 0; |
86 | } |
87 | |
88 | static int vsc73xx_platform_probe(struct platform_device *pdev) |
89 | { |
90 | struct device *dev = &pdev->dev; |
91 | struct vsc73xx_platform *vsc_platform; |
92 | int ret; |
93 | |
94 | vsc_platform = devm_kzalloc(dev, size: sizeof(*vsc_platform), GFP_KERNEL); |
95 | if (!vsc_platform) |
96 | return -ENOMEM; |
97 | |
98 | platform_set_drvdata(pdev, data: vsc_platform); |
99 | vsc_platform->pdev = pdev; |
100 | vsc_platform->vsc.dev = dev; |
101 | vsc_platform->vsc.priv = vsc_platform; |
102 | vsc_platform->vsc.ops = &vsc73xx_platform_ops; |
103 | |
104 | /* obtain I/O memory space */ |
105 | vsc_platform->base_addr = devm_platform_ioremap_resource(pdev, index: 0); |
106 | if (IS_ERR(ptr: vsc_platform->base_addr)) { |
107 | dev_err(&pdev->dev, "cannot request I/O memory space\n" ); |
108 | ret = -ENXIO; |
109 | return ret; |
110 | } |
111 | |
112 | return vsc73xx_probe(vsc: &vsc_platform->vsc); |
113 | } |
114 | |
115 | static void vsc73xx_platform_remove(struct platform_device *pdev) |
116 | { |
117 | struct vsc73xx_platform *vsc_platform = platform_get_drvdata(pdev); |
118 | |
119 | if (!vsc_platform) |
120 | return; |
121 | |
122 | vsc73xx_remove(vsc: &vsc_platform->vsc); |
123 | } |
124 | |
125 | static void vsc73xx_platform_shutdown(struct platform_device *pdev) |
126 | { |
127 | struct vsc73xx_platform *vsc_platform = platform_get_drvdata(pdev); |
128 | |
129 | if (!vsc_platform) |
130 | return; |
131 | |
132 | vsc73xx_shutdown(vsc: &vsc_platform->vsc); |
133 | |
134 | platform_set_drvdata(pdev, NULL); |
135 | } |
136 | |
137 | static const struct vsc73xx_ops vsc73xx_platform_ops = { |
138 | .read = vsc73xx_platform_read, |
139 | .write = vsc73xx_platform_write, |
140 | }; |
141 | |
142 | static const struct of_device_id vsc73xx_of_match[] = { |
143 | { |
144 | .compatible = "vitesse,vsc7385" , |
145 | }, |
146 | { |
147 | .compatible = "vitesse,vsc7388" , |
148 | }, |
149 | { |
150 | .compatible = "vitesse,vsc7395" , |
151 | }, |
152 | { |
153 | .compatible = "vitesse,vsc7398" , |
154 | }, |
155 | { }, |
156 | }; |
157 | MODULE_DEVICE_TABLE(of, vsc73xx_of_match); |
158 | |
159 | static struct platform_driver vsc73xx_platform_driver = { |
160 | .probe = vsc73xx_platform_probe, |
161 | .remove_new = vsc73xx_platform_remove, |
162 | .shutdown = vsc73xx_platform_shutdown, |
163 | .driver = { |
164 | .name = "vsc73xx-platform" , |
165 | .of_match_table = vsc73xx_of_match, |
166 | }, |
167 | }; |
168 | module_platform_driver(vsc73xx_platform_driver); |
169 | |
170 | MODULE_AUTHOR("Pawel Dembicki <paweldembicki@gmail.com>" ); |
171 | MODULE_DESCRIPTION("Vitesse VSC7385/7388/7395/7398 Platform driver" ); |
172 | MODULE_LICENSE("GPL v2" ); |
173 | |