1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * linux/drivers/mfd/mcp-core.c |
4 | * |
5 | * Copyright (C) 2001 Russell King |
6 | * |
7 | * Generic MCP (Multimedia Communications Port) layer. All MCP locking |
8 | * is solely held within this file. |
9 | */ |
10 | #include <linux/module.h> |
11 | #include <linux/init.h> |
12 | #include <linux/errno.h> |
13 | #include <linux/smp.h> |
14 | #include <linux/device.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/string.h> |
17 | #include <linux/mfd/mcp.h> |
18 | |
19 | |
20 | #define to_mcp(d) container_of(d, struct mcp, attached_device) |
21 | #define to_mcp_driver(d) container_of(d, struct mcp_driver, drv) |
22 | |
23 | static int mcp_bus_match(struct device *dev, struct device_driver *drv) |
24 | { |
25 | return 1; |
26 | } |
27 | |
28 | static int mcp_bus_probe(struct device *dev) |
29 | { |
30 | struct mcp *mcp = to_mcp(dev); |
31 | struct mcp_driver *drv = to_mcp_driver(dev->driver); |
32 | |
33 | return drv->probe(mcp); |
34 | } |
35 | |
36 | static void mcp_bus_remove(struct device *dev) |
37 | { |
38 | struct mcp *mcp = to_mcp(dev); |
39 | struct mcp_driver *drv = to_mcp_driver(dev->driver); |
40 | |
41 | drv->remove(mcp); |
42 | } |
43 | |
44 | static struct bus_type mcp_bus_type = { |
45 | .name = "mcp" , |
46 | .match = mcp_bus_match, |
47 | .probe = mcp_bus_probe, |
48 | .remove = mcp_bus_remove, |
49 | }; |
50 | |
51 | /** |
52 | * mcp_set_telecom_divisor - set the telecom divisor |
53 | * @mcp: MCP interface structure |
54 | * @div: SIB clock divisor |
55 | * |
56 | * Set the telecom divisor on the MCP interface. The resulting |
57 | * sample rate is SIBCLOCK/div. |
58 | */ |
59 | void mcp_set_telecom_divisor(struct mcp *mcp, unsigned int div) |
60 | { |
61 | unsigned long flags; |
62 | |
63 | spin_lock_irqsave(&mcp->lock, flags); |
64 | mcp->ops->set_telecom_divisor(mcp, div); |
65 | spin_unlock_irqrestore(lock: &mcp->lock, flags); |
66 | } |
67 | EXPORT_SYMBOL(mcp_set_telecom_divisor); |
68 | |
69 | /** |
70 | * mcp_set_audio_divisor - set the audio divisor |
71 | * @mcp: MCP interface structure |
72 | * @div: SIB clock divisor |
73 | * |
74 | * Set the audio divisor on the MCP interface. |
75 | */ |
76 | void mcp_set_audio_divisor(struct mcp *mcp, unsigned int div) |
77 | { |
78 | unsigned long flags; |
79 | |
80 | spin_lock_irqsave(&mcp->lock, flags); |
81 | mcp->ops->set_audio_divisor(mcp, div); |
82 | spin_unlock_irqrestore(lock: &mcp->lock, flags); |
83 | } |
84 | EXPORT_SYMBOL(mcp_set_audio_divisor); |
85 | |
86 | /** |
87 | * mcp_reg_write - write a device register |
88 | * @mcp: MCP interface structure |
89 | * @reg: 4-bit register index |
90 | * @val: 16-bit data value |
91 | * |
92 | * Write a device register. The MCP interface must be enabled |
93 | * to prevent this function hanging. |
94 | */ |
95 | void mcp_reg_write(struct mcp *mcp, unsigned int reg, unsigned int val) |
96 | { |
97 | unsigned long flags; |
98 | |
99 | spin_lock_irqsave(&mcp->lock, flags); |
100 | mcp->ops->reg_write(mcp, reg, val); |
101 | spin_unlock_irqrestore(lock: &mcp->lock, flags); |
102 | } |
103 | EXPORT_SYMBOL(mcp_reg_write); |
104 | |
105 | /** |
106 | * mcp_reg_read - read a device register |
107 | * @mcp: MCP interface structure |
108 | * @reg: 4-bit register index |
109 | * |
110 | * Read a device register and return its value. The MCP interface |
111 | * must be enabled to prevent this function hanging. |
112 | */ |
113 | unsigned int mcp_reg_read(struct mcp *mcp, unsigned int reg) |
114 | { |
115 | unsigned long flags; |
116 | unsigned int val; |
117 | |
118 | spin_lock_irqsave(&mcp->lock, flags); |
119 | val = mcp->ops->reg_read(mcp, reg); |
120 | spin_unlock_irqrestore(lock: &mcp->lock, flags); |
121 | |
122 | return val; |
123 | } |
124 | EXPORT_SYMBOL(mcp_reg_read); |
125 | |
126 | /** |
127 | * mcp_enable - enable the MCP interface |
128 | * @mcp: MCP interface to enable |
129 | * |
130 | * Enable the MCP interface. Each call to mcp_enable will need |
131 | * a corresponding call to mcp_disable to disable the interface. |
132 | */ |
133 | void mcp_enable(struct mcp *mcp) |
134 | { |
135 | unsigned long flags; |
136 | |
137 | spin_lock_irqsave(&mcp->lock, flags); |
138 | if (mcp->use_count++ == 0) |
139 | mcp->ops->enable(mcp); |
140 | spin_unlock_irqrestore(lock: &mcp->lock, flags); |
141 | } |
142 | EXPORT_SYMBOL(mcp_enable); |
143 | |
144 | /** |
145 | * mcp_disable - disable the MCP interface |
146 | * @mcp: MCP interface to disable |
147 | * |
148 | * Disable the MCP interface. The MCP interface will only be |
149 | * disabled once the number of calls to mcp_enable matches the |
150 | * number of calls to mcp_disable. |
151 | */ |
152 | void mcp_disable(struct mcp *mcp) |
153 | { |
154 | unsigned long flags; |
155 | |
156 | spin_lock_irqsave(&mcp->lock, flags); |
157 | if (--mcp->use_count == 0) |
158 | mcp->ops->disable(mcp); |
159 | spin_unlock_irqrestore(lock: &mcp->lock, flags); |
160 | } |
161 | EXPORT_SYMBOL(mcp_disable); |
162 | |
163 | static void mcp_release(struct device *dev) |
164 | { |
165 | struct mcp *mcp = container_of(dev, struct mcp, attached_device); |
166 | |
167 | kfree(objp: mcp); |
168 | } |
169 | |
170 | struct mcp *mcp_host_alloc(struct device *parent, size_t size) |
171 | { |
172 | struct mcp *mcp; |
173 | |
174 | mcp = kzalloc(size: sizeof(struct mcp) + size, GFP_KERNEL); |
175 | if (mcp) { |
176 | spin_lock_init(&mcp->lock); |
177 | device_initialize(dev: &mcp->attached_device); |
178 | mcp->attached_device.parent = parent; |
179 | mcp->attached_device.bus = &mcp_bus_type; |
180 | mcp->attached_device.dma_mask = parent->dma_mask; |
181 | mcp->attached_device.release = mcp_release; |
182 | } |
183 | return mcp; |
184 | } |
185 | EXPORT_SYMBOL(mcp_host_alloc); |
186 | |
187 | int mcp_host_add(struct mcp *mcp, void *pdata) |
188 | { |
189 | mcp->attached_device.platform_data = pdata; |
190 | dev_set_name(dev: &mcp->attached_device, name: "mcp0" ); |
191 | return device_add(dev: &mcp->attached_device); |
192 | } |
193 | EXPORT_SYMBOL(mcp_host_add); |
194 | |
195 | void mcp_host_del(struct mcp *mcp) |
196 | { |
197 | device_del(dev: &mcp->attached_device); |
198 | } |
199 | EXPORT_SYMBOL(mcp_host_del); |
200 | |
201 | void mcp_host_free(struct mcp *mcp) |
202 | { |
203 | put_device(dev: &mcp->attached_device); |
204 | } |
205 | EXPORT_SYMBOL(mcp_host_free); |
206 | |
207 | int mcp_driver_register(struct mcp_driver *mcpdrv) |
208 | { |
209 | mcpdrv->drv.bus = &mcp_bus_type; |
210 | return driver_register(drv: &mcpdrv->drv); |
211 | } |
212 | EXPORT_SYMBOL(mcp_driver_register); |
213 | |
214 | void mcp_driver_unregister(struct mcp_driver *mcpdrv) |
215 | { |
216 | driver_unregister(drv: &mcpdrv->drv); |
217 | } |
218 | EXPORT_SYMBOL(mcp_driver_unregister); |
219 | |
220 | static int __init mcp_init(void) |
221 | { |
222 | return bus_register(bus: &mcp_bus_type); |
223 | } |
224 | |
225 | static void __exit mcp_exit(void) |
226 | { |
227 | bus_unregister(bus: &mcp_bus_type); |
228 | } |
229 | |
230 | module_init(mcp_init); |
231 | module_exit(mcp_exit); |
232 | |
233 | MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>" ); |
234 | MODULE_DESCRIPTION("Core multimedia communications port driver" ); |
235 | MODULE_LICENSE("GPL" ); |
236 | |