diff mbox series

[v6,1/2] misc: cardreader: add new Alcor Micro Cardreader PCI driver

Message ID 20181202103046.29377-1-linux@rempel-privat.de (mailing list archive)
State New, archived
Headers show
Series [v6,1/2] misc: cardreader: add new Alcor Micro Cardreader PCI driver | expand

Commit Message

Oleksij Rempel Dec. 2, 2018, 10:30 a.m. UTC
This driver provides support for Alcor Micro AU6601 and AU6621
card readers.

This is single LUN HW and it is expected to work with following standards:
- Support SDR104 / SDR50
- MultiMedia Card (MMC)
- Memory Stick (MS)
- Memory Stick PRO (MS_Pro)

Since it is a PCIe controller, it should work on any architecture
supporting PCIe. For now, it was developed and tested only on x86_64.

This driver is a result of RE work and was created without any
documentation or real knowledge of HW internals.

Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
---
 drivers/misc/Makefile               |   2 +-
 drivers/misc/cardreader/Kconfig     |  11 +
 drivers/misc/cardreader/Makefile    |   4 +-
 drivers/misc/cardreader/alcor_pci.c | 371 ++++++++++++++++++++++++++++
 include/linux/alcor_pci.h           | 286 +++++++++++++++++++++
 5 files changed, 671 insertions(+), 3 deletions(-)
 create mode 100644 drivers/misc/cardreader/alcor_pci.c
 create mode 100644 include/linux/alcor_pci.h

Comments

Oleksij Rempel Dec. 2, 2018, 10:44 a.m. UTC | #1
Am 02.12.18 um 11:30 schrieb Oleksij Rempel:
> This driver provides support for Alcor Micro AU6601 and AU6621
> card readers.
> 
> This is single LUN HW and it is expected to work with following standards:
> - Support SDR104 / SDR50
> - MultiMedia Card (MMC)
> - Memory Stick (MS)
> - Memory Stick PRO (MS_Pro)
> 
> Since it is a PCIe controller, it should work on any architecture
> supporting PCIe. For now, it was developed and tested only on x86_64.
> 
> This driver is a result of RE work and was created without any
> documentation or real knowledge of HW internals.
> 
> Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>

Forgot to add a changelog for this patchset:
20181202 v6:
- split one patch to two patches: cardreader and mmc
- use SIMPLE_DEV_PM_OPS in mmc driver
- use ida_simple_get/remove
- move DRV_NAME_ALCOR_PCI to cardreader driver. Other defines are shared
between drivers.
- fix some typos
- rebase against 4.20-rc4 and test it.
Ulf Hansson Dec. 5, 2018, 2:24 p.m. UTC | #2
On Sun, 2 Dec 2018 at 11:31, Oleksij Rempel <linux@rempel-privat.de> wrote:
>
> This driver provides support for Alcor Micro AU6601 and AU6621
> card readers.
>
> This is single LUN HW and it is expected to work with following standards:
> - Support SDR104 / SDR50
> - MultiMedia Card (MMC)
> - Memory Stick (MS)
> - Memory Stick PRO (MS_Pro)
>
> Since it is a PCIe controller, it should work on any architecture
> supporting PCIe. For now, it was developed and tested only on x86_64.
>
> This driver is a result of RE work and was created without any
> documentation or real knowledge of HW internals.
>
> Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>

Applied for next, thanks!

Kind regards
Uffe

> ---
>  drivers/misc/Makefile               |   2 +-
>  drivers/misc/cardreader/Kconfig     |  11 +
>  drivers/misc/cardreader/Makefile    |   4 +-
>  drivers/misc/cardreader/alcor_pci.c | 371 ++++++++++++++++++++++++++++
>  include/linux/alcor_pci.h           | 286 +++++++++++++++++++++
>  5 files changed, 671 insertions(+), 3 deletions(-)
>  create mode 100644 drivers/misc/cardreader/alcor_pci.c
>  create mode 100644 include/linux/alcor_pci.h
>
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index af22bbc3d00c..fe3134cf3008 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -57,4 +57,4 @@ obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o
>  obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o
>  obj-$(CONFIG_PCI_ENDPOINT_TEST)        += pci_endpoint_test.o
>  obj-$(CONFIG_OCXL)             += ocxl/
> -obj-$(CONFIG_MISC_RTSX)                += cardreader/
> +obj-y          += cardreader/
> diff --git a/drivers/misc/cardreader/Kconfig b/drivers/misc/cardreader/Kconfig
> index 69e815e32a8c..ed8993b5d058 100644
> --- a/drivers/misc/cardreader/Kconfig
> +++ b/drivers/misc/cardreader/Kconfig
> @@ -1,3 +1,14 @@
> +config MISC_ALCOR_PCI
> +       tristate "Alcor Micro/Alcor Link PCI-E card reader"
> +       depends on PCI
> +       select MFD_CORE
> +       help
> +         This supports for Alcor Micro PCI-Express card reader including au6601,
> +         au6621.
> +         Alcor Micro card readers support access to many types of memory cards,
> +         such as Memory Stick, Memory Stick Pro, Secure Digital and
> +         MultiMediaCard.
> +
>  config MISC_RTSX_PCI
>         tristate "Realtek PCI-E card reader"
>         depends on PCI
> diff --git a/drivers/misc/cardreader/Makefile b/drivers/misc/cardreader/Makefile
> index 9fabfcc6fa7a..9882d2a1025c 100644
> --- a/drivers/misc/cardreader/Makefile
> +++ b/drivers/misc/cardreader/Makefile
> @@ -1,4 +1,4 @@
> -rtsx_pci-objs := rtsx_pcr.o rts5209.o rts5229.o rtl8411.o rts5227.o rts5249.o rts5260.o
> -
> +obj-$(CONFIG_MISC_ALCOR_PCI)   += alcor_pci.o
>  obj-$(CONFIG_MISC_RTSX_PCI)    += rtsx_pci.o
> +rtsx_pci-objs := rtsx_pcr.o rts5209.o rts5229.o rtl8411.o rts5227.o rts5249.o rts5260.o
>  obj-$(CONFIG_MISC_RTSX_USB)    += rtsx_usb.o
> diff --git a/drivers/misc/cardreader/alcor_pci.c b/drivers/misc/cardreader/alcor_pci.c
> new file mode 100644
> index 000000000000..6872b8e29b4d
> --- /dev/null
> +++ b/drivers/misc/cardreader/alcor_pci.c
> @@ -0,0 +1,371 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2018 Oleksij Rempel <linux@rempel-privat.de>
> + *
> + * Driver for Alcor Micro AU6601 and AU6621 controllers
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/irq.h>
> +#include <linux/mfd/core.h>
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm.h>
> +
> +#include <linux/alcor_pci.h>
> +
> +#define DRV_NAME_ALCOR_PCI                     "alcor_pci"
> +
> +static DEFINE_IDA(alcor_pci_idr);
> +
> +static struct mfd_cell alcor_pci_cells[] = {
> +       [ALCOR_SD_CARD] = {
> +               .name = DRV_NAME_ALCOR_PCI_SDMMC,
> +       },
> +       [ALCOR_MS_CARD] = {
> +               .name = DRV_NAME_ALCOR_PCI_MS,
> +       },
> +};
> +
> +static const struct alcor_dev_cfg alcor_cfg = {
> +       .dma = 0,
> +};
> +
> +static const struct alcor_dev_cfg au6621_cfg = {
> +       .dma = 1,
> +};
> +
> +static const struct pci_device_id pci_ids[] = {
> +       { PCI_DEVICE(PCI_ID_ALCOR_MICRO, PCI_ID_AU6601),
> +               .driver_data = (kernel_ulong_t)&alcor_cfg },
> +       { PCI_DEVICE(PCI_ID_ALCOR_MICRO, PCI_ID_AU6621),
> +               .driver_data = (kernel_ulong_t)&au6621_cfg },
> +       { },
> +};
> +MODULE_DEVICE_TABLE(pci, pci_ids);
> +
> +void alcor_write8(struct alcor_pci_priv *priv, u8 val, unsigned int addr)
> +{
> +       writeb(val, priv->iobase + addr);
> +}
> +EXPORT_SYMBOL_GPL(alcor_write8);
> +
> +void alcor_write16(struct alcor_pci_priv *priv, u16 val, unsigned int addr)
> +{
> +       writew(val, priv->iobase + addr);
> +}
> +EXPORT_SYMBOL_GPL(alcor_write16);
> +
> +void alcor_write32(struct alcor_pci_priv *priv, u32 val, unsigned int addr)
> +{
> +       writel(val, priv->iobase + addr);
> +}
> +EXPORT_SYMBOL_GPL(alcor_write32);
> +
> +void alcor_write32be(struct alcor_pci_priv *priv, u32 val, unsigned int addr)
> +{
> +       iowrite32be(val, priv->iobase + addr);
> +}
> +EXPORT_SYMBOL_GPL(alcor_write32be);
> +
> +u8 alcor_read8(struct alcor_pci_priv *priv, unsigned int addr)
> +{
> +       return readb(priv->iobase + addr);
> +}
> +EXPORT_SYMBOL_GPL(alcor_read8);
> +
> +u32 alcor_read32(struct alcor_pci_priv *priv, unsigned int addr)
> +{
> +       return readl(priv->iobase + addr);
> +}
> +EXPORT_SYMBOL_GPL(alcor_read32);
> +
> +u32 alcor_read32be(struct alcor_pci_priv *priv, unsigned int addr)
> +{
> +       return ioread32be(priv->iobase + addr);
> +}
> +EXPORT_SYMBOL_GPL(alcor_read32be);
> +
> +static int alcor_pci_find_cap_offset(struct alcor_pci_priv *priv,
> +                                    struct pci_dev *pci)
> +{
> +       int where;
> +       u8 val8;
> +       u32 val32;
> +
> +       where = ALCOR_CAP_START_OFFSET;
> +       pci_read_config_byte(pci, where, &val8);
> +       if (!val8)
> +               return 0;
> +
> +       where = (int)val8;
> +       while (1) {
> +               pci_read_config_dword(pci, where, &val32);
> +               if (val32 == 0xffffffff) {
> +                       dev_dbg(priv->dev, "find_cap_offset invailid value %x.\n",
> +                               val32);
> +                       return 0;
> +               }
> +
> +               if ((val32 & 0xff) == 0x10) {
> +                       dev_dbg(priv->dev, "pcie cap offset: %x\n", where);
> +                       return where;
> +               }
> +
> +               if ((val32 & 0xff00) == 0x00) {
> +                       dev_dbg(priv->dev, "pci_find_cap_offset invailid value %x.\n",
> +                               val32);
> +                       break;
> +               }
> +               where = (int)((val32 >> 8) & 0xff);
> +       }
> +
> +       return 0;
> +}
> +
> +static void alcor_pci_init_check_aspm(struct alcor_pci_priv *priv)
> +{
> +       struct pci_dev *pci;
> +       int where;
> +       u32 val32;
> +
> +       priv->pdev_cap_off    = alcor_pci_find_cap_offset(priv, priv->pdev);
> +       priv->parent_cap_off = alcor_pci_find_cap_offset(priv,
> +                                                        priv->parent_pdev);
> +
> +       if ((priv->pdev_cap_off == 0) || (priv->parent_cap_off == 0)) {
> +               dev_dbg(priv->dev, "pci_cap_off: %x, parent_cap_off: %x\n",
> +                       priv->pdev_cap_off, priv->parent_cap_off);
> +               return;
> +       }
> +
> +       /* link capability */
> +       pci   = priv->pdev;
> +       where = priv->pdev_cap_off + ALCOR_PCIE_LINK_CAP_OFFSET;
> +       pci_read_config_dword(pci, where, &val32);
> +       priv->pdev_aspm_cap = (u8)(val32 >> 10) & 0x03;
> +
> +       pci   = priv->parent_pdev;
> +       where = priv->parent_cap_off + ALCOR_PCIE_LINK_CAP_OFFSET;
> +       pci_read_config_dword(pci, where, &val32);
> +       priv->parent_aspm_cap = (u8)(val32 >> 10) & 0x03;
> +
> +       if (priv->pdev_aspm_cap != priv->parent_aspm_cap) {
> +               u8 aspm_cap;
> +
> +               dev_dbg(priv->dev, "pdev_aspm_cap: %x, parent_aspm_cap: %x\n",
> +                       priv->pdev_aspm_cap, priv->parent_aspm_cap);
> +               aspm_cap = priv->pdev_aspm_cap & priv->parent_aspm_cap;
> +               priv->pdev_aspm_cap    = aspm_cap;
> +               priv->parent_aspm_cap = aspm_cap;
> +       }
> +
> +       dev_dbg(priv->dev, "ext_config_dev_aspm: %x, pdev_aspm_cap: %x\n",
> +               priv->ext_config_dev_aspm, priv->pdev_aspm_cap);
> +       priv->ext_config_dev_aspm &= priv->pdev_aspm_cap;
> +}
> +
> +static void alcor_pci_aspm_ctrl(struct alcor_pci_priv *priv, u8 aspm_enable)
> +{
> +       struct pci_dev *pci;
> +       u8 aspm_ctrl, i;
> +       int where;
> +       u32 val32;
> +
> +       if ((!priv->pdev_cap_off) || (!priv->parent_cap_off)) {
> +               dev_dbg(priv->dev, "pci_cap_off: %x, parent_cap_off: %x\n",
> +                       priv->pdev_cap_off, priv->parent_cap_off);
> +               return;
> +       }
> +
> +       if (!priv->pdev_aspm_cap)
> +               return;
> +
> +       aspm_ctrl = 0;
> +       if (aspm_enable) {
> +               aspm_ctrl = priv->ext_config_dev_aspm;
> +
> +               if (!aspm_ctrl) {
> +                       dev_dbg(priv->dev, "aspm_ctrl == 0\n");
> +                       return;
> +               }
> +       }
> +
> +       for (i = 0; i < 2; i++) {
> +
> +               if (i) {
> +                       pci   = priv->parent_pdev;
> +                       where = priv->parent_cap_off
> +                               + ALCOR_PCIE_LINK_CTRL_OFFSET;
> +               } else {
> +                       pci   = priv->pdev;
> +                       where = priv->pdev_cap_off
> +                               + ALCOR_PCIE_LINK_CTRL_OFFSET;
> +               }
> +
> +               pci_read_config_dword(pci, where, &val32);
> +               val32 &= (~0x03);
> +               val32 |= (aspm_ctrl & priv->pdev_aspm_cap);
> +               pci_write_config_byte(pci, where, (u8)val32);
> +       }
> +
> +}
> +
> +static inline void alcor_mask_sd_irqs(struct alcor_pci_priv *priv)
> +{
> +       alcor_write32(priv, 0, AU6601_REG_INT_ENABLE);
> +}
> +
> +static inline void alcor_unmask_sd_irqs(struct alcor_pci_priv *priv)
> +{
> +       alcor_write32(priv, AU6601_INT_CMD_MASK | AU6601_INT_DATA_MASK |
> +                 AU6601_INT_CARD_INSERT | AU6601_INT_CARD_REMOVE |
> +                 AU6601_INT_OVER_CURRENT_ERR,
> +                 AU6601_REG_INT_ENABLE);
> +}
> +
> +static inline void alcor_mask_ms_irqs(struct alcor_pci_priv *priv)
> +{
> +       alcor_write32(priv, 0, AU6601_MS_INT_ENABLE);
> +}
> +
> +static inline void alcor_unmask_ms_irqs(struct alcor_pci_priv *priv)
> +{
> +       alcor_write32(priv, 0x3d00fa, AU6601_MS_INT_ENABLE);
> +}
> +
> +static int alcor_pci_probe(struct pci_dev *pdev,
> +                          const struct pci_device_id *ent)
> +{
> +       struct alcor_dev_cfg *cfg;
> +       struct alcor_pci_priv *priv;
> +       int ret, i, bar = 0;
> +
> +       cfg = (void *)ent->driver_data;
> +
> +       ret = pcim_enable_device(pdev);
> +       if (ret)
> +               return ret;
> +
> +       priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> +       if (!priv)
> +               return -ENOMEM;
> +
> +       ret = ida_simple_get(&alcor_pci_idr, 0, 0, GFP_KERNEL);
> +       if (ret < 0)
> +               return ret;
> +       priv->id = ret;
> +
> +       priv->pdev = pdev;
> +       priv->parent_pdev = pdev->bus->self;
> +       priv->dev = &pdev->dev;
> +       priv->cfg = cfg;
> +       priv->irq = pdev->irq;
> +
> +       ret = pci_request_regions(pdev, DRV_NAME_ALCOR_PCI);
> +       if (ret) {
> +               dev_err(&pdev->dev, "Cannot request region\n");
> +               return -ENOMEM;
> +       }
> +
> +       if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) {
> +               dev_err(&pdev->dev, "BAR %d is not iomem. Aborting.\n", bar);
> +               ret = -ENODEV;
> +               goto error_release_regions;
> +       }
> +
> +       priv->iobase = pcim_iomap(pdev, bar, 0);
> +       if (!priv->iobase) {
> +               ret = -ENOMEM;
> +               goto error_release_regions;
> +       }
> +
> +       /* make sure irqs are disabled */
> +       alcor_write32(priv, 0, AU6601_REG_INT_ENABLE);
> +       alcor_write32(priv, 0, AU6601_MS_INT_ENABLE);
> +
> +       ret = dma_set_mask_and_coherent(priv->dev, AU6601_SDMA_MASK);
> +       if (ret) {
> +               dev_err(priv->dev, "Failed to set DMA mask\n");
> +               goto error_release_regions;
> +       }
> +
> +       pci_set_master(pdev);
> +       pci_set_drvdata(pdev, priv);
> +       alcor_pci_init_check_aspm(priv);
> +
> +       for (i = 0; i < ARRAY_SIZE(alcor_pci_cells); i++) {
> +               alcor_pci_cells[i].platform_data = priv;
> +               alcor_pci_cells[i].pdata_size = sizeof(*priv);
> +       }
> +       ret = mfd_add_devices(&pdev->dev, priv->id, alcor_pci_cells,
> +                       ARRAY_SIZE(alcor_pci_cells), NULL, 0, NULL);
> +       if (ret < 0)
> +               goto error_release_regions;
> +
> +       alcor_pci_aspm_ctrl(priv, 0);
> +
> +       return 0;
> +
> +error_release_regions:
> +       pci_release_regions(pdev);
> +       return ret;
> +}
> +
> +static void alcor_pci_remove(struct pci_dev *pdev)
> +{
> +       struct alcor_pci_priv *priv;
> +
> +       priv = pci_get_drvdata(pdev);
> +
> +       alcor_pci_aspm_ctrl(priv, 1);
> +
> +       mfd_remove_devices(&pdev->dev);
> +
> +       ida_simple_remove(&alcor_pci_idr, priv->id);
> +
> +       pci_release_regions(pdev);
> +       pci_set_drvdata(pdev, NULL);
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int alcor_suspend(struct device *dev)
> +{
> +       struct pci_dev *pdev = to_pci_dev(dev);
> +       struct alcor_pci_priv *priv = pci_get_drvdata(pdev);
> +
> +       alcor_pci_aspm_ctrl(priv, 1);
> +       return 0;
> +}
> +
> +static int alcor_resume(struct device *dev)
> +{
> +
> +       struct pci_dev *pdev = to_pci_dev(dev);
> +       struct alcor_pci_priv *priv = pci_get_drvdata(pdev);
> +
> +       alcor_pci_aspm_ctrl(priv, 0);
> +       return 0;
> +}
> +#endif /* CONFIG_PM_SLEEP */
> +
> +static SIMPLE_DEV_PM_OPS(alcor_pci_pm_ops, alcor_suspend, alcor_resume);
> +
> +static struct pci_driver alcor_driver = {
> +       .name   =       DRV_NAME_ALCOR_PCI,
> +       .id_table =     pci_ids,
> +       .probe  =       alcor_pci_probe,
> +       .remove =       alcor_pci_remove,
> +       .driver =       {
> +               .pm     = &alcor_pci_pm_ops
> +       },
> +};
> +
> +module_pci_driver(alcor_driver);
> +
> +MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
> +MODULE_DESCRIPTION("PCI driver for Alcor Micro AU6601 Secure Digital Host Controller Interface");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/alcor_pci.h b/include/linux/alcor_pci.h
> new file mode 100644
> index 000000000000..da973e8a2da8
> --- /dev/null
> +++ b/include/linux/alcor_pci.h
> @@ -0,0 +1,286 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2018 Oleksij Rempel <linux@rempel-privat.de>
> + *
> + * Driver for Alcor Micro AU6601 and AU6621 controllers
> + */
> +
> +#ifndef __ALCOR_PCI_H
> +#define __ALCOR_PCI_H
> +
> +#define ALCOR_SD_CARD 0
> +#define ALCOR_MS_CARD 1
> +
> +#define DRV_NAME_ALCOR_PCI_SDMMC               "alcor_sdmmc"
> +#define DRV_NAME_ALCOR_PCI_MS                  "alcor_ms"
> +
> +#define PCI_ID_ALCOR_MICRO                     0x1AEA
> +#define PCI_ID_AU6601                          0x6601
> +#define PCI_ID_AU6621                          0x6621
> +
> +#define MHZ_TO_HZ(freq)                                ((freq) * 1000 * 1000)
> +
> +#define AU6601_BASE_CLOCK                      31000000
> +#define AU6601_MIN_CLOCK                       150000
> +#define AU6601_MAX_CLOCK                       208000000
> +#define AU6601_MAX_DMA_SEGMENTS                        1
> +#define AU6601_MAX_PIO_SEGMENTS                        1
> +#define AU6601_MAX_DMA_BLOCK_SIZE              0x1000
> +#define AU6601_MAX_PIO_BLOCK_SIZE              0x200
> +#define AU6601_MAX_DMA_BLOCKS                  1
> +#define AU6601_DMA_LOCAL_SEGMENTS              1
> +
> +/* registers spotter by reverse engineering but still
> + * with unknown functionality:
> + * 0x10 - ADMA phy address. AU6621 only?
> + * 0x51 - LED ctrl?
> + * 0x52 - unknown
> + * 0x61 - LED related? Always toggled BIT0
> + * 0x63 - Same as 0x61?
> + * 0x77 - unknown
> + */
> +
> +/* SDMA phy address. Higher then 0x0800.0000?
> + * The au6601 and au6621 have different DMA engines with different issues. One
> + * For example au6621 engine is triggered by addr change. No other interaction
> + * is needed. This means, if we get two buffers with same address, then engine
> + * will stall.
> + */
> +#define AU6601_REG_SDMA_ADDR                   0x00
> +#define AU6601_SDMA_MASK                       0xffffffff
> +
> +#define AU6601_DMA_BOUNDARY                    0x05
> +#define AU6621_DMA_PAGE_CNT                    0x05
> +/* PIO */
> +#define AU6601_REG_BUFFER                      0x08
> +/* ADMA ctrl? AU6621 only. */
> +#define AU6621_DMA_CTRL                                0x0c
> +#define AU6621_DMA_ENABLE                      BIT(0)
> +/* CMD index */
> +#define AU6601_REG_CMD_OPCODE                  0x23
> +/* CMD parametr */
> +#define AU6601_REG_CMD_ARG                     0x24
> +/* CMD response 4x4 Bytes */
> +#define AU6601_REG_CMD_RSP0                    0x30
> +#define AU6601_REG_CMD_RSP1                    0x34
> +#define AU6601_REG_CMD_RSP2                    0x38
> +#define AU6601_REG_CMD_RSP3                    0x3C
> +/* default timeout set to 125: 125 * 40ms = 5 sec
> + * how exactly it is calculated?
> + */
> +#define AU6601_TIME_OUT_CTRL                   0x69
> +/* Block size for SDMA or PIO */
> +#define AU6601_REG_BLOCK_SIZE                  0x6c
> +/* Some power related reg, used together with AU6601_OUTPUT_ENABLE */
> +#define AU6601_POWER_CONTROL                   0x70
> +
> +/* PLL ctrl */
> +#define AU6601_CLK_SELECT                      0x72
> +#define        AU6601_CLK_OVER_CLK                     0x80
> +#define        AU6601_CLK_384_MHZ                      0x30
> +#define        AU6601_CLK_125_MHZ                      0x20
> +#define        AU6601_CLK_48_MHZ                       0x10
> +#define        AU6601_CLK_EXT_PLL                      0x04
> +#define AU6601_CLK_X2_MODE                     0x02
> +#define AU6601_CLK_ENABLE                      0x01
> +#define AU6601_CLK_31_25_MHZ                   0x00
> +
> +#define AU6601_CLK_DIVIDER                     0x73
> +
> +#define AU6601_INTERFACE_MODE_CTRL             0x74
> +#define AU6601_DLINK_MODE                      0x80
> +#define        AU6601_INTERRUPT_DELAY_TIME             0x40
> +#define        AU6601_SIGNAL_REQ_CTRL                  0x30
> +#define AU6601_MS_CARD_WP                      BIT(3)
> +#define AU6601_SD_CARD_WP                      BIT(0)
> +
> +/* same register values are used for:
> + *  - AU6601_OUTPUT_ENABLE
> + *  - AU6601_POWER_CONTROL
> + */
> +#define AU6601_ACTIVE_CTRL                     0x75
> +#define AU6601_XD_CARD                         BIT(4)
> +/* AU6601_MS_CARD_ACTIVE - will cativate MS card section? */
> +#define AU6601_MS_CARD                         BIT(3)
> +#define AU6601_SD_CARD                         BIT(0)
> +
> +/* card slot state. It should automatically detect type of
> + * the card
> + */
> +#define AU6601_DETECT_STATUS                   0x76
> +#define AU6601_DETECT_EN                       BIT(7)
> +#define AU6601_MS_DETECTED                     BIT(3)
> +#define AU6601_SD_DETECTED                     BIT(0)
> +#define AU6601_DETECT_STATUS_M                 0xf
> +
> +#define AU6601_REG_SW_RESET                    0x79
> +#define AU6601_BUF_CTRL_RESET                  BIT(7)
> +#define AU6601_RESET_DATA                      BIT(3)
> +#define AU6601_RESET_CMD                       BIT(0)
> +
> +#define AU6601_OUTPUT_ENABLE                   0x7a
> +
> +#define AU6601_PAD_DRIVE0                      0x7b
> +#define AU6601_PAD_DRIVE1                      0x7c
> +#define AU6601_PAD_DRIVE2                      0x7d
> +/* read EEPROM? */
> +#define AU6601_FUNCTION                                0x7f
> +
> +#define AU6601_CMD_XFER_CTRL                   0x81
> +#define        AU6601_CMD_17_BYTE_CRC                  0xc0
> +#define        AU6601_CMD_6_BYTE_WO_CRC                0x80
> +#define        AU6601_CMD_6_BYTE_CRC                   0x40
> +#define        AU6601_CMD_START_XFER                   0x20
> +#define        AU6601_CMD_STOP_WAIT_RDY                0x10
> +#define        AU6601_CMD_NO_RESP                      0x00
> +
> +#define AU6601_REG_BUS_CTRL                    0x82
> +#define AU6601_BUS_WIDTH_4BIT                  0x20
> +#define AU6601_BUS_WIDTH_8BIT                  0x10
> +#define AU6601_BUS_WIDTH_1BIT                  0x00
> +
> +#define AU6601_DATA_XFER_CTRL                  0x83
> +#define AU6601_DATA_WRITE                      BIT(7)
> +#define AU6601_DATA_DMA_MODE                   BIT(6)
> +#define AU6601_DATA_START_XFER                 BIT(0)
> +
> +#define AU6601_DATA_PIN_STATE                  0x84
> +#define AU6601_BUS_STAT_CMD                    BIT(15)
> +/* BIT(4) - BIT(7) are permanently 1.
> + * May be reserved or not attached DAT4-DAT7
> + */
> +#define AU6601_BUS_STAT_DAT3                   BIT(3)
> +#define AU6601_BUS_STAT_DAT2                   BIT(2)
> +#define AU6601_BUS_STAT_DAT1                   BIT(1)
> +#define AU6601_BUS_STAT_DAT0                   BIT(0)
> +#define AU6601_BUS_STAT_DAT_MASK               0xf
> +
> +#define AU6601_OPT                             0x85
> +#define        AU6601_OPT_CMD_LINE_LEVEL               0x80
> +#define        AU6601_OPT_NCRC_16_CLK                  BIT(4)
> +#define        AU6601_OPT_CMD_NWT                      BIT(3)
> +#define        AU6601_OPT_STOP_CLK                     BIT(2)
> +#define        AU6601_OPT_DDR_MODE                     BIT(1)
> +#define        AU6601_OPT_SD_18V                       BIT(0)
> +
> +#define AU6601_CLK_DELAY                       0x86
> +#define        AU6601_CLK_DATA_POSITIVE_EDGE           0x80
> +#define        AU6601_CLK_CMD_POSITIVE_EDGE            0x40
> +#define        AU6601_CLK_POSITIVE_EDGE_ALL            (AU6601_CLK_CMD_POSITIVE_EDGE \
> +                                               | AU6601_CLK_DATA_POSITIVE_EDGE)
> +
> +
> +#define AU6601_REG_INT_STATUS                  0x90
> +#define AU6601_REG_INT_ENABLE                  0x94
> +#define AU6601_INT_DATA_END_BIT_ERR            BIT(22)
> +#define AU6601_INT_DATA_CRC_ERR                        BIT(21)
> +#define AU6601_INT_DATA_TIMEOUT_ERR            BIT(20)
> +#define AU6601_INT_CMD_INDEX_ERR               BIT(19)
> +#define AU6601_INT_CMD_END_BIT_ERR             BIT(18)
> +#define AU6601_INT_CMD_CRC_ERR                 BIT(17)
> +#define AU6601_INT_CMD_TIMEOUT_ERR             BIT(16)
> +#define AU6601_INT_ERROR                       BIT(15)
> +#define AU6601_INT_OVER_CURRENT_ERR            BIT(8)
> +#define AU6601_INT_CARD_INSERT                 BIT(7)
> +#define AU6601_INT_CARD_REMOVE                 BIT(6)
> +#define AU6601_INT_READ_BUF_RDY                        BIT(5)
> +#define AU6601_INT_WRITE_BUF_RDY               BIT(4)
> +#define AU6601_INT_DMA_END                     BIT(3)
> +#define AU6601_INT_DATA_END                    BIT(1)
> +#define AU6601_INT_CMD_END                     BIT(0)
> +
> +#define AU6601_INT_NORMAL_MASK                 0x00007FFF
> +#define AU6601_INT_ERROR_MASK                  0xFFFF8000
> +
> +#define AU6601_INT_CMD_MASK    (AU6601_INT_CMD_END | \
> +               AU6601_INT_CMD_TIMEOUT_ERR | AU6601_INT_CMD_CRC_ERR | \
> +               AU6601_INT_CMD_END_BIT_ERR | AU6601_INT_CMD_INDEX_ERR)
> +#define AU6601_INT_DATA_MASK   (AU6601_INT_DATA_END | AU6601_INT_DMA_END | \
> +               AU6601_INT_READ_BUF_RDY | AU6601_INT_WRITE_BUF_RDY | \
> +               AU6601_INT_DATA_TIMEOUT_ERR | AU6601_INT_DATA_CRC_ERR | \
> +               AU6601_INT_DATA_END_BIT_ERR)
> +#define AU6601_INT_ALL_MASK                    ((u32)-1)
> +
> +/* MS_CARD mode registers */
> +
> +#define AU6601_MS_STATUS                       0xa0
> +
> +#define AU6601_MS_BUS_MODE_CTRL                        0xa1
> +#define AU6601_MS_BUS_8BIT_MODE                        0x03
> +#define AU6601_MS_BUS_4BIT_MODE                        0x01
> +#define AU6601_MS_BUS_1BIT_MODE                        0x00
> +
> +#define AU6601_MS_TPC_CMD                      0xa2
> +#define AU6601_MS_TPC_READ_PAGE_DATA           0x02
> +#define AU6601_MS_TPC_READ_REG                 0x04
> +#define AU6601_MS_TPC_GET_INT                  0x07
> +#define AU6601_MS_TPC_WRITE_PAGE_DATA          0x0D
> +#define AU6601_MS_TPC_WRITE_REG                        0x0B
> +#define AU6601_MS_TPC_SET_RW_REG_ADRS          0x08
> +#define AU6601_MS_TPC_SET_CMD                  0x0E
> +#define AU6601_MS_TPC_EX_SET_CMD               0x09
> +#define AU6601_MS_TPC_READ_SHORT_DATA          0x03
> +#define AU6601_MS_TPC_WRITE_SHORT_DATA         0x0C
> +
> +#define AU6601_MS_TRANSFER_MODE                        0xa3
> +#define        AU6601_MS_XFER_INT_TIMEOUT_CHK          BIT(2)
> +#define        AU6601_MS_XFER_DMA_ENABLE               BIT(1)
> +#define        AU6601_MS_XFER_START                    BIT(0)
> +
> +#define AU6601_MS_DATA_PIN_STATE               0xa4
> +
> +#define AU6601_MS_INT_STATUS                   0xb0
> +#define AU6601_MS_INT_ENABLE                   0xb4
> +#define AU6601_MS_INT_OVER_CURRENT_ERROR       BIT(23)
> +#define AU6601_MS_INT_DATA_CRC_ERROR           BIT(21)
> +#define AU6601_MS_INT_INT_TIMEOUT              BIT(20)
> +#define AU6601_MS_INT_INT_RESP_ERROR           BIT(19)
> +#define AU6601_MS_INT_CED_ERROR                        BIT(18)
> +#define AU6601_MS_INT_TPC_TIMEOUT              BIT(16)
> +#define AU6601_MS_INT_ERROR                    BIT(15)
> +#define AU6601_MS_INT_CARD_INSERT              BIT(7)
> +#define AU6601_MS_INT_CARD_REMOVE              BIT(6)
> +#define AU6601_MS_INT_BUF_READ_RDY             BIT(5)
> +#define AU6601_MS_INT_BUF_WRITE_RDY            BIT(4)
> +#define AU6601_MS_INT_DMA_END                  BIT(3)
> +#define AU6601_MS_INT_TPC_END                  BIT(1)
> +
> +#define AU6601_MS_INT_DATA_MASK                        0x00000038
> +#define AU6601_MS_INT_TPC_MASK                 0x003d8002
> +#define AU6601_MS_INT_TPC_ERROR                        0x003d0000
> +
> +#define ALCOR_PCIE_LINK_CTRL_OFFSET            0x10
> +#define ALCOR_PCIE_LINK_CAP_OFFSET             0x0c
> +#define ALCOR_CAP_START_OFFSET                 0x34
> +
> +struct alcor_dev_cfg {
> +       u8      dma;
> +};
> +
> +struct alcor_pci_priv {
> +       struct pci_dev *pdev;
> +       struct pci_dev *parent_pdev;
> +       struct  device *dev;
> +       void __iomem *iobase;
> +       unsigned int irq;
> +
> +       unsigned long id; /* idr id */
> +
> +       struct alcor_dev_cfg    *cfg;
> +
> +       /* PCI ASPM related vars */
> +       int pdev_cap_off;
> +       u8  pdev_aspm_cap;
> +       int parent_cap_off;
> +       u8  parent_aspm_cap;
> +       u8 ext_config_dev_aspm;
> +};
> +
> +void alcor_write8(struct alcor_pci_priv *priv, u8 val, unsigned int addr);
> +void alcor_write16(struct alcor_pci_priv *priv, u16 val, unsigned int addr);
> +void alcor_write32(struct alcor_pci_priv *priv, u32 val, unsigned int addr);
> +void alcor_write32be(struct alcor_pci_priv *priv, u32 val, unsigned int addr);
> +u8 alcor_read8(struct alcor_pci_priv *priv, unsigned int addr);
> +u32 alcor_read32(struct alcor_pci_priv *priv, unsigned int addr);
> +u32 alcor_read32be(struct alcor_pci_priv *priv, unsigned int addr);
> +#endif
> --
> 2.17.1
>
diff mbox series

Patch

diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index af22bbc3d00c..fe3134cf3008 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -57,4 +57,4 @@  obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
 obj-$(CONFIG_ASPEED_LPC_SNOOP)	+= aspeed-lpc-snoop.o
 obj-$(CONFIG_PCI_ENDPOINT_TEST)	+= pci_endpoint_test.o
 obj-$(CONFIG_OCXL)		+= ocxl/
-obj-$(CONFIG_MISC_RTSX)		+= cardreader/
+obj-y		+= cardreader/
diff --git a/drivers/misc/cardreader/Kconfig b/drivers/misc/cardreader/Kconfig
index 69e815e32a8c..ed8993b5d058 100644
--- a/drivers/misc/cardreader/Kconfig
+++ b/drivers/misc/cardreader/Kconfig
@@ -1,3 +1,14 @@ 
+config MISC_ALCOR_PCI
+	tristate "Alcor Micro/Alcor Link PCI-E card reader"
+	depends on PCI
+	select MFD_CORE
+	help
+	  This supports for Alcor Micro PCI-Express card reader including au6601,
+	  au6621.
+	  Alcor Micro card readers support access to many types of memory cards,
+	  such as Memory Stick, Memory Stick Pro, Secure Digital and
+	  MultiMediaCard.
+
 config MISC_RTSX_PCI
 	tristate "Realtek PCI-E card reader"
 	depends on PCI
diff --git a/drivers/misc/cardreader/Makefile b/drivers/misc/cardreader/Makefile
index 9fabfcc6fa7a..9882d2a1025c 100644
--- a/drivers/misc/cardreader/Makefile
+++ b/drivers/misc/cardreader/Makefile
@@ -1,4 +1,4 @@ 
-rtsx_pci-objs := rtsx_pcr.o rts5209.o rts5229.o rtl8411.o rts5227.o rts5249.o rts5260.o
-
+obj-$(CONFIG_MISC_ALCOR_PCI)	+= alcor_pci.o
 obj-$(CONFIG_MISC_RTSX_PCI)	+= rtsx_pci.o
+rtsx_pci-objs := rtsx_pcr.o rts5209.o rts5229.o rtl8411.o rts5227.o rts5249.o rts5260.o
 obj-$(CONFIG_MISC_RTSX_USB)	+= rtsx_usb.o
diff --git a/drivers/misc/cardreader/alcor_pci.c b/drivers/misc/cardreader/alcor_pci.c
new file mode 100644
index 000000000000..6872b8e29b4d
--- /dev/null
+++ b/drivers/misc/cardreader/alcor_pci.c
@@ -0,0 +1,371 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 Oleksij Rempel <linux@rempel-privat.de>
+ *
+ * Driver for Alcor Micro AU6601 and AU6621 controllers
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+
+#include <linux/alcor_pci.h>
+
+#define DRV_NAME_ALCOR_PCI			"alcor_pci"
+
+static DEFINE_IDA(alcor_pci_idr);
+
+static struct mfd_cell alcor_pci_cells[] = {
+	[ALCOR_SD_CARD] = {
+		.name = DRV_NAME_ALCOR_PCI_SDMMC,
+	},
+	[ALCOR_MS_CARD] = {
+		.name = DRV_NAME_ALCOR_PCI_MS,
+	},
+};
+
+static const struct alcor_dev_cfg alcor_cfg = {
+	.dma = 0,
+};
+
+static const struct alcor_dev_cfg au6621_cfg = {
+	.dma = 1,
+};
+
+static const struct pci_device_id pci_ids[] = {
+	{ PCI_DEVICE(PCI_ID_ALCOR_MICRO, PCI_ID_AU6601),
+		.driver_data = (kernel_ulong_t)&alcor_cfg },
+	{ PCI_DEVICE(PCI_ID_ALCOR_MICRO, PCI_ID_AU6621),
+		.driver_data = (kernel_ulong_t)&au6621_cfg },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, pci_ids);
+
+void alcor_write8(struct alcor_pci_priv *priv, u8 val, unsigned int addr)
+{
+	writeb(val, priv->iobase + addr);
+}
+EXPORT_SYMBOL_GPL(alcor_write8);
+
+void alcor_write16(struct alcor_pci_priv *priv, u16 val, unsigned int addr)
+{
+	writew(val, priv->iobase + addr);
+}
+EXPORT_SYMBOL_GPL(alcor_write16);
+
+void alcor_write32(struct alcor_pci_priv *priv, u32 val, unsigned int addr)
+{
+	writel(val, priv->iobase + addr);
+}
+EXPORT_SYMBOL_GPL(alcor_write32);
+
+void alcor_write32be(struct alcor_pci_priv *priv, u32 val, unsigned int addr)
+{
+	iowrite32be(val, priv->iobase + addr);
+}
+EXPORT_SYMBOL_GPL(alcor_write32be);
+
+u8 alcor_read8(struct alcor_pci_priv *priv, unsigned int addr)
+{
+	return readb(priv->iobase + addr);
+}
+EXPORT_SYMBOL_GPL(alcor_read8);
+
+u32 alcor_read32(struct alcor_pci_priv *priv, unsigned int addr)
+{
+	return readl(priv->iobase + addr);
+}
+EXPORT_SYMBOL_GPL(alcor_read32);
+
+u32 alcor_read32be(struct alcor_pci_priv *priv, unsigned int addr)
+{
+	return ioread32be(priv->iobase + addr);
+}
+EXPORT_SYMBOL_GPL(alcor_read32be);
+
+static int alcor_pci_find_cap_offset(struct alcor_pci_priv *priv,
+				     struct pci_dev *pci)
+{
+	int where;
+	u8 val8;
+	u32 val32;
+
+	where = ALCOR_CAP_START_OFFSET;
+	pci_read_config_byte(pci, where, &val8);
+	if (!val8)
+		return 0;
+
+	where = (int)val8;
+	while (1) {
+		pci_read_config_dword(pci, where, &val32);
+		if (val32 == 0xffffffff) {
+			dev_dbg(priv->dev, "find_cap_offset invailid value %x.\n",
+				val32);
+			return 0;
+		}
+
+		if ((val32 & 0xff) == 0x10) {
+			dev_dbg(priv->dev, "pcie cap offset: %x\n", where);
+			return where;
+		}
+
+		if ((val32 & 0xff00) == 0x00) {
+			dev_dbg(priv->dev, "pci_find_cap_offset invailid value %x.\n",
+				val32);
+			break;
+		}
+		where = (int)((val32 >> 8) & 0xff);
+	}
+
+	return 0;
+}
+
+static void alcor_pci_init_check_aspm(struct alcor_pci_priv *priv)
+{
+	struct pci_dev *pci;
+	int where;
+	u32 val32;
+
+	priv->pdev_cap_off    = alcor_pci_find_cap_offset(priv, priv->pdev);
+	priv->parent_cap_off = alcor_pci_find_cap_offset(priv,
+							 priv->parent_pdev);
+
+	if ((priv->pdev_cap_off == 0) || (priv->parent_cap_off == 0)) {
+		dev_dbg(priv->dev, "pci_cap_off: %x, parent_cap_off: %x\n",
+			priv->pdev_cap_off, priv->parent_cap_off);
+		return;
+	}
+
+	/* link capability */
+	pci   = priv->pdev;
+	where = priv->pdev_cap_off + ALCOR_PCIE_LINK_CAP_OFFSET;
+	pci_read_config_dword(pci, where, &val32);
+	priv->pdev_aspm_cap = (u8)(val32 >> 10) & 0x03;
+
+	pci   = priv->parent_pdev;
+	where = priv->parent_cap_off + ALCOR_PCIE_LINK_CAP_OFFSET;
+	pci_read_config_dword(pci, where, &val32);
+	priv->parent_aspm_cap = (u8)(val32 >> 10) & 0x03;
+
+	if (priv->pdev_aspm_cap != priv->parent_aspm_cap) {
+		u8 aspm_cap;
+
+		dev_dbg(priv->dev, "pdev_aspm_cap: %x, parent_aspm_cap: %x\n",
+			priv->pdev_aspm_cap, priv->parent_aspm_cap);
+		aspm_cap = priv->pdev_aspm_cap & priv->parent_aspm_cap;
+		priv->pdev_aspm_cap    = aspm_cap;
+		priv->parent_aspm_cap = aspm_cap;
+	}
+
+	dev_dbg(priv->dev, "ext_config_dev_aspm: %x, pdev_aspm_cap: %x\n",
+		priv->ext_config_dev_aspm, priv->pdev_aspm_cap);
+	priv->ext_config_dev_aspm &= priv->pdev_aspm_cap;
+}
+
+static void alcor_pci_aspm_ctrl(struct alcor_pci_priv *priv, u8 aspm_enable)
+{
+	struct pci_dev *pci;
+	u8 aspm_ctrl, i;
+	int where;
+	u32 val32;
+
+	if ((!priv->pdev_cap_off) || (!priv->parent_cap_off)) {
+		dev_dbg(priv->dev, "pci_cap_off: %x, parent_cap_off: %x\n",
+			priv->pdev_cap_off, priv->parent_cap_off);
+		return;
+	}
+
+	if (!priv->pdev_aspm_cap)
+		return;
+
+	aspm_ctrl = 0;
+	if (aspm_enable) {
+		aspm_ctrl = priv->ext_config_dev_aspm;
+
+		if (!aspm_ctrl) {
+			dev_dbg(priv->dev, "aspm_ctrl == 0\n");
+			return;
+		}
+	}
+
+	for (i = 0; i < 2; i++) {
+
+		if (i) {
+			pci   = priv->parent_pdev;
+			where = priv->parent_cap_off
+				+ ALCOR_PCIE_LINK_CTRL_OFFSET;
+		} else {
+			pci   = priv->pdev;
+			where = priv->pdev_cap_off
+				+ ALCOR_PCIE_LINK_CTRL_OFFSET;
+		}
+
+		pci_read_config_dword(pci, where, &val32);
+		val32 &= (~0x03);
+		val32 |= (aspm_ctrl & priv->pdev_aspm_cap);
+		pci_write_config_byte(pci, where, (u8)val32);
+	}
+
+}
+
+static inline void alcor_mask_sd_irqs(struct alcor_pci_priv *priv)
+{
+	alcor_write32(priv, 0, AU6601_REG_INT_ENABLE);
+}
+
+static inline void alcor_unmask_sd_irqs(struct alcor_pci_priv *priv)
+{
+	alcor_write32(priv, AU6601_INT_CMD_MASK | AU6601_INT_DATA_MASK |
+		  AU6601_INT_CARD_INSERT | AU6601_INT_CARD_REMOVE |
+		  AU6601_INT_OVER_CURRENT_ERR,
+		  AU6601_REG_INT_ENABLE);
+}
+
+static inline void alcor_mask_ms_irqs(struct alcor_pci_priv *priv)
+{
+	alcor_write32(priv, 0, AU6601_MS_INT_ENABLE);
+}
+
+static inline void alcor_unmask_ms_irqs(struct alcor_pci_priv *priv)
+{
+	alcor_write32(priv, 0x3d00fa, AU6601_MS_INT_ENABLE);
+}
+
+static int alcor_pci_probe(struct pci_dev *pdev,
+			   const struct pci_device_id *ent)
+{
+	struct alcor_dev_cfg *cfg;
+	struct alcor_pci_priv *priv;
+	int ret, i, bar = 0;
+
+	cfg = (void *)ent->driver_data;
+
+	ret = pcim_enable_device(pdev);
+	if (ret)
+		return ret;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	ret = ida_simple_get(&alcor_pci_idr, 0, 0, GFP_KERNEL);
+	if (ret < 0)
+		return ret;
+	priv->id = ret;
+
+	priv->pdev = pdev;
+	priv->parent_pdev = pdev->bus->self;
+	priv->dev = &pdev->dev;
+	priv->cfg = cfg;
+	priv->irq = pdev->irq;
+
+	ret = pci_request_regions(pdev, DRV_NAME_ALCOR_PCI);
+	if (ret) {
+		dev_err(&pdev->dev, "Cannot request region\n");
+		return -ENOMEM;
+	}
+
+	if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) {
+		dev_err(&pdev->dev, "BAR %d is not iomem. Aborting.\n", bar);
+		ret = -ENODEV;
+		goto error_release_regions;
+	}
+
+	priv->iobase = pcim_iomap(pdev, bar, 0);
+	if (!priv->iobase) {
+		ret = -ENOMEM;
+		goto error_release_regions;
+	}
+
+	/* make sure irqs are disabled */
+	alcor_write32(priv, 0, AU6601_REG_INT_ENABLE);
+	alcor_write32(priv, 0, AU6601_MS_INT_ENABLE);
+
+	ret = dma_set_mask_and_coherent(priv->dev, AU6601_SDMA_MASK);
+	if (ret) {
+		dev_err(priv->dev, "Failed to set DMA mask\n");
+		goto error_release_regions;
+	}
+
+	pci_set_master(pdev);
+	pci_set_drvdata(pdev, priv);
+	alcor_pci_init_check_aspm(priv);
+
+	for (i = 0; i < ARRAY_SIZE(alcor_pci_cells); i++) {
+		alcor_pci_cells[i].platform_data = priv;
+		alcor_pci_cells[i].pdata_size = sizeof(*priv);
+	}
+	ret = mfd_add_devices(&pdev->dev, priv->id, alcor_pci_cells,
+			ARRAY_SIZE(alcor_pci_cells), NULL, 0, NULL);
+	if (ret < 0)
+		goto error_release_regions;
+
+	alcor_pci_aspm_ctrl(priv, 0);
+
+	return 0;
+
+error_release_regions:
+	pci_release_regions(pdev);
+	return ret;
+}
+
+static void alcor_pci_remove(struct pci_dev *pdev)
+{
+	struct alcor_pci_priv *priv;
+
+	priv = pci_get_drvdata(pdev);
+
+	alcor_pci_aspm_ctrl(priv, 1);
+
+	mfd_remove_devices(&pdev->dev);
+
+	ida_simple_remove(&alcor_pci_idr, priv->id);
+
+	pci_release_regions(pdev);
+	pci_set_drvdata(pdev, NULL);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int alcor_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct alcor_pci_priv *priv = pci_get_drvdata(pdev);
+
+	alcor_pci_aspm_ctrl(priv, 1);
+	return 0;
+}
+
+static int alcor_resume(struct device *dev)
+{
+
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct alcor_pci_priv *priv = pci_get_drvdata(pdev);
+
+	alcor_pci_aspm_ctrl(priv, 0);
+	return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static SIMPLE_DEV_PM_OPS(alcor_pci_pm_ops, alcor_suspend, alcor_resume);
+
+static struct pci_driver alcor_driver = {
+	.name	=	DRV_NAME_ALCOR_PCI,
+	.id_table =	pci_ids,
+	.probe	=	alcor_pci_probe,
+	.remove =	alcor_pci_remove,
+	.driver	=	{
+		.pm	= &alcor_pci_pm_ops
+	},
+};
+
+module_pci_driver(alcor_driver);
+
+MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
+MODULE_DESCRIPTION("PCI driver for Alcor Micro AU6601 Secure Digital Host Controller Interface");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/alcor_pci.h b/include/linux/alcor_pci.h
new file mode 100644
index 000000000000..da973e8a2da8
--- /dev/null
+++ b/include/linux/alcor_pci.h
@@ -0,0 +1,286 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2018 Oleksij Rempel <linux@rempel-privat.de>
+ *
+ * Driver for Alcor Micro AU6601 and AU6621 controllers
+ */
+
+#ifndef __ALCOR_PCI_H
+#define __ALCOR_PCI_H
+
+#define ALCOR_SD_CARD 0
+#define ALCOR_MS_CARD 1
+
+#define DRV_NAME_ALCOR_PCI_SDMMC		"alcor_sdmmc"
+#define DRV_NAME_ALCOR_PCI_MS			"alcor_ms"
+
+#define PCI_ID_ALCOR_MICRO			0x1AEA
+#define PCI_ID_AU6601				0x6601
+#define PCI_ID_AU6621				0x6621
+
+#define MHZ_TO_HZ(freq)				((freq) * 1000 * 1000)
+
+#define AU6601_BASE_CLOCK			31000000
+#define AU6601_MIN_CLOCK			150000
+#define AU6601_MAX_CLOCK			208000000
+#define AU6601_MAX_DMA_SEGMENTS			1
+#define AU6601_MAX_PIO_SEGMENTS			1
+#define AU6601_MAX_DMA_BLOCK_SIZE		0x1000
+#define AU6601_MAX_PIO_BLOCK_SIZE		0x200
+#define AU6601_MAX_DMA_BLOCKS			1
+#define AU6601_DMA_LOCAL_SEGMENTS		1
+
+/* registers spotter by reverse engineering but still
+ * with unknown functionality:
+ * 0x10 - ADMA phy address. AU6621 only?
+ * 0x51 - LED ctrl?
+ * 0x52 - unknown
+ * 0x61 - LED related? Always toggled BIT0
+ * 0x63 - Same as 0x61?
+ * 0x77 - unknown
+ */
+
+/* SDMA phy address. Higher then 0x0800.0000?
+ * The au6601 and au6621 have different DMA engines with different issues. One
+ * For example au6621 engine is triggered by addr change. No other interaction
+ * is needed. This means, if we get two buffers with same address, then engine
+ * will stall.
+ */
+#define AU6601_REG_SDMA_ADDR			0x00
+#define AU6601_SDMA_MASK			0xffffffff
+
+#define AU6601_DMA_BOUNDARY			0x05
+#define AU6621_DMA_PAGE_CNT			0x05
+/* PIO */
+#define AU6601_REG_BUFFER			0x08
+/* ADMA ctrl? AU6621 only. */
+#define AU6621_DMA_CTRL				0x0c
+#define AU6621_DMA_ENABLE			BIT(0)
+/* CMD index */
+#define AU6601_REG_CMD_OPCODE			0x23
+/* CMD parametr */
+#define AU6601_REG_CMD_ARG			0x24
+/* CMD response 4x4 Bytes */
+#define AU6601_REG_CMD_RSP0			0x30
+#define AU6601_REG_CMD_RSP1			0x34
+#define AU6601_REG_CMD_RSP2			0x38
+#define AU6601_REG_CMD_RSP3			0x3C
+/* default timeout set to 125: 125 * 40ms = 5 sec
+ * how exactly it is calculated?
+ */
+#define AU6601_TIME_OUT_CTRL			0x69
+/* Block size for SDMA or PIO */
+#define AU6601_REG_BLOCK_SIZE			0x6c
+/* Some power related reg, used together with AU6601_OUTPUT_ENABLE */
+#define AU6601_POWER_CONTROL			0x70
+
+/* PLL ctrl */
+#define AU6601_CLK_SELECT			0x72
+#define	AU6601_CLK_OVER_CLK			0x80
+#define	AU6601_CLK_384_MHZ			0x30
+#define	AU6601_CLK_125_MHZ			0x20
+#define	AU6601_CLK_48_MHZ			0x10
+#define	AU6601_CLK_EXT_PLL			0x04
+#define AU6601_CLK_X2_MODE			0x02
+#define AU6601_CLK_ENABLE			0x01
+#define AU6601_CLK_31_25_MHZ			0x00
+
+#define AU6601_CLK_DIVIDER			0x73
+
+#define AU6601_INTERFACE_MODE_CTRL		0x74
+#define AU6601_DLINK_MODE			0x80
+#define	AU6601_INTERRUPT_DELAY_TIME		0x40
+#define	AU6601_SIGNAL_REQ_CTRL			0x30
+#define AU6601_MS_CARD_WP			BIT(3)
+#define AU6601_SD_CARD_WP			BIT(0)
+
+/* same register values are used for:
+ *  - AU6601_OUTPUT_ENABLE
+ *  - AU6601_POWER_CONTROL
+ */
+#define AU6601_ACTIVE_CTRL			0x75
+#define AU6601_XD_CARD				BIT(4)
+/* AU6601_MS_CARD_ACTIVE - will cativate MS card section? */
+#define AU6601_MS_CARD				BIT(3)
+#define AU6601_SD_CARD				BIT(0)
+
+/* card slot state. It should automatically detect type of
+ * the card
+ */
+#define AU6601_DETECT_STATUS			0x76
+#define AU6601_DETECT_EN			BIT(7)
+#define AU6601_MS_DETECTED			BIT(3)
+#define AU6601_SD_DETECTED			BIT(0)
+#define AU6601_DETECT_STATUS_M			0xf
+
+#define AU6601_REG_SW_RESET			0x79
+#define AU6601_BUF_CTRL_RESET			BIT(7)
+#define AU6601_RESET_DATA			BIT(3)
+#define AU6601_RESET_CMD			BIT(0)
+
+#define AU6601_OUTPUT_ENABLE			0x7a
+
+#define AU6601_PAD_DRIVE0			0x7b
+#define AU6601_PAD_DRIVE1			0x7c
+#define AU6601_PAD_DRIVE2			0x7d
+/* read EEPROM? */
+#define AU6601_FUNCTION				0x7f
+
+#define AU6601_CMD_XFER_CTRL			0x81
+#define	AU6601_CMD_17_BYTE_CRC			0xc0
+#define	AU6601_CMD_6_BYTE_WO_CRC		0x80
+#define	AU6601_CMD_6_BYTE_CRC			0x40
+#define	AU6601_CMD_START_XFER			0x20
+#define	AU6601_CMD_STOP_WAIT_RDY		0x10
+#define	AU6601_CMD_NO_RESP			0x00
+
+#define AU6601_REG_BUS_CTRL			0x82
+#define AU6601_BUS_WIDTH_4BIT			0x20
+#define AU6601_BUS_WIDTH_8BIT			0x10
+#define AU6601_BUS_WIDTH_1BIT			0x00
+
+#define AU6601_DATA_XFER_CTRL			0x83
+#define AU6601_DATA_WRITE			BIT(7)
+#define AU6601_DATA_DMA_MODE			BIT(6)
+#define AU6601_DATA_START_XFER			BIT(0)
+
+#define AU6601_DATA_PIN_STATE			0x84
+#define AU6601_BUS_STAT_CMD			BIT(15)
+/* BIT(4) - BIT(7) are permanently 1.
+ * May be reserved or not attached DAT4-DAT7
+ */
+#define AU6601_BUS_STAT_DAT3			BIT(3)
+#define AU6601_BUS_STAT_DAT2			BIT(2)
+#define AU6601_BUS_STAT_DAT1			BIT(1)
+#define AU6601_BUS_STAT_DAT0			BIT(0)
+#define AU6601_BUS_STAT_DAT_MASK		0xf
+
+#define AU6601_OPT				0x85
+#define	AU6601_OPT_CMD_LINE_LEVEL		0x80
+#define	AU6601_OPT_NCRC_16_CLK			BIT(4)
+#define	AU6601_OPT_CMD_NWT			BIT(3)
+#define	AU6601_OPT_STOP_CLK			BIT(2)
+#define	AU6601_OPT_DDR_MODE			BIT(1)
+#define	AU6601_OPT_SD_18V			BIT(0)
+
+#define AU6601_CLK_DELAY			0x86
+#define	AU6601_CLK_DATA_POSITIVE_EDGE		0x80
+#define	AU6601_CLK_CMD_POSITIVE_EDGE		0x40
+#define	AU6601_CLK_POSITIVE_EDGE_ALL		(AU6601_CLK_CMD_POSITIVE_EDGE \
+						| AU6601_CLK_DATA_POSITIVE_EDGE)
+
+
+#define AU6601_REG_INT_STATUS			0x90
+#define AU6601_REG_INT_ENABLE			0x94
+#define AU6601_INT_DATA_END_BIT_ERR		BIT(22)
+#define AU6601_INT_DATA_CRC_ERR			BIT(21)
+#define AU6601_INT_DATA_TIMEOUT_ERR		BIT(20)
+#define AU6601_INT_CMD_INDEX_ERR		BIT(19)
+#define AU6601_INT_CMD_END_BIT_ERR		BIT(18)
+#define AU6601_INT_CMD_CRC_ERR			BIT(17)
+#define AU6601_INT_CMD_TIMEOUT_ERR		BIT(16)
+#define AU6601_INT_ERROR			BIT(15)
+#define AU6601_INT_OVER_CURRENT_ERR		BIT(8)
+#define AU6601_INT_CARD_INSERT			BIT(7)
+#define AU6601_INT_CARD_REMOVE			BIT(6)
+#define AU6601_INT_READ_BUF_RDY			BIT(5)
+#define AU6601_INT_WRITE_BUF_RDY		BIT(4)
+#define AU6601_INT_DMA_END			BIT(3)
+#define AU6601_INT_DATA_END			BIT(1)
+#define AU6601_INT_CMD_END			BIT(0)
+
+#define AU6601_INT_NORMAL_MASK			0x00007FFF
+#define AU6601_INT_ERROR_MASK			0xFFFF8000
+
+#define AU6601_INT_CMD_MASK	(AU6601_INT_CMD_END | \
+		AU6601_INT_CMD_TIMEOUT_ERR | AU6601_INT_CMD_CRC_ERR | \
+		AU6601_INT_CMD_END_BIT_ERR | AU6601_INT_CMD_INDEX_ERR)
+#define AU6601_INT_DATA_MASK	(AU6601_INT_DATA_END | AU6601_INT_DMA_END | \
+		AU6601_INT_READ_BUF_RDY | AU6601_INT_WRITE_BUF_RDY | \
+		AU6601_INT_DATA_TIMEOUT_ERR | AU6601_INT_DATA_CRC_ERR | \
+		AU6601_INT_DATA_END_BIT_ERR)
+#define AU6601_INT_ALL_MASK			((u32)-1)
+
+/* MS_CARD mode registers */
+
+#define AU6601_MS_STATUS			0xa0
+
+#define AU6601_MS_BUS_MODE_CTRL			0xa1
+#define AU6601_MS_BUS_8BIT_MODE			0x03
+#define AU6601_MS_BUS_4BIT_MODE			0x01
+#define AU6601_MS_BUS_1BIT_MODE			0x00
+
+#define AU6601_MS_TPC_CMD			0xa2
+#define AU6601_MS_TPC_READ_PAGE_DATA		0x02
+#define AU6601_MS_TPC_READ_REG			0x04
+#define AU6601_MS_TPC_GET_INT			0x07
+#define AU6601_MS_TPC_WRITE_PAGE_DATA		0x0D
+#define AU6601_MS_TPC_WRITE_REG			0x0B
+#define AU6601_MS_TPC_SET_RW_REG_ADRS		0x08
+#define AU6601_MS_TPC_SET_CMD			0x0E
+#define AU6601_MS_TPC_EX_SET_CMD		0x09
+#define AU6601_MS_TPC_READ_SHORT_DATA		0x03
+#define AU6601_MS_TPC_WRITE_SHORT_DATA		0x0C
+
+#define AU6601_MS_TRANSFER_MODE			0xa3
+#define	AU6601_MS_XFER_INT_TIMEOUT_CHK		BIT(2)
+#define	AU6601_MS_XFER_DMA_ENABLE		BIT(1)
+#define	AU6601_MS_XFER_START			BIT(0)
+
+#define AU6601_MS_DATA_PIN_STATE		0xa4
+
+#define AU6601_MS_INT_STATUS			0xb0
+#define AU6601_MS_INT_ENABLE			0xb4
+#define AU6601_MS_INT_OVER_CURRENT_ERROR	BIT(23)
+#define AU6601_MS_INT_DATA_CRC_ERROR		BIT(21)
+#define AU6601_MS_INT_INT_TIMEOUT		BIT(20)
+#define AU6601_MS_INT_INT_RESP_ERROR		BIT(19)
+#define AU6601_MS_INT_CED_ERROR			BIT(18)
+#define AU6601_MS_INT_TPC_TIMEOUT		BIT(16)
+#define AU6601_MS_INT_ERROR			BIT(15)
+#define AU6601_MS_INT_CARD_INSERT		BIT(7)
+#define AU6601_MS_INT_CARD_REMOVE		BIT(6)
+#define AU6601_MS_INT_BUF_READ_RDY		BIT(5)
+#define AU6601_MS_INT_BUF_WRITE_RDY		BIT(4)
+#define AU6601_MS_INT_DMA_END			BIT(3)
+#define AU6601_MS_INT_TPC_END			BIT(1)
+
+#define AU6601_MS_INT_DATA_MASK			0x00000038
+#define AU6601_MS_INT_TPC_MASK			0x003d8002
+#define AU6601_MS_INT_TPC_ERROR			0x003d0000
+
+#define ALCOR_PCIE_LINK_CTRL_OFFSET		0x10
+#define ALCOR_PCIE_LINK_CAP_OFFSET		0x0c
+#define ALCOR_CAP_START_OFFSET			0x34
+
+struct alcor_dev_cfg {
+	u8	dma;
+};
+
+struct alcor_pci_priv {
+	struct pci_dev *pdev;
+	struct pci_dev *parent_pdev;
+	struct  device *dev;
+	void __iomem *iobase;
+	unsigned int irq;
+
+	unsigned long id; /* idr id */
+
+	struct alcor_dev_cfg	*cfg;
+
+	/* PCI ASPM related vars */
+	int pdev_cap_off;
+	u8  pdev_aspm_cap;
+	int parent_cap_off;
+	u8  parent_aspm_cap;
+	u8 ext_config_dev_aspm;
+};
+
+void alcor_write8(struct alcor_pci_priv *priv, u8 val, unsigned int addr);
+void alcor_write16(struct alcor_pci_priv *priv, u16 val, unsigned int addr);
+void alcor_write32(struct alcor_pci_priv *priv, u32 val, unsigned int addr);
+void alcor_write32be(struct alcor_pci_priv *priv, u32 val, unsigned int addr);
+u8 alcor_read8(struct alcor_pci_priv *priv, unsigned int addr);
+u32 alcor_read32(struct alcor_pci_priv *priv, unsigned int addr);
+u32 alcor_read32be(struct alcor_pci_priv *priv, unsigned int addr);
+#endif