xref: /linux/drivers/soc/pxa/ssp.c (revision 4b660dbd9ee2059850fd30e0df420ca7a38a1856)
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 		return -ENODEV;
151 
152 	if (dev->of_node) {
153 		const struct of_device_id *id =
154 			of_match_device(of_match_ptr(pxa_ssp_of_ids), dev);
155 		ssp->type = (uintptr_t) id->data;
156 	} else {
157 		const struct platform_device_id *id =
158 			platform_get_device_id(pdev);
159 		ssp->type = id->driver_data;
160 
161 		/* PXA2xx/3xx SSP ports starts from 1 and the internal pdev->id
162 		 * starts from 0, do a translation here
163 		 */
164 		ssp->port_id = pdev->id + 1;
165 	}
166 
167 	ssp->use_count = 0;
168 	ssp->of_node = dev->of_node;
169 
170 	mutex_lock(&ssp_lock);
171 	list_add(&ssp->node, &ssp_list);
172 	mutex_unlock(&ssp_lock);
173 
174 	platform_set_drvdata(pdev, ssp);
175 
176 	return 0;
177 }
178 
179 static void pxa_ssp_remove(struct platform_device *pdev)
180 {
181 	struct ssp_device *ssp = platform_get_drvdata(pdev);
182 
183 	mutex_lock(&ssp_lock);
184 	list_del(&ssp->node);
185 	mutex_unlock(&ssp_lock);
186 }
187 
188 static const struct platform_device_id ssp_id_table[] = {
189 	{ "pxa25x-ssp",		PXA25x_SSP },
190 	{ "pxa25x-nssp",	PXA25x_NSSP },
191 	{ "pxa27x-ssp",		PXA27x_SSP },
192 	{ "pxa3xx-ssp",		PXA3xx_SSP },
193 	{ "pxa168-ssp",		PXA168_SSP },
194 	{ "pxa910-ssp",		PXA910_SSP },
195 	{ },
196 };
197 
198 static struct platform_driver pxa_ssp_driver = {
199 	.probe		= pxa_ssp_probe,
200 	.remove_new	= pxa_ssp_remove,
201 	.driver		= {
202 		.name		= "pxa2xx-ssp",
203 		.of_match_table	= of_match_ptr(pxa_ssp_of_ids),
204 	},
205 	.id_table	= ssp_id_table,
206 };
207 
208 static int __init pxa_ssp_init(void)
209 {
210 	return platform_driver_register(&pxa_ssp_driver);
211 }
212 
213 static void __exit pxa_ssp_exit(void)
214 {
215 	platform_driver_unregister(&pxa_ssp_driver);
216 }
217 
218 arch_initcall(pxa_ssp_init);
219 module_exit(pxa_ssp_exit);
220 
221 MODULE_DESCRIPTION("PXA SSP driver");
222 MODULE_AUTHOR("Liam Girdwood");
223 MODULE_LICENSE("GPL");
224