libata: move acpi notification code to zpodd
[deliverable/linux.git] / drivers / ata / libata-zpodd.c
CommitLineData
afe75951
AL
1#include <linux/libata.h>
2#include <linux/cdrom.h>
f064a20d
AL
3#include <linux/pm_runtime.h>
4#include <scsi/scsi_device.h>
afe75951
AL
5
6#include "libata.h"
7
8enum odd_mech_type {
9 ODD_MECH_TYPE_SLOT,
10 ODD_MECH_TYPE_DRAWER,
11 ODD_MECH_TYPE_UNSUPPORTED,
12};
13
14struct zpodd {
15 enum odd_mech_type mech_type; /* init during probe, RO afterwards */
16 struct ata_device *dev;
f064a20d
AL
17
18 /* The following fields are synchronized by PM core. */
19 bool from_notify; /* resumed as a result of
20 * acpi wake notification */
afe75951
AL
21};
22
23/* Per the spec, only slot type and drawer type ODD can be supported */
24static 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
60static 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
f064a20d
AL
77static 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
90static 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
97static 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
afe75951
AL
103void 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
f064a20d 124 ata_acpi_add_pm_notifier(dev);
afe75951
AL
125 zpodd->dev = dev;
126 dev->zpodd = zpodd;
127}
128
129void zpodd_exit(struct ata_device *dev)
130{
f064a20d 131 ata_acpi_remove_pm_notifier(dev);
afe75951
AL
132 kfree(dev->zpodd);
133 dev->zpodd = NULL;
134}
This page took 0.037024 seconds and 5 git commands to generate.