summaryrefslogtreecommitdiff
path: root/arch/arm/plat-omap/hwspinlock.c
blob: 7cfe87747501c17e34583f352b168407b49e8cb6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
/*
 * OMAP hardware spinlock driver
 *
 * Copyright (C) 2010 Texas Instruments. All rights reserved.
 *
 * Contact: Simon Que <sque@ti.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 * This driver supports:
 * - Reserved spinlocks for internal use
 * - Dynamic allocation of unreserved locks
 * - Lock, unlock, and trylock functions, with or without disabling irqs/preempt
 * - Registered as a platform device driver
 *
 * The device initialization uses hwmod to configure the devices.  One device
 * will be created for each IP.  It will pass spinlock register offset info to
 * the driver.  The device initialization file is:
 *          arch/arm/mach-omap2/hwspinlocks.c
 *
 * The driver takes in register offset info passed in device initialization.
 * It uses hwmod to obtain the base address of the hardware spinlock module.
 * Then it reads info from the registers.  The function hwspinlock_probe()
 * initializes the array of spinlock structures, each containing a spinlock
 * register address calculated from the base address and lock offsets.
 *
 * Here's an API summary:
 *
 * int hwspinlock_lock(struct hwspinlock *);
 *      Attempt to lock a hardware spinlock.  If it is busy, the function will
 *      keep trying until it succeeds.  This is a blocking function.
 * int hwspinlock_trylock(struct hwspinlock *);
 *      Attempt to lock a hardware spinlock.  If it is busy, the function will
 *      return BUSY.  If it succeeds in locking, the function will return
 *      ACQUIRED.  This is a non-blocking function
 * int hwspinlock_unlock(struct hwspinlock *);
 *      Unlock a hardware spinlock.
 *
 * struct hwspinlock *hwspinlock_request(void);
 *      Provides for "dynamic allocation" of a hardware spinlock.  It returns
 *      the handle to the next available (unallocated) spinlock.  If no more
 *      locks are available, it returns NULL.
 * struct hwspinlock *hwspinlock_request_specific(unsigned int);
 *      Provides for "static allocation" of a specific hardware spinlock. This
 *      allows the system to use a specific spinlock, identified by an ID. If
 *      the ID is invalid or if the desired lock is already allocated, this
 *      will return NULL.  Otherwise it returns a spinlock handle.
 * int hwspinlock_free(struct hwspinlock *);
 *      Frees an allocated hardware spinlock (either reserved or unreserved).
 */

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/spinlock.h>

#include <plat/hwspinlock.h>

/* Spinlock count code */
#define SPINLOCK_32_REGS		1
#define SPINLOCK_64_REGS		2
#define SPINLOCK_128_REGS		4
#define SPINLOCK_256_REGS		8
#define SPINLOCK_NUMLOCKS_OFFSET	24

/* for managing a hardware spinlock module */
struct hwspinlock_state {
	bool is_init;			/* For first-time initialization */
	int num_locks;			/* Total number of locks in system */
	spinlock_t local_lock;		/* Local protection */
	void __iomem *io_base;		/* Mapped base address */
};

/* Points to the hardware spinlock module */
static struct hwspinlock_state hwspinlock_state;
static struct hwspinlock_state *hwspinlock_module = &hwspinlock_state;

/* Spinlock object */
struct hwspinlock {
	bool is_init;
	int id;
	void __iomem *lock_reg;
	bool is_allocated;
	struct platform_device *pdev;
};

/* Array of spinlocks */
static struct hwspinlock *hwspinlocks;

/* API functions */

/* Busy loop to acquire a spinlock */
int hwspinlock_lock(struct hwspinlock *handle)
{
	int retval;

	if (WARN_ON(handle == NULL))
		return -EINVAL;

	if (WARN_ON(in_irq()))
		return -EPERM;

	if (pm_runtime_get_sync(&handle->pdev->dev) < 0)
		return -ENODEV;

	/* Attempt to acquire the lock by reading from it */
	do {
		retval = readl(handle->lock_reg);
	} while (retval == HWSPINLOCK_BUSY);

	return 0;
}
EXPORT_SYMBOL(hwspinlock_lock);

/* Attempt to acquire a spinlock once */
int hwspinlock_trylock(struct hwspinlock *handle)
{
	int retval = 0;

	if (WARN_ON(handle == NULL))
		return -EINVAL;

	if (WARN_ON(in_irq()))
		return -EPERM;

	if (pm_runtime_get_sync(&handle->pdev->dev) < 0)
		return -ENODEV;

	/* Attempt to acquire the lock by reading from it */
	retval = readl(handle->lock_reg);

	if (retval == HWSPINLOCK_BUSY)
		pm_runtime_put_sync(&handle->pdev->dev);

	return retval;
}
EXPORT_SYMBOL(hwspinlock_trylock);

/* Release a spinlock */
int hwspinlock_unlock(struct hwspinlock *handle)
{
	if (WARN_ON(handle == NULL))
		return -EINVAL;

	/* Release it by writing 0 to it */
	writel(0, handle->lock_reg);

	pm_runtime_put_sync(&handle->pdev->dev);

	return 0;
}
EXPORT_SYMBOL(hwspinlock_unlock);

/* Request an unclaimed spinlock */
struct hwspinlock *hwspinlock_request(void)
{
	int i;
	bool found = false;
	struct hwspinlock *handle = NULL;
	unsigned long flags;

	spin_lock_irqsave(&hwspinlock_module->local_lock, flags);
	/* Search for an unclaimed, unreserved lock */
	for (i = 0; i < hwspinlock_module->num_locks && !found; i++) {
		if (!hwspinlocks[i].is_allocated) {
			found = true;
			handle = &hwspinlocks[i];
		}
	}
	spin_unlock_irqrestore(&hwspinlock_module->local_lock, flags);

	/* Return error if no more locks available */
	if (!found)
		return NULL;

	handle->is_allocated = true;

	return handle;
}
EXPORT_SYMBOL(hwspinlock_request);

/* Request an unclaimed spinlock by ID */
struct hwspinlock *hwspinlock_request_specific(unsigned int id)
{
	struct hwspinlock *handle = NULL;
	unsigned long flags;

	spin_lock_irqsave(&hwspinlock_module->local_lock, flags);

	if (WARN_ON(hwspinlocks[id].is_allocated))
		goto exit;

	handle = &hwspinlocks[id];
	handle->is_allocated = true;

exit:
	spin_unlock_irqrestore(&hwspinlock_module->local_lock, flags);
	return handle;
}
EXPORT_SYMBOL(hwspinlock_request_specific);

/* Release a claimed spinlock */
int hwspinlock_free(struct hwspinlock *handle)
{
	if (WARN_ON(handle == NULL))
		return -EINVAL;

	if (WARN_ON(!handle->is_allocated))
		return -ENOMEM;

	handle->is_allocated = false;

	return 0;
}
EXPORT_SYMBOL(hwspinlock_free);

/* Probe function */
static int __devinit hwspinlock_probe(struct platform_device *pdev)
{
	struct hwspinlock_plat_info *pdata = pdev->dev.platform_data;
	struct resource *res;
	void __iomem *io_base;
	int id;

	void __iomem *sysstatus_reg;

	/* Determine number of locks */
	sysstatus_reg = ioremap(OMAP44XX_SPINLOCK_BASE +
					pdata->sysstatus_offset, sizeof(u32));
	switch (readl(sysstatus_reg) >> SPINLOCK_NUMLOCKS_OFFSET) {
	case SPINLOCK_32_REGS:
		hwspinlock_module->num_locks = 32;
		break;
	case SPINLOCK_64_REGS:
		hwspinlock_module->num_locks = 64;
		break;
	case SPINLOCK_128_REGS:
		hwspinlock_module->num_locks = 128;
		break;
	case SPINLOCK_256_REGS:
		hwspinlock_module->num_locks = 256;
		break;
	default:
		return -EINVAL;	/* Invalid spinlock count code */
	}
	iounmap(sysstatus_reg);

	/* Allocate spinlock device objects */
	hwspinlocks = kmalloc(sizeof(struct hwspinlock) *
			hwspinlock_module->num_locks, GFP_KERNEL);
	if (WARN_ON(hwspinlocks == NULL))
		return -ENOMEM;

	/* Initialize local lock */
	spin_lock_init(&hwspinlock_module->local_lock);

	/* Get address info */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

	/* Map spinlock module address space */
	io_base = ioremap(res->start, resource_size(res));
	hwspinlock_module->io_base = io_base;

	/* Set up each individual lock handle */
	for (id = 0; id < hwspinlock_module->num_locks; id++) {
		hwspinlocks[id].id		= id;
		hwspinlocks[id].pdev		= pdev;

		hwspinlocks[id].is_init		= true;
		hwspinlocks[id].is_allocated	= false;

		hwspinlocks[id].lock_reg	= io_base + pdata->
					lock_base_offset + sizeof(u32) * id;
	}
	pm_runtime_enable(&pdev->dev);

	return 0;
}

static struct platform_driver hwspinlock_driver = {
	.probe		= hwspinlock_probe,
	.driver		= {
		.name	= "hwspinlock",
	},
};

/* Initialization function */
static int __init hwspinlock_init(void)
{
	int retval = 0;

	/* Register spinlock driver */
	retval = platform_driver_register(&hwspinlock_driver);

	return retval;
}
postcore_initcall(hwspinlock_init);

/* Cleanup function */
static void __exit hwspinlock_exit(void)
{
	int id;

	platform_driver_unregister(&hwspinlock_driver);

	for (id = 0; id < hwspinlock_module->num_locks; id++)
		hwspinlocks[id].is_init = false;
	iounmap(hwspinlock_module->io_base);

	/* Free spinlock device objects */
	if (hwspinlock_module->is_init)
		kfree(hwspinlocks);
}
module_exit(hwspinlock_exit);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Hardware spinlock driver");
MODULE_AUTHOR("Simon Que");
MODULE_AUTHOR("Hari Kanigeri");