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