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