1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * u8500 HWSEM driver |
4 | * |
5 | * Copyright (C) 2010-2011 ST-Ericsson |
6 | * |
7 | * Implements u8500 semaphore handling for protocol 1, no interrupts. |
8 | * |
9 | * Author: Mathieu Poirier <mathieu.poirier@linaro.org> |
10 | * Heavily borrowed from the work of : |
11 | * Simon Que <sque@ti.com> |
12 | * Hari Kanigeri <h-kanigeri2@ti.com> |
13 | * Ohad Ben-Cohen <ohad@wizery.com> |
14 | */ |
15 | |
16 | #include <linux/module.h> |
17 | #include <linux/delay.h> |
18 | #include <linux/io.h> |
19 | #include <linux/slab.h> |
20 | #include <linux/spinlock.h> |
21 | #include <linux/hwspinlock.h> |
22 | #include <linux/platform_device.h> |
23 | |
24 | #include "hwspinlock_internal.h" |
25 | |
26 | /* |
27 | * Implementation of STE's HSem protocol 1 without interrutps. |
28 | * The only masterID we allow is '0x01' to force people to use |
29 | * HSems for synchronisation between processors rather than processes |
30 | * on the ARM core. |
31 | */ |
32 | |
33 | #define U8500_MAX_SEMAPHORE 32 /* a total of 32 semaphore */ |
34 | #define RESET_SEMAPHORE (0) /* free */ |
35 | |
36 | /* |
37 | * CPU ID for master running u8500 kernel. |
38 | * Hswpinlocks should only be used to synchonise operations |
39 | * between the Cortex A9 core and the other CPUs. Hence |
40 | * forcing the masterID to a preset value. |
41 | */ |
42 | #define HSEM_MASTER_ID 0x01 |
43 | |
44 | #define HSEM_REGISTER_OFFSET 0x08 |
45 | |
46 | #define HSEM_CTRL_REG 0x00 |
47 | #define HSEM_ICRALL 0x90 |
48 | #define HSEM_PROTOCOL_1 0x01 |
49 | |
50 | static int u8500_hsem_trylock(struct hwspinlock *lock) |
51 | { |
52 | void __iomem *lock_addr = lock->priv; |
53 | |
54 | writel(HSEM_MASTER_ID, addr: lock_addr); |
55 | |
56 | /* get only first 4 bit and compare to masterID. |
57 | * if equal, we have the semaphore, otherwise |
58 | * someone else has it. |
59 | */ |
60 | return (HSEM_MASTER_ID == (0x0F & readl(addr: lock_addr))); |
61 | } |
62 | |
63 | static void u8500_hsem_unlock(struct hwspinlock *lock) |
64 | { |
65 | void __iomem *lock_addr = lock->priv; |
66 | |
67 | /* release the lock by writing 0 to it */ |
68 | writel(RESET_SEMAPHORE, addr: lock_addr); |
69 | } |
70 | |
71 | /* |
72 | * u8500: what value is recommended here ? |
73 | */ |
74 | static void u8500_hsem_relax(struct hwspinlock *lock) |
75 | { |
76 | ndelay(50); |
77 | } |
78 | |
79 | static const struct hwspinlock_ops u8500_hwspinlock_ops = { |
80 | .trylock = u8500_hsem_trylock, |
81 | .unlock = u8500_hsem_unlock, |
82 | .relax = u8500_hsem_relax, |
83 | }; |
84 | |
85 | static int u8500_hsem_probe(struct platform_device *pdev) |
86 | { |
87 | struct hwspinlock_pdata *pdata = pdev->dev.platform_data; |
88 | struct hwspinlock_device *bank; |
89 | struct hwspinlock *hwlock; |
90 | void __iomem *io_base; |
91 | int i, num_locks = U8500_MAX_SEMAPHORE; |
92 | ulong val; |
93 | |
94 | if (!pdata) |
95 | return -ENODEV; |
96 | |
97 | io_base = devm_platform_ioremap_resource(pdev, index: 0); |
98 | if (IS_ERR(ptr: io_base)) |
99 | return PTR_ERR(ptr: io_base); |
100 | |
101 | /* make sure protocol 1 is selected */ |
102 | val = readl(addr: io_base + HSEM_CTRL_REG); |
103 | writel(val: (val & ~HSEM_PROTOCOL_1), addr: io_base + HSEM_CTRL_REG); |
104 | |
105 | /* clear all interrupts */ |
106 | writel(val: 0xFFFF, addr: io_base + HSEM_ICRALL); |
107 | |
108 | bank = devm_kzalloc(dev: &pdev->dev, struct_size(bank, lock, num_locks), |
109 | GFP_KERNEL); |
110 | if (!bank) |
111 | return -ENOMEM; |
112 | |
113 | platform_set_drvdata(pdev, data: bank); |
114 | |
115 | for (i = 0, hwlock = &bank->lock[0]; i < num_locks; i++, hwlock++) |
116 | hwlock->priv = io_base + HSEM_REGISTER_OFFSET + sizeof(u32) * i; |
117 | |
118 | return devm_hwspin_lock_register(dev: &pdev->dev, bank, |
119 | ops: &u8500_hwspinlock_ops, |
120 | base_id: pdata->base_id, num_locks); |
121 | } |
122 | |
123 | static void u8500_hsem_remove(struct platform_device *pdev) |
124 | { |
125 | struct hwspinlock_device *bank = platform_get_drvdata(pdev); |
126 | void __iomem *io_base = bank->lock[0].priv - HSEM_REGISTER_OFFSET; |
127 | |
128 | /* clear all interrupts */ |
129 | writel(val: 0xFFFF, addr: io_base + HSEM_ICRALL); |
130 | } |
131 | |
132 | static struct platform_driver u8500_hsem_driver = { |
133 | .probe = u8500_hsem_probe, |
134 | .remove_new = u8500_hsem_remove, |
135 | .driver = { |
136 | .name = "u8500_hsem" , |
137 | }, |
138 | }; |
139 | |
140 | static int __init u8500_hsem_init(void) |
141 | { |
142 | return platform_driver_register(&u8500_hsem_driver); |
143 | } |
144 | /* board init code might need to reserve hwspinlocks for predefined purposes */ |
145 | postcore_initcall(u8500_hsem_init); |
146 | |
147 | static void __exit u8500_hsem_exit(void) |
148 | { |
149 | platform_driver_unregister(&u8500_hsem_driver); |
150 | } |
151 | module_exit(u8500_hsem_exit); |
152 | |
153 | MODULE_LICENSE("GPL v2" ); |
154 | MODULE_DESCRIPTION("Hardware Spinlock driver for u8500" ); |
155 | MODULE_AUTHOR("Mathieu Poirier <mathieu.poirier@linaro.org>" ); |
156 | |