]>
Commit | Line | Data |
---|---|---|
f524f829 DM |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // IOMapped CAN bus driver for Bosch M_CAN controller | |
3 | // Copyright (C) 2014 Freescale Semiconductor, Inc. | |
4 | // Dong Aisheng <b29396@freescale.com> | |
5 | // | |
6 | // Copyright (C) 2018-19 Texas Instruments Incorporated - http://www.ti.com/ | |
7 | ||
8 | #include <linux/platform_device.h> | |
d836cb5f | 9 | #include <linux/phy/phy.h> |
f524f829 DM |
10 | |
11 | #include "m_can.h" | |
12 | ||
13 | struct m_can_plat_priv { | |
ac33ffd3 MKB |
14 | struct m_can_classdev cdev; |
15 | ||
f524f829 DM |
16 | void __iomem *base; |
17 | void __iomem *mram_base; | |
18 | }; | |
19 | ||
ac33ffd3 MKB |
20 | static inline struct m_can_plat_priv *cdev_to_priv(struct m_can_classdev *cdev) |
21 | { | |
22 | return container_of(cdev, struct m_can_plat_priv, cdev); | |
23 | } | |
24 | ||
441ac340 | 25 | static u32 iomap_read_reg(struct m_can_classdev *cdev, int reg) |
f524f829 | 26 | { |
ac33ffd3 | 27 | struct m_can_plat_priv *priv = cdev_to_priv(cdev); |
f524f829 DM |
28 | |
29 | return readl(priv->base + reg); | |
30 | } | |
31 | ||
e3938177 | 32 | static int iomap_read_fifo(struct m_can_classdev *cdev, int offset, void *val, size_t val_count) |
f524f829 | 33 | { |
ac33ffd3 | 34 | struct m_can_plat_priv *priv = cdev_to_priv(cdev); |
99d173fb | 35 | void __iomem *src = priv->mram_base + offset; |
f524f829 | 36 | |
99d173fb AG |
37 | while (val_count--) { |
38 | *(unsigned int *)val = ioread32(src); | |
39 | val += 4; | |
40 | src += 4; | |
41 | } | |
e3938177 MK |
42 | |
43 | return 0; | |
f524f829 DM |
44 | } |
45 | ||
441ac340 | 46 | static int iomap_write_reg(struct m_can_classdev *cdev, int reg, int val) |
f524f829 | 47 | { |
ac33ffd3 | 48 | struct m_can_plat_priv *priv = cdev_to_priv(cdev); |
f524f829 DM |
49 | |
50 | writel(val, priv->base + reg); | |
51 | ||
52 | return 0; | |
53 | } | |
54 | ||
e3938177 MK |
55 | static int iomap_write_fifo(struct m_can_classdev *cdev, int offset, |
56 | const void *val, size_t val_count) | |
f524f829 | 57 | { |
ac33ffd3 | 58 | struct m_can_plat_priv *priv = cdev_to_priv(cdev); |
99d173fb | 59 | void __iomem *dst = priv->mram_base + offset; |
f524f829 | 60 | |
99d173fb AG |
61 | while (val_count--) { |
62 | iowrite32(*(unsigned int *)val, dst); | |
63 | val += 4; | |
64 | dst += 4; | |
65 | } | |
f524f829 DM |
66 | |
67 | return 0; | |
68 | } | |
69 | ||
70 | static struct m_can_ops m_can_plat_ops = { | |
71 | .read_reg = iomap_read_reg, | |
72 | .write_reg = iomap_write_reg, | |
73 | .write_fifo = iomap_write_fifo, | |
74 | .read_fifo = iomap_read_fifo, | |
75 | }; | |
76 | ||
77 | static int m_can_plat_probe(struct platform_device *pdev) | |
78 | { | |
441ac340 | 79 | struct m_can_classdev *mcan_class; |
f524f829 DM |
80 | struct m_can_plat_priv *priv; |
81 | struct resource *res; | |
82 | void __iomem *addr; | |
83 | void __iomem *mram_addr; | |
d836cb5f | 84 | struct phy *transceiver; |
f524f829 DM |
85 | int irq, ret = 0; |
86 | ||
ac33ffd3 MKB |
87 | mcan_class = m_can_class_allocate_dev(&pdev->dev, |
88 | sizeof(struct m_can_plat_priv)); | |
b3402c40 MKB |
89 | if (!mcan_class) |
90 | return -ENOMEM; | |
91 | ||
ac33ffd3 | 92 | priv = cdev_to_priv(mcan_class); |
f524f829 | 93 | |
85816aba DM |
94 | ret = m_can_class_get_clocks(mcan_class); |
95 | if (ret) | |
96 | goto probe_fail; | |
f524f829 | 97 | |
9808dba1 | 98 | addr = devm_platform_ioremap_resource_byname(pdev, "m_can"); |
f524f829 DM |
99 | irq = platform_get_irq_byname(pdev, "int0"); |
100 | if (IS_ERR(addr) || irq < 0) { | |
101 | ret = -EINVAL; | |
85816aba | 102 | goto probe_fail; |
f524f829 DM |
103 | } |
104 | ||
105 | /* message ram could be shared */ | |
106 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "message_ram"); | |
107 | if (!res) { | |
108 | ret = -ENODEV; | |
85816aba | 109 | goto probe_fail; |
f524f829 DM |
110 | } |
111 | ||
112 | mram_addr = devm_ioremap(&pdev->dev, res->start, resource_size(res)); | |
113 | if (!mram_addr) { | |
114 | ret = -ENOMEM; | |
85816aba | 115 | goto probe_fail; |
f524f829 DM |
116 | } |
117 | ||
d836cb5f FA |
118 | transceiver = devm_phy_optional_get(&pdev->dev, NULL); |
119 | if (IS_ERR(transceiver)) { | |
120 | ret = PTR_ERR(transceiver); | |
121 | dev_err_probe(&pdev->dev, ret, "failed to get phy\n"); | |
122 | goto probe_fail; | |
123 | } | |
124 | ||
125 | if (transceiver) | |
126 | mcan_class->can.bitrate_max = transceiver->attrs.max_link_rate; | |
127 | ||
f524f829 DM |
128 | priv->base = addr; |
129 | priv->mram_base = mram_addr; | |
130 | ||
131 | mcan_class->net->irq = irq; | |
132 | mcan_class->pm_clock_support = 1; | |
133 | mcan_class->can.clock.freq = clk_get_rate(mcan_class->cclk); | |
134 | mcan_class->dev = &pdev->dev; | |
d836cb5f | 135 | mcan_class->transceiver = transceiver; |
f524f829 DM |
136 | |
137 | mcan_class->ops = &m_can_plat_ops; | |
138 | ||
139 | mcan_class->is_peripheral = false; | |
140 | ||
c6b73489 | 141 | platform_set_drvdata(pdev, mcan_class); |
f524f829 | 142 | |
e3938177 MK |
143 | ret = m_can_init_ram(mcan_class); |
144 | if (ret) | |
145 | goto probe_fail; | |
f524f829 | 146 | |
227619c3 PF |
147 | pm_runtime_enable(mcan_class->dev); |
148 | ret = m_can_class_register(mcan_class); | |
149 | if (ret) | |
150 | goto out_runtime_disable; | |
151 | ||
152 | return ret; | |
f524f829 | 153 | |
227619c3 PF |
154 | out_runtime_disable: |
155 | pm_runtime_disable(mcan_class->dev); | |
85816aba DM |
156 | probe_fail: |
157 | m_can_class_free_dev(mcan_class->net); | |
f524f829 DM |
158 | return ret; |
159 | } | |
160 | ||
161 | static __maybe_unused int m_can_suspend(struct device *dev) | |
162 | { | |
163 | return m_can_class_suspend(dev); | |
164 | } | |
165 | ||
166 | static __maybe_unused int m_can_resume(struct device *dev) | |
167 | { | |
168 | return m_can_class_resume(dev); | |
169 | } | |
170 | ||
171 | static int m_can_plat_remove(struct platform_device *pdev) | |
172 | { | |
c6b73489 MKB |
173 | struct m_can_plat_priv *priv = platform_get_drvdata(pdev); |
174 | struct m_can_classdev *mcan_class = &priv->cdev; | |
f524f829 DM |
175 | |
176 | m_can_class_unregister(mcan_class); | |
177 | ||
85816aba DM |
178 | m_can_class_free_dev(mcan_class->net); |
179 | ||
f524f829 DM |
180 | return 0; |
181 | } | |
182 | ||
183 | static int __maybe_unused m_can_runtime_suspend(struct device *dev) | |
184 | { | |
c6b73489 MKB |
185 | struct m_can_plat_priv *priv = dev_get_drvdata(dev); |
186 | struct m_can_classdev *mcan_class = &priv->cdev; | |
f524f829 | 187 | |
f524f829 DM |
188 | clk_disable_unprepare(mcan_class->cclk); |
189 | clk_disable_unprepare(mcan_class->hclk); | |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
194 | static int __maybe_unused m_can_runtime_resume(struct device *dev) | |
195 | { | |
c6b73489 MKB |
196 | struct m_can_plat_priv *priv = dev_get_drvdata(dev); |
197 | struct m_can_classdev *mcan_class = &priv->cdev; | |
f524f829 DM |
198 | int err; |
199 | ||
200 | err = clk_prepare_enable(mcan_class->hclk); | |
201 | if (err) | |
202 | return err; | |
203 | ||
204 | err = clk_prepare_enable(mcan_class->cclk); | |
205 | if (err) | |
206 | clk_disable_unprepare(mcan_class->hclk); | |
207 | ||
f524f829 DM |
208 | return err; |
209 | } | |
210 | ||
211 | static const struct dev_pm_ops m_can_pmops = { | |
212 | SET_RUNTIME_PM_OPS(m_can_runtime_suspend, | |
213 | m_can_runtime_resume, NULL) | |
214 | SET_SYSTEM_SLEEP_PM_OPS(m_can_suspend, m_can_resume) | |
215 | }; | |
216 | ||
217 | static const struct of_device_id m_can_of_table[] = { | |
218 | { .compatible = "bosch,m_can", .data = NULL }, | |
219 | { /* sentinel */ }, | |
220 | }; | |
221 | MODULE_DEVICE_TABLE(of, m_can_of_table); | |
222 | ||
223 | static struct platform_driver m_can_plat_driver = { | |
224 | .driver = { | |
225 | .name = KBUILD_MODNAME, | |
226 | .of_match_table = m_can_of_table, | |
227 | .pm = &m_can_pmops, | |
228 | }, | |
229 | .probe = m_can_plat_probe, | |
230 | .remove = m_can_plat_remove, | |
231 | }; | |
232 | ||
233 | module_platform_driver(m_can_plat_driver); | |
234 | ||
235 | MODULE_AUTHOR("Dong Aisheng <b29396@freescale.com>"); | |
236 | MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>"); | |
237 | MODULE_LICENSE("GPL v2"); | |
238 | MODULE_DESCRIPTION("M_CAN driver for IO Mapped Bosch controllers"); |