brintos

brintos / linux-shallow public Read only

0
0
Text · 7.3 KiB · 4b83b51 Raw
296 lines · c
1// SPDX-License-Identifier: GPL-2.02#include <linux/libata.h>3#include <linux/cdrom.h>4#include <linux/pm_runtime.h>5#include <linux/module.h>6#include <linux/pm_qos.h>7#include <scsi/scsi_device.h>8 9#include "libata.h"10 11static int zpodd_poweroff_delay = 30; /* 30 seconds for power off delay */12module_param(zpodd_poweroff_delay, int, 0644);13MODULE_PARM_DESC(zpodd_poweroff_delay, "Poweroff delay for ZPODD in seconds");14 15enum odd_mech_type {16	ODD_MECH_TYPE_SLOT,17	ODD_MECH_TYPE_DRAWER,18	ODD_MECH_TYPE_UNSUPPORTED,19};20 21struct zpodd {22	enum odd_mech_type	mech_type; /* init during probe, RO afterwards */23	struct ata_device	*dev;24 25	/* The following fields are synchronized by PM core. */26	bool			from_notify; /* resumed as a result of27					      * acpi wake notification */28	bool			zp_ready; /* ZP ready state */29	unsigned long		last_ready; /* last ZP ready timestamp */30	bool			zp_sampled; /* ZP ready state sampled */31	bool			powered_off; /* ODD is powered off32					      *	during suspend */33};34 35static int eject_tray(struct ata_device *dev)36{37	struct ata_taskfile tf;38	static const char cdb[ATAPI_CDB_LEN] = {  GPCMD_START_STOP_UNIT,39		0, 0, 0,40		0x02,     /* LoEj */41		0, 0, 0, 0, 0, 0, 0,42	};43 44	ata_tf_init(dev, &tf);45	tf.flags = ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE;46	tf.command = ATA_CMD_PACKET;47	tf.protocol = ATAPI_PROT_NODATA;48 49	return ata_exec_internal(dev, &tf, cdb, DMA_NONE, NULL, 0, 0);50}51 52/* Per the spec, only slot type and drawer type ODD can be supported */53static enum odd_mech_type zpodd_get_mech_type(struct ata_device *dev)54{55	char *buf;56	unsigned int ret;57	struct rm_feature_desc *desc;58	struct ata_taskfile tf;59	static const char cdb[ATAPI_CDB_LEN] = {  GPCMD_GET_CONFIGURATION,60			2,      /* only 1 feature descriptor requested */61			0, 3,   /* 3, removable medium feature */62			0, 0, 0,/* reserved */63			0, 16,64			0, 0, 0,65	};66 67	buf = kzalloc(16, GFP_KERNEL);68	if (!buf)69		return ODD_MECH_TYPE_UNSUPPORTED;70	desc = (void *)(buf + 8);71 72	ata_tf_init(dev, &tf);73	tf.flags = ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE;74	tf.command = ATA_CMD_PACKET;75	tf.protocol = ATAPI_PROT_PIO;76	tf.lbam = 16;77 78	ret = ata_exec_internal(dev, &tf, cdb, DMA_FROM_DEVICE,79				buf, 16, 0);80	if (ret) {81		kfree(buf);82		return ODD_MECH_TYPE_UNSUPPORTED;83	}84 85	if (be16_to_cpu(desc->feature_code) != 3) {86		kfree(buf);87		return ODD_MECH_TYPE_UNSUPPORTED;88	}89 90	if (desc->mech_type == 0 && desc->load == 0 && desc->eject == 1) {91		kfree(buf);92		return ODD_MECH_TYPE_SLOT;93	} else if (desc->mech_type == 1 && desc->load == 0 &&94		   desc->eject == 1) {95		kfree(buf);96		return ODD_MECH_TYPE_DRAWER;97	} else {98		kfree(buf);99		return ODD_MECH_TYPE_UNSUPPORTED;100	}101}102 103/* Test if ODD is zero power ready by sense code */104static bool zpready(struct ata_device *dev)105{106	u8 sense_key, *sense_buf;107	unsigned int ret, asc, ascq, add_len;108	struct zpodd *zpodd = dev->zpodd;109 110	ret = atapi_eh_tur(dev, &sense_key);111 112	if (!ret || sense_key != NOT_READY)113		return false;114 115	sense_buf = dev->sector_buf;116	ret = atapi_eh_request_sense(dev, sense_buf, sense_key);117	if (ret)118		return false;119 120	/* sense valid */121	if ((sense_buf[0] & 0x7f) != 0x70)122		return false;123 124	add_len = sense_buf[7];125	/* has asc and ascq */126	if (add_len < 6)127		return false;128 129	asc = sense_buf[12];130	ascq = sense_buf[13];131 132	if (zpodd->mech_type == ODD_MECH_TYPE_SLOT)133		/* no media inside */134		return asc == 0x3a;135	else136		/* no media inside and door closed */137		return asc == 0x3a && ascq == 0x01;138}139 140/*141 * Update the zpodd->zp_ready field. This field will only be set142 * if the ODD has stayed in ZP ready state for zpodd_poweroff_delay143 * time, and will be used to decide if power off is allowed. If it144 * is set, it will be cleared during resume from powered off state.145 */146void zpodd_on_suspend(struct ata_device *dev)147{148	struct zpodd *zpodd = dev->zpodd;149	unsigned long expires;150 151	if (!zpready(dev)) {152		zpodd->zp_sampled = false;153		zpodd->zp_ready = false;154		return;155	}156 157	if (!zpodd->zp_sampled) {158		zpodd->zp_sampled = true;159		zpodd->last_ready = jiffies;160		return;161	}162 163	expires = zpodd->last_ready +164		  msecs_to_jiffies(zpodd_poweroff_delay * 1000);165	if (time_before(jiffies, expires))166		return;167 168	zpodd->zp_ready = true;169}170 171bool zpodd_zpready(struct ata_device *dev)172{173	struct zpodd *zpodd = dev->zpodd;174	return zpodd->zp_ready;175}176 177/*178 * Enable runtime wake capability through ACPI and set the powered_off flag,179 * this flag will be used during resume to decide what operations are needed180 * to take.181 *182 * Also, media poll needs to be silenced, so that it doesn't bring the ODD183 * back to full power state every few seconds.184 */185void zpodd_enable_run_wake(struct ata_device *dev)186{187	struct zpodd *zpodd = dev->zpodd;188 189	sdev_disable_disk_events(dev->sdev);190 191	zpodd->powered_off = true;192	acpi_pm_set_device_wakeup(&dev->tdev, true);193}194 195/* Disable runtime wake capability if it is enabled */196void zpodd_disable_run_wake(struct ata_device *dev)197{198	struct zpodd *zpodd = dev->zpodd;199 200	if (zpodd->powered_off)201		acpi_pm_set_device_wakeup(&dev->tdev, false);202}203 204/*205 * Post power on processing after the ODD has been recovered. If the206 * ODD wasn't powered off during suspend, it doesn't do anything.207 *208 * For drawer type ODD, if it is powered on due to user pressed the209 * eject button, the tray needs to be ejected. This can only be done210 * after the ODD has been recovered, i.e. link is initialized and211 * device is able to process NON_DATA PIO command, as eject needs to212 * send command for the ODD to process.213 *214 * The from_notify flag set in wake notification handler function215 * zpodd_wake_dev represents if power on is due to user's action.216 *217 * For both types of ODD, several fields need to be reset.218 */219void zpodd_post_poweron(struct ata_device *dev)220{221	struct zpodd *zpodd = dev->zpodd;222 223	if (!zpodd->powered_off)224		return;225 226	zpodd->powered_off = false;227 228	if (zpodd->from_notify) {229		zpodd->from_notify = false;230		if (zpodd->mech_type == ODD_MECH_TYPE_DRAWER)231			eject_tray(dev);232	}233 234	zpodd->zp_sampled = false;235	zpodd->zp_ready = false;236 237	sdev_enable_disk_events(dev->sdev);238}239 240static void zpodd_wake_dev(acpi_handle handle, u32 event, void *context)241{242	struct ata_device *ata_dev = context;243	struct zpodd *zpodd = ata_dev->zpodd;244	struct device *dev = &ata_dev->sdev->sdev_gendev;245 246	if (event == ACPI_NOTIFY_DEVICE_WAKE && pm_runtime_suspended(dev)) {247		zpodd->from_notify = true;248		pm_runtime_resume(dev);249	}250}251 252static void ata_acpi_add_pm_notifier(struct ata_device *dev)253{254	acpi_handle handle = ata_dev_acpi_handle(dev);255	acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,256				    zpodd_wake_dev, dev);257}258 259static void ata_acpi_remove_pm_notifier(struct ata_device *dev)260{261	acpi_handle handle = ata_dev_acpi_handle(dev);262	acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY, zpodd_wake_dev);263}264 265void zpodd_init(struct ata_device *dev)266{267	struct acpi_device *adev = ACPI_COMPANION(&dev->tdev);268	enum odd_mech_type mech_type;269	struct zpodd *zpodd;270 271	if (dev->zpodd || !adev || !acpi_device_can_poweroff(adev))272		return;273 274	mech_type = zpodd_get_mech_type(dev);275	if (mech_type == ODD_MECH_TYPE_UNSUPPORTED)276		return;277 278	zpodd = kzalloc(sizeof(struct zpodd), GFP_KERNEL);279	if (!zpodd)280		return;281 282	zpodd->mech_type = mech_type;283 284	ata_acpi_add_pm_notifier(dev);285	zpodd->dev = dev;286	dev->zpodd = zpodd;287	dev_pm_qos_expose_flags(&dev->tdev, 0);288}289 290void zpodd_exit(struct ata_device *dev)291{292	ata_acpi_remove_pm_notifier(dev);293	kfree(dev->zpodd);294	dev->zpodd = NULL;295}296