Commit | Line | Data |
---|---|---|
d6ea3df0 SAS |
1 | /* |
2 | * CE4100's SPI device is more or less the same one as found on PXA | |
3 | * | |
4 | */ | |
5 | #include <linux/pci.h> | |
6 | #include <linux/platform_device.h> | |
7 | #include <linux/of_device.h> | |
d7614de4 | 8 | #include <linux/module.h> |
d6ea3df0 SAS |
9 | #include <linux/spi/pxa2xx_spi.h> |
10 | ||
0f3e1d27 | 11 | struct ce4100_info { |
d6ea3df0 | 12 | struct ssp_device ssp; |
0f3e1d27 | 13 | struct platform_device *spi_pdev; |
d6ea3df0 SAS |
14 | }; |
15 | ||
16 | static DEFINE_MUTEX(ssp_lock); | |
17 | static LIST_HEAD(ssp_list); | |
18 | ||
19 | struct ssp_device *pxa_ssp_request(int port, const char *label) | |
20 | { | |
21 | struct ssp_device *ssp = NULL; | |
22 | ||
23 | mutex_lock(&ssp_lock); | |
24 | ||
25 | list_for_each_entry(ssp, &ssp_list, node) { | |
26 | if (ssp->port_id == port && ssp->use_count == 0) { | |
27 | ssp->use_count++; | |
28 | ssp->label = label; | |
29 | break; | |
30 | } | |
31 | } | |
32 | ||
33 | mutex_unlock(&ssp_lock); | |
34 | ||
35 | if (&ssp->node == &ssp_list) | |
36 | return NULL; | |
37 | ||
38 | return ssp; | |
39 | } | |
40 | EXPORT_SYMBOL_GPL(pxa_ssp_request); | |
41 | ||
42 | void pxa_ssp_free(struct ssp_device *ssp) | |
43 | { | |
44 | mutex_lock(&ssp_lock); | |
45 | if (ssp->use_count) { | |
46 | ssp->use_count--; | |
47 | ssp->label = NULL; | |
48 | } else | |
49 | dev_err(&ssp->pdev->dev, "device already free\n"); | |
50 | mutex_unlock(&ssp_lock); | |
51 | } | |
52 | EXPORT_SYMBOL_GPL(pxa_ssp_free); | |
53 | ||
d6ea3df0 SAS |
54 | static int __devinit ce4100_spi_probe(struct pci_dev *dev, |
55 | const struct pci_device_id *ent) | |
56 | { | |
57 | int ret; | |
58 | resource_size_t phys_beg; | |
59 | resource_size_t phys_len; | |
0f3e1d27 | 60 | struct ce4100_info *spi_info; |
d6ea3df0 | 61 | struct platform_device *pdev; |
0f3e1d27 | 62 | struct pxa2xx_spi_master spi_pdata; |
d6ea3df0 SAS |
63 | struct ssp_device *ssp; |
64 | ||
65 | ret = pci_enable_device(dev); | |
66 | if (ret) | |
67 | return ret; | |
68 | ||
69 | phys_beg = pci_resource_start(dev, 0); | |
70 | phys_len = pci_resource_len(dev, 0); | |
71 | ||
72 | if (!request_mem_region(phys_beg, phys_len, | |
73 | "CE4100 SPI")) { | |
74 | dev_err(&dev->dev, "Can't request register space.\n"); | |
75 | ret = -EBUSY; | |
76 | return ret; | |
77 | } | |
78 | ||
0f3e1d27 | 79 | pdev = platform_device_alloc("pxa2xx-spi", dev->devfn); |
d6ea3df0 | 80 | spi_info = kzalloc(sizeof(*spi_info), GFP_KERNEL); |
0f3e1d27 | 81 | if (!pdev || !spi_info ) { |
d6ea3df0 | 82 | ret = -ENOMEM; |
0f3e1d27 | 83 | goto err_nomem; |
d6ea3df0 | 84 | } |
0f3e1d27 SAS |
85 | memset(&spi_pdata, 0, sizeof(spi_pdata)); |
86 | spi_pdata.num_chipselect = dev->devfn; | |
d6ea3df0 | 87 | |
0f3e1d27 SAS |
88 | ret = platform_device_add_data(pdev, &spi_pdata, sizeof(spi_pdata)); |
89 | if (ret) | |
90 | goto err_nomem; | |
d6ea3df0 | 91 | |
0f3e1d27 | 92 | pdev->dev.parent = &dev->dev; |
d6ea3df0 | 93 | pdev->dev.of_node = dev->dev.of_node; |
0f3e1d27 | 94 | ssp = &spi_info->ssp; |
d6ea3df0 SAS |
95 | ssp->phys_base = pci_resource_start(dev, 0); |
96 | ssp->mmio_base = ioremap(phys_beg, phys_len); | |
97 | if (!ssp->mmio_base) { | |
98 | dev_err(&pdev->dev, "failed to ioremap() registers\n"); | |
99 | ret = -EIO; | |
0f3e1d27 | 100 | goto err_nomem; |
d6ea3df0 SAS |
101 | } |
102 | ssp->irq = dev->irq; | |
103 | ssp->port_id = pdev->id; | |
104 | ssp->type = PXA25x_SSP; | |
105 | ||
106 | mutex_lock(&ssp_lock); | |
107 | list_add(&ssp->node, &ssp_list); | |
108 | mutex_unlock(&ssp_lock); | |
109 | ||
110 | pci_set_drvdata(dev, spi_info); | |
111 | ||
0f3e1d27 | 112 | ret = platform_device_add(pdev); |
d6ea3df0 SAS |
113 | if (ret) |
114 | goto err_dev_add; | |
115 | ||
116 | return ret; | |
117 | ||
118 | err_dev_add: | |
119 | pci_set_drvdata(dev, NULL); | |
120 | mutex_lock(&ssp_lock); | |
121 | list_del(&ssp->node); | |
122 | mutex_unlock(&ssp_lock); | |
123 | iounmap(ssp->mmio_base); | |
124 | ||
0f3e1d27 | 125 | err_nomem: |
d6ea3df0 | 126 | release_mem_region(phys_beg, phys_len); |
0f3e1d27 SAS |
127 | platform_device_put(pdev); |
128 | kfree(spi_info); | |
d6ea3df0 SAS |
129 | return ret; |
130 | } | |
131 | ||
132 | static void __devexit ce4100_spi_remove(struct pci_dev *dev) | |
133 | { | |
0f3e1d27 | 134 | struct ce4100_info *spi_info; |
d6ea3df0 SAS |
135 | struct ssp_device *ssp; |
136 | ||
137 | spi_info = pci_get_drvdata(dev); | |
d6ea3df0 | 138 | ssp = &spi_info->ssp; |
0f3e1d27 | 139 | platform_device_unregister(spi_info->spi_pdev); |
d6ea3df0 SAS |
140 | |
141 | iounmap(ssp->mmio_base); | |
142 | release_mem_region(pci_resource_start(dev, 0), | |
143 | pci_resource_len(dev, 0)); | |
144 | ||
145 | mutex_lock(&ssp_lock); | |
146 | list_del(&ssp->node); | |
147 | mutex_unlock(&ssp_lock); | |
148 | ||
149 | pci_set_drvdata(dev, NULL); | |
150 | pci_disable_device(dev); | |
151 | kfree(spi_info); | |
152 | } | |
153 | ||
e290cf27 | 154 | static DEFINE_PCI_DEVICE_TABLE(ce4100_spi_devices) = { |
d6ea3df0 SAS |
155 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x2e6a) }, |
156 | { }, | |
157 | }; | |
158 | MODULE_DEVICE_TABLE(pci, ce4100_spi_devices); | |
159 | ||
160 | static struct pci_driver ce4100_spi_driver = { | |
161 | .name = "ce4100_spi", | |
162 | .id_table = ce4100_spi_devices, | |
163 | .probe = ce4100_spi_probe, | |
164 | .remove = __devexit_p(ce4100_spi_remove), | |
165 | }; | |
166 | ||
167 | static int __init ce4100_spi_init(void) | |
168 | { | |
169 | return pci_register_driver(&ce4100_spi_driver); | |
170 | } | |
171 | module_init(ce4100_spi_init); | |
172 | ||
173 | static void __exit ce4100_spi_exit(void) | |
174 | { | |
175 | pci_unregister_driver(&ce4100_spi_driver); | |
176 | } | |
177 | module_exit(ce4100_spi_exit); | |
178 | ||
179 | MODULE_DESCRIPTION("CE4100 PCI-SPI glue code for PXA's driver"); | |
180 | MODULE_LICENSE("GPL v2"); | |
181 | MODULE_AUTHOR("Sebastian Andrzej Siewior <bigeasy@linutronix.de>"); |