xref: /linux/drivers/soc/pxa/ssp.c (revision 3a38ef2b3cb6b63c105247b5ea4a9cf600e673f0)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  *  linux/arch/arm/mach-pxa/ssp.c
4  *
5  *  based on linux/arch/arm/mach-sa1100/ssp.c by Russell King
6  *
7  *  Copyright (C) 2003 Russell King.
8  *  Copyright (C) 2003 Wolfson Microelectronics PLC
9  *
10  *  PXA2xx SSP driver.  This provides the generic core for simple
11  *  IO-based SSP applications and allows easy port setup for DMA access.
12  *
13  *  Author: Liam Girdwood <liam.girdwood@wolfsonmicro.com>
14  */
15 
16 #include <linux/module.h>
17 #include <linux/kernel.h>
18 #include <linux/sched.h>
19 #include <linux/slab.h>
20 #include <linux/errno.h>
21 #include <linux/interrupt.h>
22 #include <linux/ioport.h>
23 #include <linux/init.h>
24 #include <linux/mutex.h>
25 #include <linux/clk.h>
26 #include <linux/err.h>
27 #include <linux/platform_device.h>
28 #include <linux/spi/pxa2xx_spi.h>
29 #include <linux/io.h>
30 #include <linux/of.h>
31 #include <linux/of_device.h>
32 
33 #include <asm/irq.h>
34 
35 static DEFINE_MUTEX(ssp_lock);
36 static LIST_HEAD(ssp_list);
37 
38 struct ssp_device *pxa_ssp_request(int port, const char *label)
39 {
40 	struct ssp_device *ssp = NULL;
41 
42 	mutex_lock(&ssp_lock);
43 
44 	list_for_each_entry(ssp, &ssp_list, node) {
45 		if (ssp->port_id == port && ssp->use_count == 0) {
46 			ssp->use_count++;
47 			ssp->label = label;
48 			break;
49 		}
50 	}
51 
52 	mutex_unlock(&ssp_lock);
53 
54 	if (&ssp->node == &ssp_list)
55 		return NULL;
56 
57 	return ssp;
58 }
59 EXPORT_SYMBOL(pxa_ssp_request);
60 
61 struct ssp_device *pxa_ssp_request_of(const struct device_node *of_node,
62 				      const char *label)
63 {
64 	struct ssp_device *ssp = NULL;
65 
66 	mutex_lock(&ssp_lock);
67 
68 	list_for_each_entry(ssp, &ssp_list, node) {
69 		if (ssp->of_node == of_node && ssp->use_count == 0) {
70 			ssp->use_count++;
71 			ssp->label = label;
72 			break;
73 		}
74 	}
75 
76 	mutex_unlock(&ssp_lock);
77 
78 	if (&ssp->node == &ssp_list)
79 		return NULL;
80 
81 	return ssp;
82 }
83 EXPORT_SYMBOL(pxa_ssp_request_of);
84 
85 void pxa_ssp_free(struct ssp_device *ssp)
86 {
87 	mutex_lock(&ssp_lock);
88 	if (ssp->use_count) {
89 		ssp->use_count--;
90 		ssp->label = NULL;
91 	} else
92 		dev_err(ssp->dev, "device already free\n");
93 	mutex_unlock(&ssp_lock);
94 }
95 EXPORT_SYMBOL(pxa_ssp_free);
96 
97 #ifdef CONFIG_OF
98 static const struct of_device_id pxa_ssp_of_ids[] = {
99 	{ .compatible = "mrvl,pxa25x-ssp",	.data = (void *) PXA25x_SSP },
100 	{ .compatible = "mvrl,pxa25x-nssp",	.data = (void *) PXA25x_NSSP },
101 	{ .compatible = "mrvl,pxa27x-ssp",	.data = (void *) PXA27x_SSP },
102 	{ .compatible = "mrvl,pxa3xx-ssp",	.data = (void *) PXA3xx_SSP },
103 	{ .compatible = "mvrl,pxa168-ssp",	.data = (void *) PXA168_SSP },
104 	{ .compatible = "mrvl,pxa910-ssp",	.data = (void *) PXA910_SSP },
105 	{ .compatible = "mrvl,ce4100-ssp",	.data = (void *) CE4100_SSP },
106 	{ },
107 };
108 MODULE_DEVICE_TABLE(of, pxa_ssp_of_ids);
109 #endif
110 
111 static int pxa_ssp_probe(struct platform_device *pdev)
112 {
113 	struct resource *res;
114 	struct ssp_device *ssp;
115 	struct device *dev = &pdev->dev;
116 
117 	ssp = devm_kzalloc(dev, sizeof(struct ssp_device), GFP_KERNEL);
118 	if (ssp == NULL)
119 		return -ENOMEM;
120 
121 	ssp->dev = dev;
122 
123 	ssp->clk = devm_clk_get(dev, NULL);
124 	if (IS_ERR(ssp->clk))
125 		return PTR_ERR(ssp->clk);
126 
127 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
128 	if (res == NULL) {
129 		dev_err(dev, "no memory resource defined\n");
130 		return -ENODEV;
131 	}
132 
133 	res = devm_request_mem_region(dev, res->start, resource_size(res),
134 				      pdev->name);
135 	if (res == NULL) {
136 		dev_err(dev, "failed to request memory resource\n");
137 		return -EBUSY;
138 	}
139 
140 	ssp->phys_base = res->start;
141 
142 	ssp->mmio_base = devm_ioremap(dev, res->start, resource_size(res));
143 	if (ssp->mmio_base == NULL) {
144 		dev_err(dev, "failed to ioremap() registers\n");
145 		return -ENODEV;
146 	}
147 
148 	ssp->irq = platform_get_irq(pdev, 0);
149 	if (ssp->irq < 0) {
150 		dev_err(dev, "no IRQ resource defined\n");
151 		return -ENODEV;
152 	}
153 
154 	if (dev->of_node) {
155 		const struct of_device_id *id =
156 			of_match_device(of_match_ptr(pxa_ssp_of_ids), dev);
157 		ssp->type = (int) id->data;
158 	} else {
159 		const struct platform_device_id *id =
160 			platform_get_device_id(pdev);
161 		ssp->type = (int) id->driver_data;
162 
163 		/* PXA2xx/3xx SSP ports starts from 1 and the internal pdev->id
164 		 * starts from 0, do a translation here
165 		 */
166 		ssp->port_id = pdev->id + 1;
167 	}
168 
169 	ssp->use_count = 0;
170 	ssp->of_node = dev->of_node;
171 
172 	mutex_lock(&ssp_lock);
173 	list_add(&ssp->node, &ssp_list);
174 	mutex_unlock(&ssp_lock);
175 
176 	platform_set_drvdata(pdev, ssp);
177 
178 	return 0;
179 }
180 
181 static int pxa_ssp_remove(struct platform_device *pdev)
182 {
183 	struct ssp_device *ssp = platform_get_drvdata(pdev);
184 
185 	mutex_lock(&ssp_lock);
186 	list_del(&ssp->node);
187 	mutex_unlock(&ssp_lock);
188 
189 	return 0;
190 }
191 
192 static const struct platform_device_id ssp_id_table[] = {
193 	{ "pxa25x-ssp",		PXA25x_SSP },
194 	{ "pxa25x-nssp",	PXA25x_NSSP },
195 	{ "pxa27x-ssp",		PXA27x_SSP },
196 	{ "pxa3xx-ssp",		PXA3xx_SSP },
197 	{ "pxa168-ssp",		PXA168_SSP },
198 	{ "pxa910-ssp",		PXA910_SSP },
199 	{ },
200 };
201 
202 static struct platform_driver pxa_ssp_driver = {
203 	.probe		= pxa_ssp_probe,
204 	.remove		= pxa_ssp_remove,
205 	.driver		= {
206 		.name		= "pxa2xx-ssp",
207 		.of_match_table	= of_match_ptr(pxa_ssp_of_ids),
208 	},
209 	.id_table	= ssp_id_table,
210 };
211 
212 static int __init pxa_ssp_init(void)
213 {
214 	return platform_driver_register(&pxa_ssp_driver);
215 }
216 
217 static void __exit pxa_ssp_exit(void)
218 {
219 	platform_driver_unregister(&pxa_ssp_driver);
220 }
221 
222 arch_initcall(pxa_ssp_init);
223 module_exit(pxa_ssp_exit);
224 
225 MODULE_DESCRIPTION("PXA SSP driver");
226 MODULE_AUTHOR("Liam Girdwood");
227 MODULE_LICENSE("GPL");
228