1 // SPDX-License-Identifier: GPL-2.0+
3 * Copyright (C) 2018 Oleksij Rempel <linux@rempel-privat.de>
5 * Driver for Alcor Micro AU6601 and AU6621 controllers
8 #include <linux/delay.h>
9 #include <linux/interrupt.h>
11 #include <linux/irq.h>
12 #include <linux/mfd/core.h>
13 #include <linux/module.h>
14 #include <linux/pci.h>
15 #include <linux/platform_device.h>
18 #include <linux/alcor_pci.h>
20 #define DRV_NAME_ALCOR_PCI "alcor_pci"
22 static DEFINE_IDA(alcor_pci_idr
);
24 static struct mfd_cell alcor_pci_cells
[] = {
26 .name
= DRV_NAME_ALCOR_PCI_SDMMC
,
29 .name
= DRV_NAME_ALCOR_PCI_MS
,
33 static const struct alcor_dev_cfg alcor_cfg
= {
37 static const struct alcor_dev_cfg au6621_cfg
= {
41 static const struct alcor_dev_cfg au6625_cfg
= {
45 static const struct pci_device_id pci_ids
[] = {
46 { PCI_DEVICE(PCI_ID_ALCOR_MICRO
, PCI_ID_AU6601
),
47 .driver_data
= (kernel_ulong_t
)&alcor_cfg
},
48 { PCI_DEVICE(PCI_ID_ALCOR_MICRO
, PCI_ID_AU6621
),
49 .driver_data
= (kernel_ulong_t
)&au6621_cfg
},
50 { PCI_DEVICE(PCI_ID_ALCOR_MICRO
, PCI_ID_AU6625
),
51 .driver_data
= (kernel_ulong_t
)&au6625_cfg
},
54 MODULE_DEVICE_TABLE(pci
, pci_ids
);
56 void alcor_write8(struct alcor_pci_priv
*priv
, u8 val
, unsigned int addr
)
58 writeb(val
, priv
->iobase
+ addr
);
60 EXPORT_SYMBOL_GPL(alcor_write8
);
62 void alcor_write16(struct alcor_pci_priv
*priv
, u16 val
, unsigned int addr
)
64 writew(val
, priv
->iobase
+ addr
);
66 EXPORT_SYMBOL_GPL(alcor_write16
);
68 void alcor_write32(struct alcor_pci_priv
*priv
, u32 val
, unsigned int addr
)
70 writel(val
, priv
->iobase
+ addr
);
72 EXPORT_SYMBOL_GPL(alcor_write32
);
74 void alcor_write32be(struct alcor_pci_priv
*priv
, u32 val
, unsigned int addr
)
76 iowrite32be(val
, priv
->iobase
+ addr
);
78 EXPORT_SYMBOL_GPL(alcor_write32be
);
80 u8
alcor_read8(struct alcor_pci_priv
*priv
, unsigned int addr
)
82 return readb(priv
->iobase
+ addr
);
84 EXPORT_SYMBOL_GPL(alcor_read8
);
86 u32
alcor_read32(struct alcor_pci_priv
*priv
, unsigned int addr
)
88 return readl(priv
->iobase
+ addr
);
90 EXPORT_SYMBOL_GPL(alcor_read32
);
92 u32
alcor_read32be(struct alcor_pci_priv
*priv
, unsigned int addr
)
94 return ioread32be(priv
->iobase
+ addr
);
96 EXPORT_SYMBOL_GPL(alcor_read32be
);
98 static int alcor_pci_find_cap_offset(struct alcor_pci_priv
*priv
,
105 where
= ALCOR_CAP_START_OFFSET
;
106 pci_read_config_byte(pci
, where
, &val8
);
112 pci_read_config_dword(pci
, where
, &val32
);
113 if (val32
== 0xffffffff) {
114 dev_dbg(priv
->dev
, "find_cap_offset invalid value %x.\n",
119 if ((val32
& 0xff) == 0x10) {
120 dev_dbg(priv
->dev
, "pcie cap offset: %x\n", where
);
124 if ((val32
& 0xff00) == 0x00) {
125 dev_dbg(priv
->dev
, "pci_find_cap_offset invalid value %x.\n",
129 where
= (int)((val32
>> 8) & 0xff);
135 static void alcor_pci_init_check_aspm(struct alcor_pci_priv
*priv
)
141 priv
->pdev_cap_off
= alcor_pci_find_cap_offset(priv
, priv
->pdev
);
142 priv
->parent_cap_off
= alcor_pci_find_cap_offset(priv
,
145 if ((priv
->pdev_cap_off
== 0) || (priv
->parent_cap_off
== 0)) {
146 dev_dbg(priv
->dev
, "pci_cap_off: %x, parent_cap_off: %x\n",
147 priv
->pdev_cap_off
, priv
->parent_cap_off
);
151 /* link capability */
153 where
= priv
->pdev_cap_off
+ ALCOR_PCIE_LINK_CAP_OFFSET
;
154 pci_read_config_dword(pci
, where
, &val32
);
155 priv
->pdev_aspm_cap
= (u8
)(val32
>> 10) & 0x03;
157 pci
= priv
->parent_pdev
;
158 where
= priv
->parent_cap_off
+ ALCOR_PCIE_LINK_CAP_OFFSET
;
159 pci_read_config_dword(pci
, where
, &val32
);
160 priv
->parent_aspm_cap
= (u8
)(val32
>> 10) & 0x03;
162 if (priv
->pdev_aspm_cap
!= priv
->parent_aspm_cap
) {
165 dev_dbg(priv
->dev
, "pdev_aspm_cap: %x, parent_aspm_cap: %x\n",
166 priv
->pdev_aspm_cap
, priv
->parent_aspm_cap
);
167 aspm_cap
= priv
->pdev_aspm_cap
& priv
->parent_aspm_cap
;
168 priv
->pdev_aspm_cap
= aspm_cap
;
169 priv
->parent_aspm_cap
= aspm_cap
;
172 dev_dbg(priv
->dev
, "ext_config_dev_aspm: %x, pdev_aspm_cap: %x\n",
173 priv
->ext_config_dev_aspm
, priv
->pdev_aspm_cap
);
174 priv
->ext_config_dev_aspm
&= priv
->pdev_aspm_cap
;
177 static void alcor_pci_aspm_ctrl(struct alcor_pci_priv
*priv
, u8 aspm_enable
)
184 if ((!priv
->pdev_cap_off
) || (!priv
->parent_cap_off
)) {
185 dev_dbg(priv
->dev
, "pci_cap_off: %x, parent_cap_off: %x\n",
186 priv
->pdev_cap_off
, priv
->parent_cap_off
);
190 if (!priv
->pdev_aspm_cap
)
195 aspm_ctrl
= priv
->ext_config_dev_aspm
;
198 dev_dbg(priv
->dev
, "aspm_ctrl == 0\n");
203 for (i
= 0; i
< 2; i
++) {
206 pci
= priv
->parent_pdev
;
207 where
= priv
->parent_cap_off
208 + ALCOR_PCIE_LINK_CTRL_OFFSET
;
211 where
= priv
->pdev_cap_off
212 + ALCOR_PCIE_LINK_CTRL_OFFSET
;
215 pci_read_config_dword(pci
, where
, &val32
);
217 val32
|= (aspm_ctrl
& priv
->pdev_aspm_cap
);
218 pci_write_config_byte(pci
, where
, (u8
)val32
);
223 static inline void alcor_mask_sd_irqs(struct alcor_pci_priv
*priv
)
225 alcor_write32(priv
, 0, AU6601_REG_INT_ENABLE
);
228 static inline void alcor_unmask_sd_irqs(struct alcor_pci_priv
*priv
)
230 alcor_write32(priv
, AU6601_INT_CMD_MASK
| AU6601_INT_DATA_MASK
|
231 AU6601_INT_CARD_INSERT
| AU6601_INT_CARD_REMOVE
|
232 AU6601_INT_OVER_CURRENT_ERR
,
233 AU6601_REG_INT_ENABLE
);
236 static inline void alcor_mask_ms_irqs(struct alcor_pci_priv
*priv
)
238 alcor_write32(priv
, 0, AU6601_MS_INT_ENABLE
);
241 static inline void alcor_unmask_ms_irqs(struct alcor_pci_priv
*priv
)
243 alcor_write32(priv
, 0x3d00fa, AU6601_MS_INT_ENABLE
);
246 static int alcor_pci_probe(struct pci_dev
*pdev
,
247 const struct pci_device_id
*ent
)
249 struct alcor_dev_cfg
*cfg
;
250 struct alcor_pci_priv
*priv
;
253 cfg
= (void *)ent
->driver_data
;
255 ret
= pcim_enable_device(pdev
);
259 priv
= devm_kzalloc(&pdev
->dev
, sizeof(*priv
), GFP_KERNEL
);
263 ret
= ida_simple_get(&alcor_pci_idr
, 0, 0, GFP_KERNEL
);
269 priv
->parent_pdev
= pdev
->bus
->self
;
270 priv
->dev
= &pdev
->dev
;
272 priv
->irq
= pdev
->irq
;
274 ret
= pci_request_regions(pdev
, DRV_NAME_ALCOR_PCI
);
276 dev_err(&pdev
->dev
, "Cannot request region\n");
280 if (!(pci_resource_flags(pdev
, bar
) & IORESOURCE_MEM
)) {
281 dev_err(&pdev
->dev
, "BAR %d is not iomem. Aborting.\n", bar
);
283 goto error_release_regions
;
286 priv
->iobase
= pcim_iomap(pdev
, bar
, 0);
289 goto error_release_regions
;
292 /* make sure irqs are disabled */
293 alcor_write32(priv
, 0, AU6601_REG_INT_ENABLE
);
294 alcor_write32(priv
, 0, AU6601_MS_INT_ENABLE
);
296 ret
= dma_set_mask_and_coherent(priv
->dev
, AU6601_SDMA_MASK
);
298 dev_err(priv
->dev
, "Failed to set DMA mask\n");
299 goto error_release_regions
;
302 pci_set_master(pdev
);
303 pci_set_drvdata(pdev
, priv
);
304 alcor_pci_init_check_aspm(priv
);
306 for (i
= 0; i
< ARRAY_SIZE(alcor_pci_cells
); i
++) {
307 alcor_pci_cells
[i
].platform_data
= priv
;
308 alcor_pci_cells
[i
].pdata_size
= sizeof(*priv
);
310 ret
= mfd_add_devices(&pdev
->dev
, priv
->id
, alcor_pci_cells
,
311 ARRAY_SIZE(alcor_pci_cells
), NULL
, 0, NULL
);
313 goto error_release_regions
;
315 alcor_pci_aspm_ctrl(priv
, 0);
319 error_release_regions
:
320 pci_release_regions(pdev
);
324 static void alcor_pci_remove(struct pci_dev
*pdev
)
326 struct alcor_pci_priv
*priv
;
328 priv
= pci_get_drvdata(pdev
);
330 alcor_pci_aspm_ctrl(priv
, 1);
332 mfd_remove_devices(&pdev
->dev
);
334 ida_simple_remove(&alcor_pci_idr
, priv
->id
);
336 pci_release_regions(pdev
);
337 pci_set_drvdata(pdev
, NULL
);
340 #ifdef CONFIG_PM_SLEEP
341 static int alcor_suspend(struct device
*dev
)
343 struct alcor_pci_priv
*priv
= dev_get_drvdata(dev
);
345 alcor_pci_aspm_ctrl(priv
, 1);
349 static int alcor_resume(struct device
*dev
)
352 struct alcor_pci_priv
*priv
= dev_get_drvdata(dev
);
354 alcor_pci_aspm_ctrl(priv
, 0);
357 #endif /* CONFIG_PM_SLEEP */
359 static SIMPLE_DEV_PM_OPS(alcor_pci_pm_ops
, alcor_suspend
, alcor_resume
);
361 static struct pci_driver alcor_driver
= {
362 .name
= DRV_NAME_ALCOR_PCI
,
364 .probe
= alcor_pci_probe
,
365 .remove
= alcor_pci_remove
,
367 .pm
= &alcor_pci_pm_ops
371 module_pci_driver(alcor_driver
);
373 MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
374 MODULE_DESCRIPTION("PCI driver for Alcor Micro AU6601 Secure Digital Host Controller Interface");
375 MODULE_LICENSE("GPL");