Aaron Lu | afe7595 | 2013-01-15 17:20:58 +0800 | [diff] [blame] | 1 | #include <linux/libata.h> |
| 2 | #include <linux/cdrom.h> |
Aaron Lu | f064a20 | 2013-01-15 17:20:59 +0800 | [diff] [blame^] | 3 | #include <linux/pm_runtime.h> |
| 4 | #include <scsi/scsi_device.h> |
Aaron Lu | afe7595 | 2013-01-15 17:20:58 +0800 | [diff] [blame] | 5 | |
| 6 | #include "libata.h" |
| 7 | |
| 8 | enum odd_mech_type { |
| 9 | ODD_MECH_TYPE_SLOT, |
| 10 | ODD_MECH_TYPE_DRAWER, |
| 11 | ODD_MECH_TYPE_UNSUPPORTED, |
| 12 | }; |
| 13 | |
| 14 | struct zpodd { |
| 15 | enum odd_mech_type mech_type; /* init during probe, RO afterwards */ |
| 16 | struct ata_device *dev; |
Aaron Lu | f064a20 | 2013-01-15 17:20:59 +0800 | [diff] [blame^] | 17 | |
| 18 | /* The following fields are synchronized by PM core. */ |
| 19 | bool from_notify; /* resumed as a result of |
| 20 | * acpi wake notification */ |
Aaron Lu | afe7595 | 2013-01-15 17:20:58 +0800 | [diff] [blame] | 21 | }; |
| 22 | |
| 23 | /* Per the spec, only slot type and drawer type ODD can be supported */ |
| 24 | static enum odd_mech_type zpodd_get_mech_type(struct ata_device *dev) |
| 25 | { |
| 26 | char buf[16]; |
| 27 | unsigned int ret; |
| 28 | struct rm_feature_desc *desc = (void *)(buf + 8); |
| 29 | struct ata_taskfile tf = {}; |
| 30 | |
| 31 | char cdb[] = { GPCMD_GET_CONFIGURATION, |
| 32 | 2, /* only 1 feature descriptor requested */ |
| 33 | 0, 3, /* 3, removable medium feature */ |
| 34 | 0, 0, 0,/* reserved */ |
| 35 | 0, sizeof(buf), |
| 36 | 0, 0, 0, |
| 37 | }; |
| 38 | |
| 39 | tf.flags = ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE; |
| 40 | tf.command = ATA_CMD_PACKET; |
| 41 | tf.protocol = ATAPI_PROT_PIO; |
| 42 | tf.lbam = sizeof(buf); |
| 43 | |
| 44 | ret = ata_exec_internal(dev, &tf, cdb, DMA_FROM_DEVICE, |
| 45 | buf, sizeof(buf), 0); |
| 46 | if (ret) |
| 47 | return ODD_MECH_TYPE_UNSUPPORTED; |
| 48 | |
| 49 | if (be16_to_cpu(desc->feature_code) != 3) |
| 50 | return ODD_MECH_TYPE_UNSUPPORTED; |
| 51 | |
| 52 | if (desc->mech_type == 0 && desc->load == 0 && desc->eject == 1) |
| 53 | return ODD_MECH_TYPE_SLOT; |
| 54 | else if (desc->mech_type == 1 && desc->load == 0 && desc->eject == 1) |
| 55 | return ODD_MECH_TYPE_DRAWER; |
| 56 | else |
| 57 | return ODD_MECH_TYPE_UNSUPPORTED; |
| 58 | } |
| 59 | |
| 60 | static bool odd_can_poweroff(struct ata_device *ata_dev) |
| 61 | { |
| 62 | acpi_handle handle; |
| 63 | acpi_status status; |
| 64 | struct acpi_device *acpi_dev; |
| 65 | |
| 66 | handle = ata_dev_acpi_handle(ata_dev); |
| 67 | if (!handle) |
| 68 | return false; |
| 69 | |
| 70 | status = acpi_bus_get_device(handle, &acpi_dev); |
| 71 | if (ACPI_FAILURE(status)) |
| 72 | return false; |
| 73 | |
| 74 | return acpi_device_can_poweroff(acpi_dev); |
| 75 | } |
| 76 | |
Aaron Lu | f064a20 | 2013-01-15 17:20:59 +0800 | [diff] [blame^] | 77 | static void zpodd_wake_dev(acpi_handle handle, u32 event, void *context) |
| 78 | { |
| 79 | struct ata_device *ata_dev = context; |
| 80 | struct zpodd *zpodd = ata_dev->zpodd; |
| 81 | struct device *dev = &ata_dev->sdev->sdev_gendev; |
| 82 | |
| 83 | if (event == ACPI_NOTIFY_DEVICE_WAKE && ata_dev && |
| 84 | pm_runtime_suspended(dev)) { |
| 85 | zpodd->from_notify = true; |
| 86 | pm_runtime_resume(dev); |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | static void ata_acpi_add_pm_notifier(struct ata_device *dev) |
| 91 | { |
| 92 | acpi_handle handle = ata_dev_acpi_handle(dev); |
| 93 | acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, |
| 94 | zpodd_wake_dev, dev); |
| 95 | } |
| 96 | |
| 97 | static void ata_acpi_remove_pm_notifier(struct ata_device *dev) |
| 98 | { |
| 99 | acpi_handle handle = DEVICE_ACPI_HANDLE(&dev->sdev->sdev_gendev); |
| 100 | acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY, zpodd_wake_dev); |
| 101 | } |
| 102 | |
Aaron Lu | afe7595 | 2013-01-15 17:20:58 +0800 | [diff] [blame] | 103 | void zpodd_init(struct ata_device *dev) |
| 104 | { |
| 105 | enum odd_mech_type mech_type; |
| 106 | struct zpodd *zpodd; |
| 107 | |
| 108 | if (dev->zpodd) |
| 109 | return; |
| 110 | |
| 111 | if (!odd_can_poweroff(dev)) |
| 112 | return; |
| 113 | |
| 114 | mech_type = zpodd_get_mech_type(dev); |
| 115 | if (mech_type == ODD_MECH_TYPE_UNSUPPORTED) |
| 116 | return; |
| 117 | |
| 118 | zpodd = kzalloc(sizeof(struct zpodd), GFP_KERNEL); |
| 119 | if (!zpodd) |
| 120 | return; |
| 121 | |
| 122 | zpodd->mech_type = mech_type; |
| 123 | |
Aaron Lu | f064a20 | 2013-01-15 17:20:59 +0800 | [diff] [blame^] | 124 | ata_acpi_add_pm_notifier(dev); |
Aaron Lu | afe7595 | 2013-01-15 17:20:58 +0800 | [diff] [blame] | 125 | zpodd->dev = dev; |
| 126 | dev->zpodd = zpodd; |
| 127 | } |
| 128 | |
| 129 | void zpodd_exit(struct ata_device *dev) |
| 130 | { |
Aaron Lu | f064a20 | 2013-01-15 17:20:59 +0800 | [diff] [blame^] | 131 | ata_acpi_remove_pm_notifier(dev); |
Aaron Lu | afe7595 | 2013-01-15 17:20:58 +0800 | [diff] [blame] | 132 | kfree(dev->zpodd); |
| 133 | dev->zpodd = NULL; |
| 134 | } |