diff options
Diffstat (limited to 'drivers/ata/libata-zpodd.c')
-rw-r--r-- | drivers/ata/libata-zpodd.c | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/drivers/ata/libata-zpodd.c b/drivers/ata/libata-zpodd.c new file mode 100644 index 000000000000..27eed2f09a8a --- /dev/null +++ b/drivers/ata/libata-zpodd.c @@ -0,0 +1,100 @@ +#include <linux/libata.h> +#include <linux/cdrom.h> + +#include "libata.h" + +enum odd_mech_type { + ODD_MECH_TYPE_SLOT, + ODD_MECH_TYPE_DRAWER, + ODD_MECH_TYPE_UNSUPPORTED, +}; + +struct zpodd { + enum odd_mech_type mech_type; /* init during probe, RO afterwards */ + struct ata_device *dev; +}; + +/* Per the spec, only slot type and drawer type ODD can be supported */ +static enum odd_mech_type zpodd_get_mech_type(struct ata_device *dev) +{ + char buf[16]; + unsigned int ret; + struct rm_feature_desc *desc = (void *)(buf + 8); + struct ata_taskfile tf = {}; + + char cdb[] = { GPCMD_GET_CONFIGURATION, + 2, /* only 1 feature descriptor requested */ + 0, 3, /* 3, removable medium feature */ + 0, 0, 0,/* reserved */ + 0, sizeof(buf), + 0, 0, 0, + }; + + tf.flags = ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE; + tf.command = ATA_CMD_PACKET; + tf.protocol = ATAPI_PROT_PIO; + tf.lbam = sizeof(buf); + + ret = ata_exec_internal(dev, &tf, cdb, DMA_FROM_DEVICE, + buf, sizeof(buf), 0); + if (ret) + return ODD_MECH_TYPE_UNSUPPORTED; + + if (be16_to_cpu(desc->feature_code) != 3) + return ODD_MECH_TYPE_UNSUPPORTED; + + if (desc->mech_type == 0 && desc->load == 0 && desc->eject == 1) + return ODD_MECH_TYPE_SLOT; + else if (desc->mech_type == 1 && desc->load == 0 && desc->eject == 1) + return ODD_MECH_TYPE_DRAWER; + else + return ODD_MECH_TYPE_UNSUPPORTED; +} + +static bool odd_can_poweroff(struct ata_device *ata_dev) +{ + acpi_handle handle; + acpi_status status; + struct acpi_device *acpi_dev; + + handle = ata_dev_acpi_handle(ata_dev); + if (!handle) + return false; + + status = acpi_bus_get_device(handle, &acpi_dev); + if (ACPI_FAILURE(status)) + return false; + + return acpi_device_can_poweroff(acpi_dev); +} + +void zpodd_init(struct ata_device *dev) +{ + enum odd_mech_type mech_type; + struct zpodd *zpodd; + + if (dev->zpodd) + return; + + if (!odd_can_poweroff(dev)) + return; + + mech_type = zpodd_get_mech_type(dev); + if (mech_type == ODD_MECH_TYPE_UNSUPPORTED) + return; + + zpodd = kzalloc(sizeof(struct zpodd), GFP_KERNEL); + if (!zpodd) + return; + + zpodd->mech_type = mech_type; + + zpodd->dev = dev; + dev->zpodd = zpodd; +} + +void zpodd_exit(struct ata_device *dev) +{ + kfree(dev->zpodd); + dev->zpodd = NULL; +} |