|  | // 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/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 const 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_platform_ioremap_resource(pdev, 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); |