Add Zhaoxin Serial ATA support for Zhaoxin CPUs.
The patch is scheduled to be submitted to the kernel mainline in 2021.
Signed-off-by: LeoLiu-oc <LeoLiu-oc(a)zhaoxin.com>
---
drivers/ata/Kconfig | 8 +
drivers/ata/Makefile | 1 +
drivers/ata/sata_zhaoxin.c | 395 +++++++++++++++++++++++++++++++++++++
3 files changed, 404 insertions(+)
create mode 100644 drivers/ata/sata_zhaoxin.c
diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig
index 99698d7fe585..20cec29cc4d3 100644
--- a/drivers/ata/Kconfig
+++ b/drivers/ata/Kconfig
@@ -478,6 +478,14 @@ config SATA_ULI
If unsure, say N.
+config SATA_ZHAOXIN
+ tristate "ZhaoXin SATA support"
+ depends on PCI
+ help
+ This option enables support for ZhaoXin Serial ATA.
+
+ If unsure, say N.
+
config SATA_VIA
tristate "VIA SATA support"
depends on PCI
diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile
index d21cdd83f7ab..2d9220311187 100644
--- a/drivers/ata/Makefile
+++ b/drivers/ata/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_SATA_SIL) += sata_sil.o
obj-$(CONFIG_SATA_SIS) += sata_sis.o
obj-$(CONFIG_SATA_SVW) += sata_svw.o
obj-$(CONFIG_SATA_ULI) += sata_uli.o
+obj-$(CONFIG_SATA_ZHAOXIN) += sata_zhaoxin.o
obj-$(CONFIG_SATA_VIA) += sata_via.o
obj-$(CONFIG_SATA_VITESSE) += sata_vsc.o
diff --git a/drivers/ata/sata_zhaoxin.c b/drivers/ata/sata_zhaoxin.c
new file mode 100644
index 000000000000..b0b16f6f364b
--- /dev/null
+++ b/drivers/ata/sata_zhaoxin.c
@@ -0,0 +1,395 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sata_zhaoxin.c - ZhaoXin Serial ATA controllers
+ *
+ * Maintained by: Tejun Heo <tj(a)kernel.org>
+ * Please ALWAYS copy linux-ide(a)vger.kernel.org
+ * on emails.
+ *
+ * Copyright 2003-2004 Red Hat, Inc. All rights reserved.
+ * Copyright 2003-2004 Jeff Garzik
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * libata documentation is available via 'make {ps|pdf}docs',
+ * as Documentation/DocBook/libata.*
+ *
+ * Hardware documentation available under NDA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/blkdev.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_host.h>
+#include <linux/libata.h>
+
+#define DRV_NAME "sata_zx"
+#define DRV_VERSION "2.6.1"
+
+enum board_ids_enum {
+ cnd001,
+};
+
+enum {
+ SATA_CHAN_ENAB = 0x40, /* SATA channel enable */
+ SATA_INT_GATE = 0x41, /* SATA interrupt gating */
+ SATA_NATIVE_MODE = 0x42, /* Native mode enable */
+ PATA_UDMA_TIMING = 0xB3, /* PATA timing for DMA/ cable detect */
+ PATA_PIO_TIMING = 0xAB, /* PATA timing register */
+
+ PORT0 = (1 << 1),
+ PORT1 = (1 << 0),
+ ALL_PORTS = PORT0 | PORT1,
+
+ NATIVE_MODE_ALL = (1 << 7) | (1 << 6) | (1 << 5) | (1 << 4),
+
+ SATA_EXT_PHY = (1 << 6), /* 0==use PATA, 1==ext phy */
+};
+
+static int szx_init_one(struct pci_dev *pdev, const struct
pci_device_id *ent);
+static int cnd001_scr_read(struct ata_link *link, unsigned int scr, u32
*val);
+static int cnd001_scr_write(struct ata_link *link, unsigned int scr,
u32 val);
+static int szx_hardreset(struct ata_link *link, unsigned int *class,
unsigned long deadline);
+
+static void szx_tf_load(struct ata_port *ap, const struct ata_taskfile
*tf);
+
+static const struct pci_device_id szx_pci_tbl[] = {
+ { PCI_VDEVICE(ZHAOXIN, 0x9002), cnd001 },
+ { PCI_VDEVICE(ZHAOXIN, 0x9003), cnd001 },
+
+ { } /* terminate list */
+};
+
+static struct pci_driver szx_pci_driver = {
+ .name = DRV_NAME,
+ .id_table = szx_pci_tbl,
+ .probe = szx_init_one,
+#ifdef CONFIG_PM_SLEEP
+ .suspend = ata_pci_device_suspend,
+ .resume = ata_pci_device_resume,
+#endif
+ .remove = ata_pci_remove_one,
+};
+
+static struct scsi_host_template szx_sht = {
+ ATA_BMDMA_SHT(DRV_NAME),
+};
+
+static struct ata_port_operations szx_base_ops = {
+ .inherits = &ata_bmdma_port_ops,
+ .sff_tf_load = szx_tf_load,
+};
+
+static struct ata_port_operations cnd001_ops = {
+ .inherits = &szx_base_ops,
+ .hardreset = szx_hardreset,
+ .scr_read = cnd001_scr_read,
+ .scr_write = cnd001_scr_write,
+};
+
+static struct ata_port_info cnd001_port_info = {
+ .flags = ATA_FLAG_SATA | ATA_FLAG_SLAVE_POSS,
+ .pio_mask = ATA_PIO4,
+ .mwdma_mask = ATA_MWDMA2,
+ .udma_mask = ATA_UDMA6,
+ .port_ops = &cnd001_ops,
+};
+
+
+MODULE_AUTHOR("Jeff Garzik");
+MODULE_DESCRIPTION("SCSI low-level driver for ZX SATA controllers");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(pci, szx_pci_tbl);
+MODULE_VERSION(DRV_VERSION);
+
+static int szx_hardreset(struct ata_link *link, unsigned int *class,
unsigned long deadline)
+{
+ int rc;
+
+ rc = sata_std_hardreset(link, class, deadline);
+ if (!rc || rc == -EAGAIN) {
+ struct ata_port *ap = link->ap;
+ int pmp = link->pmp;
+ int tmprc;
+
+ if (pmp) {
+ ap->ops->sff_dev_select(ap, pmp);
+ tmprc = ata_sff_wait_ready(&ap->link, deadline);
+ } else {
+ tmprc = ata_sff_wait_ready(link, deadline);
+ }
+ if (tmprc) {
+ ata_link_err(link, "COMRESET failed for wait (errno=%d)\n", rc);
+ } else {
+ ata_link_err(link, "wait for bsy success\n");
+ }
+ ata_link_err(link, "COMRESET success (errno=%d) ap=%d link %d\n", rc,
link->ap->port_no, link->pmp);
+ }else{
+ ata_link_err(link, "COMRESET failed (errno=%d) ap=%d link %d\n", rc,
link->ap->port_no, link->pmp);
+ }
+ return rc;
+}
+
+static int cnd001_scr_read(struct ata_link *link, unsigned int scr, u32
*val)
+{
+ static const u8 ipm_tbl[] = { 1, 2, 6, 0 };
+ struct pci_dev *pdev = to_pci_dev(link->ap->host->dev);
+ int slot = 2 * link->ap->port_no + link->pmp;
+ u32 v = 0;
+ u8 raw;
+
+ switch (scr) {
+ case SCR_STATUS:
+ pci_read_config_byte(pdev, 0xA0 + slot, &raw);
+
+ /* read the DET field, bit0 and 1 of the config byte */
+ v |= raw & 0x03;
+
+ /* read the SPD field, bit4 of the configure byte */
+ v |= raw & 0x30;
+
+ /* read the IPM field, bit2 and 3 of the config byte */
+ v |= ((ipm_tbl[(raw >> 2) & 0x3])<<8);
+ break;
+
+ case SCR_ERROR:
+ /* devices other than 5287 uses 0xA8 as base */
+ WARN_ON(pdev->device != 0x9002 && pdev->device != 0x9003);
+ pci_write_config_byte(pdev, 0x42, slot);
+ pci_read_config_dword(pdev, 0xA8, &v);
+ break;
+
+ case SCR_CONTROL:
+ pci_read_config_byte(pdev, 0xA4 + slot, &raw);
+
+ /* read the DET field, bit0 and bit1 */
+ v |= ((raw & 0x02) << 1) | (raw & 0x01);
+
+ /* read the IPM field, bit2 and bit3 */
+ v |= ((raw >> 2) & 0x03) << 8;
+
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ *val = v;
+ return 0;
+}
+
+static int cnd001_scr_write(struct ata_link *link, unsigned int scr,
u32 val)
+{
+ struct pci_dev *pdev = to_pci_dev(link->ap->host->dev);
+ int slot = 2 * link->ap->port_no + link->pmp;
+ u32 v = 0;
+
+ WARN_ON(pdev == NULL);
+
+ switch (scr) {
+ case SCR_ERROR:
+ /* devices 0x9002 uses 0xA8 as base */
+ WARN_ON(pdev->device != 0x9002 && pdev->device != 0x9003);
+ pci_write_config_byte(pdev, 0x42, slot);
+ pci_write_config_dword(pdev, 0xA8, val);
+ return 0;
+
+ case SCR_CONTROL:
+ /* set the DET field */
+ v |= ((val & 0x4) >> 1) | (val & 0x1);
+
+ /* set the IPM field */
+ v |= ((val >> 8) & 0x3) << 2;
+
+
+ pci_write_config_byte(pdev, 0xA4 + slot, v);
+
+
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+
+/**
+ * szx_tf_load - send taskfile registers to host controller
+ * @ap: Port to which output is sent
+ * @tf: ATA taskfile register set
+ *
+ * Outputs ATA taskfile to standard ATA host controller.
+ *
+ * This is to fix the internal bug of zx chipsets, which will
+ * reset the device register after changing the IEN bit on ctl
+ * register.
+ */
+static void szx_tf_load(struct ata_port *ap, const struct ata_taskfile *tf)
+{
+ struct ata_taskfile ttf;
+
+ if (tf->ctl != ap->last_ctl) {
+ ttf = *tf;
+ ttf.flags |= ATA_TFLAG_DEVICE;
+ tf = &ttf;
+ }
+ ata_sff_tf_load(ap, tf);
+}
+
+static const unsigned int szx_bar_sizes[] = {
+ 8, 4, 8, 4, 16, 256
+};
+
+static const unsigned int cnd001_bar_sizes0[] = {
+ 8, 4, 8, 4, 16, 0
+};
+
+static const unsigned int cnd001_bar_sizes1[] = {
+ 8, 4, 0, 0, 16, 0
+};
+
+static int cnd001_prepare_host(struct pci_dev *pdev, struct ata_host
**r_host)
+{
+ const struct ata_port_info *ppi0[] = { &cnd001_port_info, NULL };
+ const struct ata_port_info *ppi1[] = { &cnd001_port_info,
&ata_dummy_port_info};
+ struct ata_host *host;
+ int i, rc;
+
+ if (pdev->device == 0x9002)
+ rc = ata_pci_bmdma_prepare_host(pdev, ppi0, &host);
+ else if (pdev->device == 0x9003)
+ rc = ata_pci_bmdma_prepare_host(pdev, ppi1, &host);
+ else
+ rc = -EINVAL;
+ if (rc)
+ return rc;
+ *r_host = host;
+
+
+ /* cnd001 9002 hosts four sata ports as M/S of the two channels */
+ /* cnd001 9003 hosts two sata ports as M/S of the one channel */
+ for (i = 0; i < host->n_ports; i++)
+ ata_slave_link_init(host->ports[i]);
+
+ return 0;
+}
+
+static void szx_configure(struct pci_dev *pdev, int board_id)
+{
+ u8 tmp8;
+
+ pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &tmp8);
+ dev_info(&pdev->dev, "routed to hard irq line %d\n",
+ (int) (tmp8 & 0xf0) == 0xf0 ? 0 : tmp8 & 0x0f);
+
+ /* make sure SATA channels are enabled */
+ pci_read_config_byte(pdev, SATA_CHAN_ENAB, &tmp8);
+ if ((tmp8 & ALL_PORTS) != ALL_PORTS) {
+ dev_dbg(&pdev->dev, "enabling SATA channels (0x%x)\n",
+ (int)tmp8);
+ tmp8 |= ALL_PORTS;
+ pci_write_config_byte(pdev, SATA_CHAN_ENAB, tmp8);
+ }
+
+ /* make sure interrupts for each channel sent to us */
+ pci_read_config_byte(pdev, SATA_INT_GATE, &tmp8);
+ if ((tmp8 & ALL_PORTS) != ALL_PORTS) {
+ dev_dbg(&pdev->dev, "enabling SATA channel interrupts (0x%x)\n",
+ (int) tmp8);
+ tmp8 |= ALL_PORTS;
+ pci_write_config_byte(pdev, SATA_INT_GATE, tmp8);
+ }
+
+ /* make sure native mode is enabled */
+ pci_read_config_byte(pdev, SATA_NATIVE_MODE, &tmp8);
+ if ((tmp8 & NATIVE_MODE_ALL) != NATIVE_MODE_ALL) {
+ dev_dbg(&pdev->dev,
+ "enabling SATA channel native mode (0x%x)\n",
+ (int) tmp8);
+ tmp8 |= NATIVE_MODE_ALL;
+ pci_write_config_byte(pdev, SATA_NATIVE_MODE, tmp8);
+ }
+}
+
+static int szx_init_one(struct pci_dev *pdev, const struct
pci_device_id *ent)
+{
+ unsigned int i;
+ int rc;
+ struct ata_host *host = NULL;
+ int board_id = (int) ent->driver_data;
+ const unsigned * bar_sizes;
+ int legacy_mode = 0;
+
+ ata_print_version_once(&pdev->dev, DRV_VERSION);
+
+ if (pdev->device == 0x9002 || pdev->device == 0x9003) {
+ if ((pdev->class >> 8) == PCI_CLASS_STORAGE_IDE) {
+ u8 tmp8, mask;
+
+ /* TODO: What if one channel is in native mode ... */
+ pci_read_config_byte(pdev, PCI_CLASS_PROG, &tmp8);
+ mask = (1 << 2) | (1 << 0);
+ if ((tmp8 & mask) != mask)
+ legacy_mode = 1;
+ }
+ if (legacy_mode)
+ return -EINVAL;
+ }
+
+
+ rc = pcim_enable_device(pdev);
+ if (rc)
+ return rc;
+
+ if (board_id == cnd001 && pdev->device == 0x9002)
+ bar_sizes = &cnd001_bar_sizes0[0];
+ else if (board_id == cnd001 && pdev->device == 0x9003)
+ bar_sizes = &cnd001_bar_sizes1[0];
+ else
+ bar_sizes = &szx_bar_sizes[0];
+
+ for (i = 0; i < ARRAY_SIZE(szx_bar_sizes); i++)
+ if ((pci_resource_start(pdev, i) == 0) ||
+ (pci_resource_len(pdev, i) < bar_sizes[i])) {
+ if (bar_sizes[i] == 0)
+ continue;
+ dev_err(&pdev->dev,
+ "invalid PCI BAR %u (sz 0x%llx, val 0x%llx)\n",
+ i,
+ (unsigned long long)pci_resource_start(pdev, i),
+ (unsigned long long)pci_resource_len(pdev, i));
+ return -ENODEV;
+ }
+
+ switch (board_id) {
+ case cnd001:
+ rc = cnd001_prepare_host(pdev, &host);
+ break;
+ default:
+ rc = -EINVAL;
+ }
+ if (rc)
+ return rc;
+
+ szx_configure(pdev, board_id);
+
+ pci_set_master(pdev);
+ return ata_host_activate(host, pdev->irq, ata_bmdma_interrupt,
+ IRQF_SHARED, &szx_sht);
+}
+
+module_pci_driver(szx_pci_driver);
--
2.20.1