diff options
author | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-10-29 15:12:34 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-10-29 15:12:34 -0400 |
commit | 3529a233421fc43fa7bfdf7a4317daf28348a23d (patch) | |
tree | 520558ffb70e4f4743495ad7a62d4c775c0c1ea3 /drivers/ata/ahci.c | |
parent | 00cda56d39f013cce60f44f1e3da19b87eba5d85 (diff) | |
parent | 31556594f913fa81d008cecfe46d7211c919a853 (diff) |
Merge branch 'alpm' of master.kernel.org:/pub/scm/linux/kernel/git/jgarzik/libata-dev
* 'alpm' of master.kernel.org:/pub/scm/linux/kernel/git/jgarzik/libata-dev:
[libata] AHCI: add hw link power management support
[libata] Link power management infrastructure
Diffstat (limited to 'drivers/ata/ahci.c')
-rw-r--r-- | drivers/ata/ahci.c | 157 |
1 files changed, 155 insertions, 2 deletions
diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c index c8ab947cf359..ed9b407e42d4 100644 --- a/drivers/ata/ahci.c +++ b/drivers/ata/ahci.c | |||
@@ -49,6 +49,9 @@ | |||
49 | #define DRV_NAME "ahci" | 49 | #define DRV_NAME "ahci" |
50 | #define DRV_VERSION "3.0" | 50 | #define DRV_VERSION "3.0" |
51 | 51 | ||
52 | static int ahci_enable_alpm(struct ata_port *ap, | ||
53 | enum link_pm policy); | ||
54 | static void ahci_disable_alpm(struct ata_port *ap); | ||
52 | 55 | ||
53 | enum { | 56 | enum { |
54 | AHCI_PCI_BAR = 5, | 57 | AHCI_PCI_BAR = 5, |
@@ -99,6 +102,7 @@ enum { | |||
99 | HOST_CAP_SSC = (1 << 14), /* Slumber capable */ | 102 | HOST_CAP_SSC = (1 << 14), /* Slumber capable */ |
100 | HOST_CAP_PMP = (1 << 17), /* Port Multiplier support */ | 103 | HOST_CAP_PMP = (1 << 17), /* Port Multiplier support */ |
101 | HOST_CAP_CLO = (1 << 24), /* Command List Override support */ | 104 | HOST_CAP_CLO = (1 << 24), /* Command List Override support */ |
105 | HOST_CAP_ALPM = (1 << 26), /* Aggressive Link PM support */ | ||
102 | HOST_CAP_SSS = (1 << 27), /* Staggered Spin-up */ | 106 | HOST_CAP_SSS = (1 << 27), /* Staggered Spin-up */ |
103 | HOST_CAP_SNTF = (1 << 29), /* SNotification register */ | 107 | HOST_CAP_SNTF = (1 << 29), /* SNotification register */ |
104 | HOST_CAP_NCQ = (1 << 30), /* Native Command Queueing */ | 108 | HOST_CAP_NCQ = (1 << 30), /* Native Command Queueing */ |
@@ -155,6 +159,8 @@ enum { | |||
155 | PORT_IRQ_PIOS_FIS | PORT_IRQ_D2H_REG_FIS, | 159 | PORT_IRQ_PIOS_FIS | PORT_IRQ_D2H_REG_FIS, |
156 | 160 | ||
157 | /* PORT_CMD bits */ | 161 | /* PORT_CMD bits */ |
162 | PORT_CMD_ASP = (1 << 27), /* Aggressive Slumber/Partial */ | ||
163 | PORT_CMD_ALPE = (1 << 26), /* Aggressive Link PM enable */ | ||
158 | PORT_CMD_ATAPI = (1 << 24), /* Device is ATAPI */ | 164 | PORT_CMD_ATAPI = (1 << 24), /* Device is ATAPI */ |
159 | PORT_CMD_PMP = (1 << 17), /* PMP attached */ | 165 | PORT_CMD_PMP = (1 << 17), /* PMP attached */ |
160 | PORT_CMD_LIST_ON = (1 << 15), /* cmd list DMA engine running */ | 166 | PORT_CMD_LIST_ON = (1 << 15), /* cmd list DMA engine running */ |
@@ -178,13 +184,14 @@ enum { | |||
178 | AHCI_HFLAG_MV_PATA = (1 << 4), /* PATA port */ | 184 | AHCI_HFLAG_MV_PATA = (1 << 4), /* PATA port */ |
179 | AHCI_HFLAG_NO_MSI = (1 << 5), /* no PCI MSI */ | 185 | AHCI_HFLAG_NO_MSI = (1 << 5), /* no PCI MSI */ |
180 | AHCI_HFLAG_NO_PMP = (1 << 6), /* no PMP */ | 186 | AHCI_HFLAG_NO_PMP = (1 << 6), /* no PMP */ |
187 | AHCI_HFLAG_NO_HOTPLUG = (1 << 7), /* ignore PxSERR.DIAG.N */ | ||
181 | 188 | ||
182 | /* ap->flags bits */ | 189 | /* ap->flags bits */ |
183 | AHCI_FLAG_NO_HOTPLUG = (1 << 24), /* ignore PxSERR.DIAG.N */ | ||
184 | 190 | ||
185 | AHCI_FLAG_COMMON = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | | 191 | AHCI_FLAG_COMMON = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | |
186 | ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA | | 192 | ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA | |
187 | ATA_FLAG_ACPI_SATA | ATA_FLAG_AN, | 193 | ATA_FLAG_ACPI_SATA | ATA_FLAG_AN | |
194 | ATA_FLAG_IPM, | ||
188 | AHCI_LFLAG_COMMON = ATA_LFLAG_SKIP_D2H_BSY, | 195 | AHCI_LFLAG_COMMON = ATA_LFLAG_SKIP_D2H_BSY, |
189 | }; | 196 | }; |
190 | 197 | ||
@@ -254,6 +261,11 @@ static int ahci_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg); | |||
254 | static int ahci_pci_device_resume(struct pci_dev *pdev); | 261 | static int ahci_pci_device_resume(struct pci_dev *pdev); |
255 | #endif | 262 | #endif |
256 | 263 | ||
264 | static struct class_device_attribute *ahci_shost_attrs[] = { | ||
265 | &class_device_attr_link_power_management_policy, | ||
266 | NULL | ||
267 | }; | ||
268 | |||
257 | static struct scsi_host_template ahci_sht = { | 269 | static struct scsi_host_template ahci_sht = { |
258 | .module = THIS_MODULE, | 270 | .module = THIS_MODULE, |
259 | .name = DRV_NAME, | 271 | .name = DRV_NAME, |
@@ -271,6 +283,7 @@ static struct scsi_host_template ahci_sht = { | |||
271 | .slave_configure = ata_scsi_slave_config, | 283 | .slave_configure = ata_scsi_slave_config, |
272 | .slave_destroy = ata_scsi_slave_destroy, | 284 | .slave_destroy = ata_scsi_slave_destroy, |
273 | .bios_param = ata_std_bios_param, | 285 | .bios_param = ata_std_bios_param, |
286 | .shost_attrs = ahci_shost_attrs, | ||
274 | }; | 287 | }; |
275 | 288 | ||
276 | static const struct ata_port_operations ahci_ops = { | 289 | static const struct ata_port_operations ahci_ops = { |
@@ -302,6 +315,8 @@ static const struct ata_port_operations ahci_ops = { | |||
302 | .port_suspend = ahci_port_suspend, | 315 | .port_suspend = ahci_port_suspend, |
303 | .port_resume = ahci_port_resume, | 316 | .port_resume = ahci_port_resume, |
304 | #endif | 317 | #endif |
318 | .enable_pm = ahci_enable_alpm, | ||
319 | .disable_pm = ahci_disable_alpm, | ||
305 | 320 | ||
306 | .port_start = ahci_port_start, | 321 | .port_start = ahci_port_start, |
307 | .port_stop = ahci_port_stop, | 322 | .port_stop = ahci_port_stop, |
@@ -836,6 +851,130 @@ static void ahci_power_up(struct ata_port *ap) | |||
836 | writel(cmd | PORT_CMD_ICC_ACTIVE, port_mmio + PORT_CMD); | 851 | writel(cmd | PORT_CMD_ICC_ACTIVE, port_mmio + PORT_CMD); |
837 | } | 852 | } |
838 | 853 | ||
854 | static void ahci_disable_alpm(struct ata_port *ap) | ||
855 | { | ||
856 | struct ahci_host_priv *hpriv = ap->host->private_data; | ||
857 | void __iomem *port_mmio = ahci_port_base(ap); | ||
858 | u32 cmd; | ||
859 | struct ahci_port_priv *pp = ap->private_data; | ||
860 | |||
861 | /* IPM bits should be disabled by libata-core */ | ||
862 | /* get the existing command bits */ | ||
863 | cmd = readl(port_mmio + PORT_CMD); | ||
864 | |||
865 | /* disable ALPM and ASP */ | ||
866 | cmd &= ~PORT_CMD_ASP; | ||
867 | cmd &= ~PORT_CMD_ALPE; | ||
868 | |||
869 | /* force the interface back to active */ | ||
870 | cmd |= PORT_CMD_ICC_ACTIVE; | ||
871 | |||
872 | /* write out new cmd value */ | ||
873 | writel(cmd, port_mmio + PORT_CMD); | ||
874 | cmd = readl(port_mmio + PORT_CMD); | ||
875 | |||
876 | /* wait 10ms to be sure we've come out of any low power state */ | ||
877 | msleep(10); | ||
878 | |||
879 | /* clear out any PhyRdy stuff from interrupt status */ | ||
880 | writel(PORT_IRQ_PHYRDY, port_mmio + PORT_IRQ_STAT); | ||
881 | |||
882 | /* go ahead and clean out PhyRdy Change from Serror too */ | ||
883 | ahci_scr_write(ap, SCR_ERROR, ((1 << 16) | (1 << 18))); | ||
884 | |||
885 | /* | ||
886 | * Clear flag to indicate that we should ignore all PhyRdy | ||
887 | * state changes | ||
888 | */ | ||
889 | hpriv->flags &= ~AHCI_HFLAG_NO_HOTPLUG; | ||
890 | |||
891 | /* | ||
892 | * Enable interrupts on Phy Ready. | ||
893 | */ | ||
894 | pp->intr_mask |= PORT_IRQ_PHYRDY; | ||
895 | writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK); | ||
896 | |||
897 | /* | ||
898 | * don't change the link pm policy - we can be called | ||
899 | * just to turn of link pm temporarily | ||
900 | */ | ||
901 | } | ||
902 | |||
903 | static int ahci_enable_alpm(struct ata_port *ap, | ||
904 | enum link_pm policy) | ||
905 | { | ||
906 | struct ahci_host_priv *hpriv = ap->host->private_data; | ||
907 | void __iomem *port_mmio = ahci_port_base(ap); | ||
908 | u32 cmd; | ||
909 | struct ahci_port_priv *pp = ap->private_data; | ||
910 | u32 asp; | ||
911 | |||
912 | /* Make sure the host is capable of link power management */ | ||
913 | if (!(hpriv->cap & HOST_CAP_ALPM)) | ||
914 | return -EINVAL; | ||
915 | |||
916 | switch (policy) { | ||
917 | case MAX_PERFORMANCE: | ||
918 | case NOT_AVAILABLE: | ||
919 | /* | ||
920 | * if we came here with NOT_AVAILABLE, | ||
921 | * it just means this is the first time we | ||
922 | * have tried to enable - default to max performance, | ||
923 | * and let the user go to lower power modes on request. | ||
924 | */ | ||
925 | ahci_disable_alpm(ap); | ||
926 | return 0; | ||
927 | case MIN_POWER: | ||
928 | /* configure HBA to enter SLUMBER */ | ||
929 | asp = PORT_CMD_ASP; | ||
930 | break; | ||
931 | case MEDIUM_POWER: | ||
932 | /* configure HBA to enter PARTIAL */ | ||
933 | asp = 0; | ||
934 | break; | ||
935 | default: | ||
936 | return -EINVAL; | ||
937 | } | ||
938 | |||
939 | /* | ||
940 | * Disable interrupts on Phy Ready. This keeps us from | ||
941 | * getting woken up due to spurious phy ready interrupts | ||
942 | * TBD - Hot plug should be done via polling now, is | ||
943 | * that even supported? | ||
944 | */ | ||
945 | pp->intr_mask &= ~PORT_IRQ_PHYRDY; | ||
946 | writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK); | ||
947 | |||
948 | /* | ||
949 | * Set a flag to indicate that we should ignore all PhyRdy | ||
950 | * state changes since these can happen now whenever we | ||
951 | * change link state | ||
952 | */ | ||
953 | hpriv->flags |= AHCI_HFLAG_NO_HOTPLUG; | ||
954 | |||
955 | /* get the existing command bits */ | ||
956 | cmd = readl(port_mmio + PORT_CMD); | ||
957 | |||
958 | /* | ||
959 | * Set ASP based on Policy | ||
960 | */ | ||
961 | cmd |= asp; | ||
962 | |||
963 | /* | ||
964 | * Setting this bit will instruct the HBA to aggressively | ||
965 | * enter a lower power link state when it's appropriate and | ||
966 | * based on the value set above for ASP | ||
967 | */ | ||
968 | cmd |= PORT_CMD_ALPE; | ||
969 | |||
970 | /* write out new cmd value */ | ||
971 | writel(cmd, port_mmio + PORT_CMD); | ||
972 | cmd = readl(port_mmio + PORT_CMD); | ||
973 | |||
974 | /* IPM bits should be set by libata-core */ | ||
975 | return 0; | ||
976 | } | ||
977 | |||
839 | #ifdef CONFIG_PM | 978 | #ifdef CONFIG_PM |
840 | static void ahci_power_down(struct ata_port *ap) | 979 | static void ahci_power_down(struct ata_port *ap) |
841 | { | 980 | { |
@@ -1504,6 +1643,17 @@ static void ahci_port_intr(struct ata_port *ap) | |||
1504 | if (unlikely(resetting)) | 1643 | if (unlikely(resetting)) |
1505 | status &= ~PORT_IRQ_BAD_PMP; | 1644 | status &= ~PORT_IRQ_BAD_PMP; |
1506 | 1645 | ||
1646 | /* If we are getting PhyRdy, this is | ||
1647 | * just a power state change, we should | ||
1648 | * clear out this, plus the PhyRdy/Comm | ||
1649 | * Wake bits from Serror | ||
1650 | */ | ||
1651 | if ((hpriv->flags & AHCI_HFLAG_NO_HOTPLUG) && | ||
1652 | (status & PORT_IRQ_PHYRDY)) { | ||
1653 | status &= ~PORT_IRQ_PHYRDY; | ||
1654 | ahci_scr_write(ap, SCR_ERROR, ((1 << 16) | (1 << 18))); | ||
1655 | } | ||
1656 | |||
1507 | if (unlikely(status & PORT_IRQ_ERROR)) { | 1657 | if (unlikely(status & PORT_IRQ_ERROR)) { |
1508 | ahci_error_intr(ap, status); | 1658 | ahci_error_intr(ap, status); |
1509 | return; | 1659 | return; |
@@ -2151,6 +2301,9 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) | |||
2151 | ata_port_pbar_desc(ap, AHCI_PCI_BAR, | 2301 | ata_port_pbar_desc(ap, AHCI_PCI_BAR, |
2152 | 0x100 + ap->port_no * 0x80, "port"); | 2302 | 0x100 + ap->port_no * 0x80, "port"); |
2153 | 2303 | ||
2304 | /* set initial link pm policy */ | ||
2305 | ap->pm_policy = NOT_AVAILABLE; | ||
2306 | |||
2154 | /* standard SATA port setup */ | 2307 | /* standard SATA port setup */ |
2155 | if (hpriv->port_map & (1 << i)) | 2308 | if (hpriv->port_map & (1 << i)) |
2156 | ap->ioaddr.cmd_addr = port_mmio; | 2309 | ap->ioaddr.cmd_addr = port_mmio; |