1 | /*====================================================================== |
2 | |
3 | Device driver for the PCMCIA control functionality of StrongARM |
4 | SA-1100 microprocessors. |
5 | |
6 | The contents of this file are subject to the Mozilla Public |
7 | License Version 1.1 (the "License"); you may not use this file |
8 | except in compliance with the License. You may obtain a copy of |
9 | the License at http://www.mozilla.org/MPL/ |
10 | |
11 | Software distributed under the License is distributed on an "AS |
12 | IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or |
13 | implied. See the License for the specific language governing |
14 | rights and limitations under the License. |
15 | |
16 | The initial developer of the original code is John G. Dorsey |
17 | <john+@cs.cmu.edu>. Portions created by John G. Dorsey are |
18 | Copyright (C) 1999 John G. Dorsey. All Rights Reserved. |
19 | |
20 | Alternatively, the contents of this file may be used under the |
21 | terms of the GNU Public License version 2 (the "GPL"), in which |
22 | case the provisions of the GPL are applicable instead of the |
23 | above. If you wish to allow the use of your version of this file |
24 | only under the terms of the GPL and not to allow others to use |
25 | your version of this file under the MPL, indicate your decision |
26 | by deleting the provisions above and replace them with the notice |
27 | and other provisions required by the GPL. If you do not delete |
28 | the provisions above, a recipient may use your version of this |
29 | file under either the MPL or the GPL. |
30 | |
31 | ======================================================================*/ |
32 | |
33 | #include <linux/module.h> |
34 | #include <linux/gpio/consumer.h> |
35 | #include <linux/init.h> |
36 | #include <linux/regulator/consumer.h> |
37 | #include <linux/slab.h> |
38 | #include <linux/platform_device.h> |
39 | |
40 | #include <pcmcia/ss.h> |
41 | |
42 | #include <asm/hardware/scoop.h> |
43 | |
44 | #include "sa1100_generic.h" |
45 | |
46 | static const char *sa11x0_cf_gpio_names[] = { |
47 | [SOC_STAT_CD] = "detect" , |
48 | [SOC_STAT_BVD1] = "bvd1" , |
49 | [SOC_STAT_BVD2] = "bvd2" , |
50 | [SOC_STAT_RDY] = "ready" , |
51 | }; |
52 | |
53 | static int sa11x0_cf_hw_init(struct soc_pcmcia_socket *skt) |
54 | { |
55 | struct device *dev = skt->socket.dev.parent; |
56 | int i; |
57 | |
58 | skt->gpio_reset = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
59 | if (IS_ERR(ptr: skt->gpio_reset)) |
60 | return PTR_ERR(ptr: skt->gpio_reset); |
61 | |
62 | skt->gpio_bus_enable = devm_gpiod_get_optional(dev, con_id: "bus-enable" , |
63 | flags: GPIOD_OUT_HIGH); |
64 | if (IS_ERR(ptr: skt->gpio_bus_enable)) |
65 | return PTR_ERR(ptr: skt->gpio_bus_enable); |
66 | |
67 | skt->vcc.reg = devm_regulator_get_optional(dev, id: "vcc" ); |
68 | if (IS_ERR(ptr: skt->vcc.reg)) |
69 | return PTR_ERR(ptr: skt->vcc.reg); |
70 | |
71 | if (!skt->vcc.reg) |
72 | dev_warn(dev, |
73 | "no Vcc regulator provided, ignoring Vcc controls\n" ); |
74 | |
75 | for (i = 0; i < ARRAY_SIZE(sa11x0_cf_gpio_names); i++) { |
76 | skt->stat[i].name = sa11x0_cf_gpio_names[i]; |
77 | skt->stat[i].desc = devm_gpiod_get_optional(dev, |
78 | con_id: sa11x0_cf_gpio_names[i], flags: GPIOD_IN); |
79 | if (IS_ERR(ptr: skt->stat[i].desc)) |
80 | return PTR_ERR(ptr: skt->stat[i].desc); |
81 | } |
82 | return 0; |
83 | } |
84 | |
85 | static int sa11x0_cf_configure_socket(struct soc_pcmcia_socket *skt, |
86 | const socket_state_t *state) |
87 | { |
88 | return soc_pcmcia_regulator_set(skt, r: &skt->vcc, v: state->Vcc); |
89 | } |
90 | |
91 | static struct pcmcia_low_level sa11x0_cf_ops = { |
92 | .owner = THIS_MODULE, |
93 | .hw_init = sa11x0_cf_hw_init, |
94 | .socket_state = soc_common_cf_socket_state, |
95 | .configure_socket = sa11x0_cf_configure_socket, |
96 | }; |
97 | |
98 | int __init pcmcia_collie_init(struct device *dev); |
99 | |
100 | static int (*sa11x0_pcmcia_legacy_hw_init[])(struct device *dev) = { |
101 | #ifdef CONFIG_SA1100_H3600 |
102 | pcmcia_h3600_init, |
103 | #endif |
104 | #ifdef CONFIG_SA1100_COLLIE |
105 | pcmcia_collie_init, |
106 | #endif |
107 | }; |
108 | |
109 | static int sa11x0_drv_pcmcia_legacy_probe(struct platform_device *dev) |
110 | { |
111 | int i, ret = -ENODEV; |
112 | |
113 | /* |
114 | * Initialise any "on-board" PCMCIA sockets. |
115 | */ |
116 | for (i = 0; i < ARRAY_SIZE(sa11x0_pcmcia_legacy_hw_init); i++) { |
117 | ret = sa11x0_pcmcia_legacy_hw_init[i](&dev->dev); |
118 | if (ret == 0) |
119 | break; |
120 | } |
121 | |
122 | return ret; |
123 | } |
124 | |
125 | static void sa11x0_drv_pcmcia_legacy_remove(struct platform_device *dev) |
126 | { |
127 | struct skt_dev_info *sinfo = platform_get_drvdata(pdev: dev); |
128 | int i; |
129 | |
130 | platform_set_drvdata(pdev: dev, NULL); |
131 | |
132 | for (i = 0; i < sinfo->nskt; i++) |
133 | soc_pcmcia_remove_one(skt: &sinfo->skt[i]); |
134 | } |
135 | |
136 | static int sa11x0_drv_pcmcia_probe(struct platform_device *pdev) |
137 | { |
138 | struct soc_pcmcia_socket *skt; |
139 | struct device *dev = &pdev->dev; |
140 | |
141 | if (pdev->id == -1) |
142 | return sa11x0_drv_pcmcia_legacy_probe(dev: pdev); |
143 | |
144 | skt = devm_kzalloc(dev, size: sizeof(*skt), GFP_KERNEL); |
145 | if (!skt) |
146 | return -ENOMEM; |
147 | |
148 | platform_set_drvdata(pdev, data: skt); |
149 | |
150 | skt->nr = pdev->id; |
151 | skt->clk = devm_clk_get(dev, NULL); |
152 | if (IS_ERR(ptr: skt->clk)) |
153 | return PTR_ERR(ptr: skt->clk); |
154 | |
155 | sa11xx_drv_pcmcia_ops(ops: &sa11x0_cf_ops); |
156 | soc_pcmcia_init_one(skt, ops: &sa11x0_cf_ops, dev); |
157 | |
158 | return sa11xx_drv_pcmcia_add_one(skt); |
159 | } |
160 | |
161 | static int sa11x0_drv_pcmcia_remove(struct platform_device *dev) |
162 | { |
163 | struct soc_pcmcia_socket *skt; |
164 | |
165 | if (dev->id == -1) { |
166 | sa11x0_drv_pcmcia_legacy_remove(dev); |
167 | return 0; |
168 | } |
169 | |
170 | skt = platform_get_drvdata(pdev: dev); |
171 | |
172 | soc_pcmcia_remove_one(skt); |
173 | |
174 | return 0; |
175 | } |
176 | |
177 | static struct platform_driver sa11x0_pcmcia_driver = { |
178 | .driver = { |
179 | .name = "sa11x0-pcmcia" , |
180 | }, |
181 | .probe = sa11x0_drv_pcmcia_probe, |
182 | .remove = sa11x0_drv_pcmcia_remove, |
183 | }; |
184 | |
185 | /* sa11x0_pcmcia_init() |
186 | * ^^^^^^^^^^^^^^^^^^^^ |
187 | * |
188 | * This routine performs low-level PCMCIA initialization and then |
189 | * registers this socket driver with Card Services. |
190 | * |
191 | * Returns: 0 on success, -ve error code on failure |
192 | */ |
193 | static int __init sa11x0_pcmcia_init(void) |
194 | { |
195 | return platform_driver_register(&sa11x0_pcmcia_driver); |
196 | } |
197 | |
198 | /* sa11x0_pcmcia_exit() |
199 | * ^^^^^^^^^^^^^^^^^^^^ |
200 | * Invokes the low-level kernel service to free IRQs associated with this |
201 | * socket controller and reset GPIO edge detection. |
202 | */ |
203 | static void __exit sa11x0_pcmcia_exit(void) |
204 | { |
205 | platform_driver_unregister(&sa11x0_pcmcia_driver); |
206 | } |
207 | |
208 | MODULE_AUTHOR("John Dorsey <john+@cs.cmu.edu>" ); |
209 | MODULE_DESCRIPTION("Linux PCMCIA Card Services: SA-11x0 Socket Controller" ); |
210 | MODULE_LICENSE("Dual MPL/GPL" ); |
211 | |
212 | fs_initcall(sa11x0_pcmcia_init); |
213 | module_exit(sa11x0_pcmcia_exit); |
214 | |