Aaron Lu | afe7595 | 2013-01-15 17:20:58 +0800 | [diff] [blame^] | 1 | #include <linux/libata.h> |
| 2 | #include <linux/cdrom.h> |
| 3 | |
| 4 | #include "libata.h" |
| 5 | |
| 6 | enum odd_mech_type { |
| 7 | ODD_MECH_TYPE_SLOT, |
| 8 | ODD_MECH_TYPE_DRAWER, |
| 9 | ODD_MECH_TYPE_UNSUPPORTED, |
| 10 | }; |
| 11 | |
| 12 | struct zpodd { |
| 13 | enum odd_mech_type mech_type; /* init during probe, RO afterwards */ |
| 14 | struct ata_device *dev; |
| 15 | }; |
| 16 | |
| 17 | /* Per the spec, only slot type and drawer type ODD can be supported */ |
| 18 | static enum odd_mech_type zpodd_get_mech_type(struct ata_device *dev) |
| 19 | { |
| 20 | char buf[16]; |
| 21 | unsigned int ret; |
| 22 | struct rm_feature_desc *desc = (void *)(buf + 8); |
| 23 | struct ata_taskfile tf = {}; |
| 24 | |
| 25 | char cdb[] = { GPCMD_GET_CONFIGURATION, |
| 26 | 2, /* only 1 feature descriptor requested */ |
| 27 | 0, 3, /* 3, removable medium feature */ |
| 28 | 0, 0, 0,/* reserved */ |
| 29 | 0, sizeof(buf), |
| 30 | 0, 0, 0, |
| 31 | }; |
| 32 | |
| 33 | tf.flags = ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE; |
| 34 | tf.command = ATA_CMD_PACKET; |
| 35 | tf.protocol = ATAPI_PROT_PIO; |
| 36 | tf.lbam = sizeof(buf); |
| 37 | |
| 38 | ret = ata_exec_internal(dev, &tf, cdb, DMA_FROM_DEVICE, |
| 39 | buf, sizeof(buf), 0); |
| 40 | if (ret) |
| 41 | return ODD_MECH_TYPE_UNSUPPORTED; |
| 42 | |
| 43 | if (be16_to_cpu(desc->feature_code) != 3) |
| 44 | return ODD_MECH_TYPE_UNSUPPORTED; |
| 45 | |
| 46 | if (desc->mech_type == 0 && desc->load == 0 && desc->eject == 1) |
| 47 | return ODD_MECH_TYPE_SLOT; |
| 48 | else if (desc->mech_type == 1 && desc->load == 0 && desc->eject == 1) |
| 49 | return ODD_MECH_TYPE_DRAWER; |
| 50 | else |
| 51 | return ODD_MECH_TYPE_UNSUPPORTED; |
| 52 | } |
| 53 | |
| 54 | static bool odd_can_poweroff(struct ata_device *ata_dev) |
| 55 | { |
| 56 | acpi_handle handle; |
| 57 | acpi_status status; |
| 58 | struct acpi_device *acpi_dev; |
| 59 | |
| 60 | handle = ata_dev_acpi_handle(ata_dev); |
| 61 | if (!handle) |
| 62 | return false; |
| 63 | |
| 64 | status = acpi_bus_get_device(handle, &acpi_dev); |
| 65 | if (ACPI_FAILURE(status)) |
| 66 | return false; |
| 67 | |
| 68 | return acpi_device_can_poweroff(acpi_dev); |
| 69 | } |
| 70 | |
| 71 | void zpodd_init(struct ata_device *dev) |
| 72 | { |
| 73 | enum odd_mech_type mech_type; |
| 74 | struct zpodd *zpodd; |
| 75 | |
| 76 | if (dev->zpodd) |
| 77 | return; |
| 78 | |
| 79 | if (!odd_can_poweroff(dev)) |
| 80 | return; |
| 81 | |
| 82 | mech_type = zpodd_get_mech_type(dev); |
| 83 | if (mech_type == ODD_MECH_TYPE_UNSUPPORTED) |
| 84 | return; |
| 85 | |
| 86 | zpodd = kzalloc(sizeof(struct zpodd), GFP_KERNEL); |
| 87 | if (!zpodd) |
| 88 | return; |
| 89 | |
| 90 | zpodd->mech_type = mech_type; |
| 91 | |
| 92 | zpodd->dev = dev; |
| 93 | dev->zpodd = zpodd; |
| 94 | } |
| 95 | |
| 96 | void zpodd_exit(struct ata_device *dev) |
| 97 | { |
| 98 | kfree(dev->zpodd); |
| 99 | dev->zpodd = NULL; |
| 100 | } |