| // SPDX-License-Identifier: GPL-2.0-only | 
| /* | 
|  * AMD Seattle AHCI SATA driver | 
|  * | 
|  * Copyright (c) 2015, Advanced Micro Devices | 
|  * Author: Brijesh Singh <brijesh.singh@amd.com> | 
|  * | 
|  * based on the AHCI SATA platform driver by Jeff Garzik and Anton Vorontsov | 
|  */ | 
|   | 
| #include <linux/kernel.h> | 
| #include <linux/module.h> | 
| #include <linux/pm.h> | 
| #include <linux/device.h> | 
| #include <linux/of_device.h> | 
| #include <linux/platform_device.h> | 
| #include <linux/libata.h> | 
| #include <linux/ahci_platform.h> | 
| #include <linux/acpi.h> | 
| #include <linux/pci_ids.h> | 
| #include "ahci.h" | 
|   | 
| /* SGPIO Control Register definition | 
|  * | 
|  * Bit        Type        Description | 
|  * 31        RW        OD7.2 (activity) | 
|  * 30        RW        OD7.1 (locate) | 
|  * 29        RW        OD7.0 (fault) | 
|  * 28...8    RW        OD6.2...OD0.0 (3bits per port, 1 bit per LED) | 
|  * 7        RO        SGPIO feature flag | 
|  * 6:4        RO        Reserved | 
|  * 3:0        RO        Number of ports (0 means no port supported) | 
|  */ | 
| #define ACTIVITY_BIT_POS(x)        (8 + (3 * x)) | 
| #define LOCATE_BIT_POS(x)        (ACTIVITY_BIT_POS(x) + 1) | 
| #define FAULT_BIT_POS(x)        (LOCATE_BIT_POS(x) + 1) | 
|   | 
| #define ACTIVITY_MASK            0x00010000 | 
| #define LOCATE_MASK            0x00080000 | 
| #define FAULT_MASK            0x00400000 | 
|   | 
| #define DRV_NAME "ahci-seattle" | 
|   | 
| static ssize_t seattle_transmit_led_message(struct ata_port *ap, u32 state, | 
|                         ssize_t size); | 
|   | 
| struct seattle_plat_data { | 
|     void __iomem *sgpio_ctrl; | 
| }; | 
|   | 
| static struct ata_port_operations ahci_port_ops = { | 
|     .inherits        = &ahci_ops, | 
| }; | 
|   | 
| static const struct ata_port_info ahci_port_info = { | 
|     .flags        = AHCI_FLAG_COMMON, | 
|     .pio_mask    = ATA_PIO4, | 
|     .udma_mask    = ATA_UDMA6, | 
|     .port_ops    = &ahci_port_ops, | 
| }; | 
|   | 
| static struct ata_port_operations ahci_seattle_ops = { | 
|     .inherits        = &ahci_ops, | 
|     .transmit_led_message   = seattle_transmit_led_message, | 
| }; | 
|   | 
| static const struct ata_port_info ahci_port_seattle_info = { | 
|     .flags        = AHCI_FLAG_COMMON | ATA_FLAG_EM | ATA_FLAG_SW_ACTIVITY, | 
|     .link_flags    = ATA_LFLAG_SW_ACTIVITY, | 
|     .pio_mask    = ATA_PIO4, | 
|     .udma_mask    = ATA_UDMA6, | 
|     .port_ops    = &ahci_seattle_ops, | 
| }; | 
|   | 
| static struct scsi_host_template ahci_platform_sht = { | 
|     AHCI_SHT(DRV_NAME), | 
| }; | 
|   | 
| static ssize_t seattle_transmit_led_message(struct ata_port *ap, u32 state, | 
|                         ssize_t size) | 
| { | 
|     struct ahci_host_priv *hpriv = ap->host->private_data; | 
|     struct ahci_port_priv *pp = ap->private_data; | 
|     struct seattle_plat_data *plat_data = hpriv->plat_data; | 
|     unsigned long flags; | 
|     int pmp; | 
|     struct ahci_em_priv *emp; | 
|     u32 val; | 
|   | 
|     /* get the slot number from the message */ | 
|     pmp = (state & EM_MSG_LED_PMP_SLOT) >> 8; | 
|     if (pmp >= EM_MAX_SLOTS) | 
|         return -EINVAL; | 
|     emp = &pp->em_priv[pmp]; | 
|   | 
|     val = ioread32(plat_data->sgpio_ctrl); | 
|     if (state & ACTIVITY_MASK) | 
|         val |= 1 << ACTIVITY_BIT_POS((ap->port_no)); | 
|     else | 
|         val &= ~(1 << ACTIVITY_BIT_POS((ap->port_no))); | 
|   | 
|     if (state & LOCATE_MASK) | 
|         val |= 1 << LOCATE_BIT_POS((ap->port_no)); | 
|     else | 
|         val &= ~(1 << LOCATE_BIT_POS((ap->port_no))); | 
|   | 
|     if (state & FAULT_MASK) | 
|         val |= 1 << FAULT_BIT_POS((ap->port_no)); | 
|     else | 
|         val &= ~(1 << FAULT_BIT_POS((ap->port_no))); | 
|   | 
|     iowrite32(val, plat_data->sgpio_ctrl); | 
|   | 
|     spin_lock_irqsave(ap->lock, flags); | 
|   | 
|     /* save off new led state for port/slot */ | 
|     emp->led_state = state; | 
|   | 
|     spin_unlock_irqrestore(ap->lock, flags); | 
|   | 
|     return size; | 
| } | 
|   | 
| static const struct ata_port_info *ahci_seattle_get_port_info( | 
|         struct platform_device *pdev, struct ahci_host_priv *hpriv) | 
| { | 
|     struct device *dev = &pdev->dev; | 
|     struct seattle_plat_data *plat_data; | 
|     u32 val; | 
|   | 
|     plat_data = devm_kzalloc(dev, sizeof(*plat_data), GFP_KERNEL); | 
|     if (!plat_data) | 
|         return &ahci_port_info; | 
|   | 
|     plat_data->sgpio_ctrl = devm_ioremap_resource(dev, | 
|                   platform_get_resource(pdev, IORESOURCE_MEM, 1)); | 
|     if (IS_ERR(plat_data->sgpio_ctrl)) | 
|         return &ahci_port_info; | 
|   | 
|     val = ioread32(plat_data->sgpio_ctrl); | 
|   | 
|     if (!(val & 0xf)) | 
|         return &ahci_port_info; | 
|   | 
|     hpriv->em_loc = 0; | 
|     hpriv->em_buf_sz = 4; | 
|     hpriv->em_msg_type = EM_MSG_TYPE_LED; | 
|     hpriv->plat_data = plat_data; | 
|   | 
|     dev_info(dev, "SGPIO LED control is enabled.\n"); | 
|     return &ahci_port_seattle_info; | 
| } | 
|   | 
| static int ahci_seattle_probe(struct platform_device *pdev) | 
| { | 
|     int rc; | 
|     struct ahci_host_priv *hpriv; | 
|   | 
|     hpriv = ahci_platform_get_resources(pdev, 0); | 
|     if (IS_ERR(hpriv)) | 
|         return PTR_ERR(hpriv); | 
|   | 
|     rc = ahci_platform_enable_resources(hpriv); | 
|     if (rc) | 
|         return rc; | 
|   | 
|     rc = ahci_platform_init_host(pdev, hpriv, | 
|                      ahci_seattle_get_port_info(pdev, hpriv), | 
|                      &ahci_platform_sht); | 
|     if (rc) | 
|         goto disable_resources; | 
|   | 
|     return 0; | 
| disable_resources: | 
|     ahci_platform_disable_resources(hpriv); | 
|     return rc; | 
| } | 
|   | 
| static SIMPLE_DEV_PM_OPS(ahci_pm_ops, ahci_platform_suspend, | 
|              ahci_platform_resume); | 
|   | 
| static const struct acpi_device_id ahci_acpi_match[] = { | 
|     { "AMDI0600", 0 }, | 
|     {} | 
| }; | 
| MODULE_DEVICE_TABLE(acpi, ahci_acpi_match); | 
|   | 
| static struct platform_driver ahci_seattle_driver = { | 
|     .probe = ahci_seattle_probe, | 
|     .remove = ata_platform_remove_one, | 
|     .driver = { | 
|         .name = DRV_NAME, | 
|         .acpi_match_table = ahci_acpi_match, | 
|         .pm = &ahci_pm_ops, | 
|     }, | 
| }; | 
| module_platform_driver(ahci_seattle_driver); | 
|   | 
| MODULE_DESCRIPTION("Seattle AHCI SATA platform driver"); | 
| MODULE_AUTHOR("Brijesh Singh <brijesh.singh@amd.com>"); | 
| MODULE_LICENSE("GPL"); | 
| MODULE_ALIAS("platform:" DRV_NAME); |