11d609992SMaximilian Luz // SPDX-License-Identifier: GPL-2.0+
21d609992SMaximilian Luz /*
31d609992SMaximilian Luz  * Surface Book (gen. 2 and later) detachment system (DTX) driver.
41d609992SMaximilian Luz  *
51d609992SMaximilian Luz  * Provides a user-space interface to properly handle clipboard/tablet
61d609992SMaximilian Luz  * (containing screen and processor) detachment from the base of the device
71d609992SMaximilian Luz  * (containing the keyboard and optionally a discrete GPU). Allows to
81d609992SMaximilian Luz  * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in
91d609992SMaximilian Luz  * use), or request detachment via user-space.
101d609992SMaximilian Luz  *
11221756e6SMaximilian Luz  * Copyright (C) 2019-2022 Maximilian Luz <[email protected]>
121d609992SMaximilian Luz  */
131d609992SMaximilian Luz 
141d609992SMaximilian Luz #include <linux/fs.h>
151d609992SMaximilian Luz #include <linux/input.h>
161d609992SMaximilian Luz #include <linux/ioctl.h>
171d609992SMaximilian Luz #include <linux/kernel.h>
181d609992SMaximilian Luz #include <linux/kfifo.h>
191d609992SMaximilian Luz #include <linux/kref.h>
201d609992SMaximilian Luz #include <linux/miscdevice.h>
211d609992SMaximilian Luz #include <linux/module.h>
221d609992SMaximilian Luz #include <linux/mutex.h>
231d609992SMaximilian Luz #include <linux/platform_device.h>
241d609992SMaximilian Luz #include <linux/poll.h>
251d609992SMaximilian Luz #include <linux/rwsem.h>
261d609992SMaximilian Luz #include <linux/slab.h>
271d609992SMaximilian Luz #include <linux/workqueue.h>
281d609992SMaximilian Luz 
291d609992SMaximilian Luz #include <linux/surface_aggregator/controller.h>
30e893d45fSMaximilian Luz #include <linux/surface_aggregator/device.h>
311d609992SMaximilian Luz #include <linux/surface_aggregator/dtx.h>
321d609992SMaximilian Luz 
331d609992SMaximilian Luz 
341d609992SMaximilian Luz /* -- SSAM interface. ------------------------------------------------------- */
351d609992SMaximilian Luz 
361d609992SMaximilian Luz enum sam_event_cid_bas {
371d609992SMaximilian Luz 	SAM_EVENT_CID_DTX_CONNECTION			= 0x0c,
381d609992SMaximilian Luz 	SAM_EVENT_CID_DTX_REQUEST			= 0x0e,
391d609992SMaximilian Luz 	SAM_EVENT_CID_DTX_CANCEL			= 0x0f,
401d609992SMaximilian Luz 	SAM_EVENT_CID_DTX_LATCH_STATUS			= 0x11,
411d609992SMaximilian Luz };
421d609992SMaximilian Luz 
431d609992SMaximilian Luz enum ssam_bas_base_state {
441d609992SMaximilian Luz 	SSAM_BAS_BASE_STATE_DETACH_SUCCESS		= 0x00,
451d609992SMaximilian Luz 	SSAM_BAS_BASE_STATE_ATTACHED			= 0x01,
461d609992SMaximilian Luz 	SSAM_BAS_BASE_STATE_NOT_FEASIBLE		= 0x02,
471d609992SMaximilian Luz };
481d609992SMaximilian Luz 
491d609992SMaximilian Luz enum ssam_bas_latch_status {
501d609992SMaximilian Luz 	SSAM_BAS_LATCH_STATUS_CLOSED			= 0x00,
511d609992SMaximilian Luz 	SSAM_BAS_LATCH_STATUS_OPENED			= 0x01,
521d609992SMaximilian Luz 	SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN		= 0x02,
531d609992SMaximilian Luz 	SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN	= 0x03,
541d609992SMaximilian Luz 	SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE		= 0x04,
551d609992SMaximilian Luz };
561d609992SMaximilian Luz 
571d609992SMaximilian Luz enum ssam_bas_cancel_reason {
581d609992SMaximilian Luz 	SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE		= 0x00,  /* Low battery. */
591d609992SMaximilian Luz 	SSAM_BAS_CANCEL_REASON_TIMEOUT			= 0x02,
601d609992SMaximilian Luz 	SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN		= 0x03,
611d609992SMaximilian Luz 	SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN	= 0x04,
621d609992SMaximilian Luz 	SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE		= 0x05,
631d609992SMaximilian Luz };
641d609992SMaximilian Luz 
651d609992SMaximilian Luz struct ssam_bas_base_info {
661d609992SMaximilian Luz 	u8 state;
671d609992SMaximilian Luz 	u8 base_id;
681d609992SMaximilian Luz } __packed;
691d609992SMaximilian Luz 
701d609992SMaximilian Luz static_assert(sizeof(struct ssam_bas_base_info) == 2);
711d609992SMaximilian Luz 
721d609992SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, {
731d609992SMaximilian Luz 	.target_category = SSAM_SSH_TC_BAS,
741e6201d9SMaximilian Luz 	.target_id       = SSAM_SSH_TID_SAM,
751d609992SMaximilian Luz 	.command_id      = 0x06,
761d609992SMaximilian Luz 	.instance_id     = 0x00,
771d609992SMaximilian Luz });
781d609992SMaximilian Luz 
791d609992SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, {
801d609992SMaximilian Luz 	.target_category = SSAM_SSH_TC_BAS,
811e6201d9SMaximilian Luz 	.target_id       = SSAM_SSH_TID_SAM,
821d609992SMaximilian Luz 	.command_id      = 0x07,
831d609992SMaximilian Luz 	.instance_id     = 0x00,
841d609992SMaximilian Luz });
851d609992SMaximilian Luz 
861d609992SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, {
871d609992SMaximilian Luz 	.target_category = SSAM_SSH_TC_BAS,
881e6201d9SMaximilian Luz 	.target_id       = SSAM_SSH_TID_SAM,
891d609992SMaximilian Luz 	.command_id      = 0x08,
901d609992SMaximilian Luz 	.instance_id     = 0x00,
911d609992SMaximilian Luz });
921d609992SMaximilian Luz 
931d609992SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, {
941d609992SMaximilian Luz 	.target_category = SSAM_SSH_TC_BAS,
951e6201d9SMaximilian Luz 	.target_id       = SSAM_SSH_TID_SAM,
961d609992SMaximilian Luz 	.command_id      = 0x09,
971d609992SMaximilian Luz 	.instance_id     = 0x00,
981d609992SMaximilian Luz });
991d609992SMaximilian Luz 
1001d609992SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, {
1011d609992SMaximilian Luz 	.target_category = SSAM_SSH_TC_BAS,
1021e6201d9SMaximilian Luz 	.target_id       = SSAM_SSH_TID_SAM,
1031d609992SMaximilian Luz 	.command_id      = 0x0a,
1041d609992SMaximilian Luz 	.instance_id     = 0x00,
1051d609992SMaximilian Luz });
1061d609992SMaximilian Luz 
1071d609992SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, {
1081d609992SMaximilian Luz 	.target_category = SSAM_SSH_TC_BAS,
1091e6201d9SMaximilian Luz 	.target_id       = SSAM_SSH_TID_SAM,
1101d609992SMaximilian Luz 	.command_id      = 0x0b,
1111d609992SMaximilian Luz 	.instance_id     = 0x00,
1121d609992SMaximilian Luz });
1131d609992SMaximilian Luz 
1141d609992SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, {
1151d609992SMaximilian Luz 	.target_category = SSAM_SSH_TC_BAS,
1161e6201d9SMaximilian Luz 	.target_id       = SSAM_SSH_TID_SAM,
1171d609992SMaximilian Luz 	.command_id      = 0x0c,
1181d609992SMaximilian Luz 	.instance_id     = 0x00,
1191d609992SMaximilian Luz });
1201d609992SMaximilian Luz 
1211d609992SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, {
1221d609992SMaximilian Luz 	.target_category = SSAM_SSH_TC_BAS,
1231e6201d9SMaximilian Luz 	.target_id       = SSAM_SSH_TID_SAM,
1241d609992SMaximilian Luz 	.command_id      = 0x0d,
1251d609992SMaximilian Luz 	.instance_id     = 0x00,
1261d609992SMaximilian Luz });
1271d609992SMaximilian Luz 
1281d609992SMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, {
1291d609992SMaximilian Luz 	.target_category = SSAM_SSH_TC_BAS,
1301e6201d9SMaximilian Luz 	.target_id       = SSAM_SSH_TID_SAM,
1311d609992SMaximilian Luz 	.command_id      = 0x11,
1321d609992SMaximilian Luz 	.instance_id     = 0x00,
1331d609992SMaximilian Luz });
1341d609992SMaximilian Luz 
1351d609992SMaximilian Luz 
1361d609992SMaximilian Luz /* -- Main structures. ------------------------------------------------------ */
1371d609992SMaximilian Luz 
1381d609992SMaximilian Luz enum sdtx_device_state {
1391d609992SMaximilian Luz 	SDTX_DEVICE_SHUTDOWN_BIT    = BIT(0),
1401d609992SMaximilian Luz 	SDTX_DEVICE_DIRTY_BASE_BIT  = BIT(1),
1411d609992SMaximilian Luz 	SDTX_DEVICE_DIRTY_MODE_BIT  = BIT(2),
1421d609992SMaximilian Luz 	SDTX_DEVICE_DIRTY_LATCH_BIT = BIT(3),
1431d609992SMaximilian Luz };
1441d609992SMaximilian Luz 
1451d609992SMaximilian Luz struct sdtx_device {
1461d609992SMaximilian Luz 	struct kref kref;
1471d609992SMaximilian Luz 	struct rw_semaphore lock;         /* Guards device and controller reference. */
1481d609992SMaximilian Luz 
1491d609992SMaximilian Luz 	struct device *dev;
1501d609992SMaximilian Luz 	struct ssam_controller *ctrl;
1511d609992SMaximilian Luz 	unsigned long flags;
1521d609992SMaximilian Luz 
1531d609992SMaximilian Luz 	struct miscdevice mdev;
1541d609992SMaximilian Luz 	wait_queue_head_t waitq;
1551d609992SMaximilian Luz 	struct mutex write_lock;          /* Guards order of events/notifications. */
1561d609992SMaximilian Luz 	struct rw_semaphore client_lock;  /* Guards client list.                   */
1571d609992SMaximilian Luz 	struct list_head client_list;
1581d609992SMaximilian Luz 
1591d609992SMaximilian Luz 	struct delayed_work state_work;
1601d609992SMaximilian Luz 	struct {
1611d609992SMaximilian Luz 		struct ssam_bas_base_info base;
1621d609992SMaximilian Luz 		u8 device_mode;
1631d609992SMaximilian Luz 		u8 latch_status;
1641d609992SMaximilian Luz 	} state;
1651d609992SMaximilian Luz 
1661d609992SMaximilian Luz 	struct delayed_work mode_work;
1671d609992SMaximilian Luz 	struct input_dev *mode_switch;
1681d609992SMaximilian Luz 
1691d609992SMaximilian Luz 	struct ssam_event_notifier notif;
1701d609992SMaximilian Luz };
1711d609992SMaximilian Luz 
1721d609992SMaximilian Luz enum sdtx_client_state {
1731d609992SMaximilian Luz 	SDTX_CLIENT_EVENTS_ENABLED_BIT = BIT(0),
1741d609992SMaximilian Luz };
1751d609992SMaximilian Luz 
1761d609992SMaximilian Luz struct sdtx_client {
1771d609992SMaximilian Luz 	struct sdtx_device *ddev;
1781d609992SMaximilian Luz 	struct list_head node;
1791d609992SMaximilian Luz 	unsigned long flags;
1801d609992SMaximilian Luz 
1811d609992SMaximilian Luz 	struct fasync_struct *fasync;
1821d609992SMaximilian Luz 
1831d609992SMaximilian Luz 	struct mutex read_lock;           /* Guards FIFO buffer read access. */
1841d609992SMaximilian Luz 	DECLARE_KFIFO(buffer, u8, 512);
1851d609992SMaximilian Luz };
1861d609992SMaximilian Luz 
__sdtx_device_release(struct kref * kref)1871d609992SMaximilian Luz static void __sdtx_device_release(struct kref *kref)
1881d609992SMaximilian Luz {
1891d609992SMaximilian Luz 	struct sdtx_device *ddev = container_of(kref, struct sdtx_device, kref);
1901d609992SMaximilian Luz 
1911d609992SMaximilian Luz 	mutex_destroy(&ddev->write_lock);
1921d609992SMaximilian Luz 	kfree(ddev);
1931d609992SMaximilian Luz }
1941d609992SMaximilian Luz 
sdtx_device_get(struct sdtx_device * ddev)1951d609992SMaximilian Luz static struct sdtx_device *sdtx_device_get(struct sdtx_device *ddev)
1961d609992SMaximilian Luz {
1971d609992SMaximilian Luz 	if (ddev)
1981d609992SMaximilian Luz 		kref_get(&ddev->kref);
1991d609992SMaximilian Luz 
2001d609992SMaximilian Luz 	return ddev;
2011d609992SMaximilian Luz }
2021d609992SMaximilian Luz 
sdtx_device_put(struct sdtx_device * ddev)2031d609992SMaximilian Luz static void sdtx_device_put(struct sdtx_device *ddev)
2041d609992SMaximilian Luz {
2051d609992SMaximilian Luz 	if (ddev)
2061d609992SMaximilian Luz 		kref_put(&ddev->kref, __sdtx_device_release);
2071d609992SMaximilian Luz }
2081d609992SMaximilian Luz 
2091d609992SMaximilian Luz 
2101d609992SMaximilian Luz /* -- Firmware value translations. ------------------------------------------ */
2111d609992SMaximilian Luz 
sdtx_translate_base_state(struct sdtx_device * ddev,u8 state)2121d609992SMaximilian Luz static u16 sdtx_translate_base_state(struct sdtx_device *ddev, u8 state)
2131d609992SMaximilian Luz {
2141d609992SMaximilian Luz 	switch (state) {
2151d609992SMaximilian Luz 	case SSAM_BAS_BASE_STATE_ATTACHED:
2161d609992SMaximilian Luz 		return SDTX_BASE_ATTACHED;
2171d609992SMaximilian Luz 
2181d609992SMaximilian Luz 	case SSAM_BAS_BASE_STATE_DETACH_SUCCESS:
2191d609992SMaximilian Luz 		return SDTX_BASE_DETACHED;
2201d609992SMaximilian Luz 
2211d609992SMaximilian Luz 	case SSAM_BAS_BASE_STATE_NOT_FEASIBLE:
2221d609992SMaximilian Luz 		return SDTX_DETACH_NOT_FEASIBLE;
2231d609992SMaximilian Luz 
2241d609992SMaximilian Luz 	default:
2251d609992SMaximilian Luz 		dev_err(ddev->dev, "unknown base state: %#04x\n", state);
2261d609992SMaximilian Luz 		return SDTX_UNKNOWN(state);
2271d609992SMaximilian Luz 	}
2281d609992SMaximilian Luz }
2291d609992SMaximilian Luz 
sdtx_translate_latch_status(struct sdtx_device * ddev,u8 status)2301d609992SMaximilian Luz static u16 sdtx_translate_latch_status(struct sdtx_device *ddev, u8 status)
2311d609992SMaximilian Luz {
2321d609992SMaximilian Luz 	switch (status) {
2331d609992SMaximilian Luz 	case SSAM_BAS_LATCH_STATUS_CLOSED:
2341d609992SMaximilian Luz 		return SDTX_LATCH_CLOSED;
2351d609992SMaximilian Luz 
2361d609992SMaximilian Luz 	case SSAM_BAS_LATCH_STATUS_OPENED:
2371d609992SMaximilian Luz 		return SDTX_LATCH_OPENED;
2381d609992SMaximilian Luz 
2391d609992SMaximilian Luz 	case SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN:
2401d609992SMaximilian Luz 		return SDTX_ERR_FAILED_TO_OPEN;
2411d609992SMaximilian Luz 
2421d609992SMaximilian Luz 	case SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN:
2431d609992SMaximilian Luz 		return SDTX_ERR_FAILED_TO_REMAIN_OPEN;
2441d609992SMaximilian Luz 
2451d609992SMaximilian Luz 	case SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE:
2461d609992SMaximilian Luz 		return SDTX_ERR_FAILED_TO_CLOSE;
2471d609992SMaximilian Luz 
2481d609992SMaximilian Luz 	default:
2491d609992SMaximilian Luz 		dev_err(ddev->dev, "unknown latch status: %#04x\n", status);
2501d609992SMaximilian Luz 		return SDTX_UNKNOWN(status);
2511d609992SMaximilian Luz 	}
2521d609992SMaximilian Luz }
2531d609992SMaximilian Luz 
sdtx_translate_cancel_reason(struct sdtx_device * ddev,u8 reason)2541d609992SMaximilian Luz static u16 sdtx_translate_cancel_reason(struct sdtx_device *ddev, u8 reason)
2551d609992SMaximilian Luz {
2561d609992SMaximilian Luz 	switch (reason) {
2571d609992SMaximilian Luz 	case SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE:
2581d609992SMaximilian Luz 		return SDTX_DETACH_NOT_FEASIBLE;
2591d609992SMaximilian Luz 
2601d609992SMaximilian Luz 	case SSAM_BAS_CANCEL_REASON_TIMEOUT:
2611d609992SMaximilian Luz 		return SDTX_DETACH_TIMEDOUT;
2621d609992SMaximilian Luz 
2631d609992SMaximilian Luz 	case SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN:
2641d609992SMaximilian Luz 		return SDTX_ERR_FAILED_TO_OPEN;
2651d609992SMaximilian Luz 
2661d609992SMaximilian Luz 	case SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN:
2671d609992SMaximilian Luz 		return SDTX_ERR_FAILED_TO_REMAIN_OPEN;
2681d609992SMaximilian Luz 
2691d609992SMaximilian Luz 	case SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE:
2701d609992SMaximilian Luz 		return SDTX_ERR_FAILED_TO_CLOSE;
2711d609992SMaximilian Luz 
2721d609992SMaximilian Luz 	default:
2731d609992SMaximilian Luz 		dev_err(ddev->dev, "unknown cancel reason: %#04x\n", reason);
2741d609992SMaximilian Luz 		return SDTX_UNKNOWN(reason);
2751d609992SMaximilian Luz 	}
2761d609992SMaximilian Luz }
2771d609992SMaximilian Luz 
2781d609992SMaximilian Luz 
2791d609992SMaximilian Luz /* -- IOCTLs. --------------------------------------------------------------- */
2801d609992SMaximilian Luz 
sdtx_ioctl_get_base_info(struct sdtx_device * ddev,struct sdtx_base_info __user * buf)2811d609992SMaximilian Luz static int sdtx_ioctl_get_base_info(struct sdtx_device *ddev,
2821d609992SMaximilian Luz 				    struct sdtx_base_info __user *buf)
2831d609992SMaximilian Luz {
2841d609992SMaximilian Luz 	struct ssam_bas_base_info raw;
2851d609992SMaximilian Luz 	struct sdtx_base_info info;
2861d609992SMaximilian Luz 	int status;
2871d609992SMaximilian Luz 
2881d609992SMaximilian Luz 	lockdep_assert_held_read(&ddev->lock);
2891d609992SMaximilian Luz 
2901d609992SMaximilian Luz 	status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &raw);
2911d609992SMaximilian Luz 	if (status < 0)
2921d609992SMaximilian Luz 		return status;
2931d609992SMaximilian Luz 
2941d609992SMaximilian Luz 	info.state = sdtx_translate_base_state(ddev, raw.state);
2951d609992SMaximilian Luz 	info.base_id = SDTX_BASE_TYPE_SSH(raw.base_id);
2961d609992SMaximilian Luz 
2971d609992SMaximilian Luz 	if (copy_to_user(buf, &info, sizeof(info)))
2981d609992SMaximilian Luz 		return -EFAULT;
2991d609992SMaximilian Luz 
3001d609992SMaximilian Luz 	return 0;
3011d609992SMaximilian Luz }
3021d609992SMaximilian Luz 
sdtx_ioctl_get_device_mode(struct sdtx_device * ddev,u16 __user * buf)3031d609992SMaximilian Luz static int sdtx_ioctl_get_device_mode(struct sdtx_device *ddev, u16 __user *buf)
3041d609992SMaximilian Luz {
3051d609992SMaximilian Luz 	u8 mode;
3061d609992SMaximilian Luz 	int status;
3071d609992SMaximilian Luz 
3081d609992SMaximilian Luz 	lockdep_assert_held_read(&ddev->lock);
3091d609992SMaximilian Luz 
3101d609992SMaximilian Luz 	status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
3111d609992SMaximilian Luz 	if (status < 0)
3121d609992SMaximilian Luz 		return status;
3131d609992SMaximilian Luz 
3141d609992SMaximilian Luz 	return put_user(mode, buf);
3151d609992SMaximilian Luz }
3161d609992SMaximilian Luz 
sdtx_ioctl_get_latch_status(struct sdtx_device * ddev,u16 __user * buf)3171d609992SMaximilian Luz static int sdtx_ioctl_get_latch_status(struct sdtx_device *ddev, u16 __user *buf)
3181d609992SMaximilian Luz {
3191d609992SMaximilian Luz 	u8 latch;
3201d609992SMaximilian Luz 	int status;
3211d609992SMaximilian Luz 
3221d609992SMaximilian Luz 	lockdep_assert_held_read(&ddev->lock);
3231d609992SMaximilian Luz 
3241d609992SMaximilian Luz 	status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch);
3251d609992SMaximilian Luz 	if (status < 0)
3261d609992SMaximilian Luz 		return status;
3271d609992SMaximilian Luz 
3281d609992SMaximilian Luz 	return put_user(sdtx_translate_latch_status(ddev, latch), buf);
3291d609992SMaximilian Luz }
3301d609992SMaximilian Luz 
__surface_dtx_ioctl(struct sdtx_client * client,unsigned int cmd,unsigned long arg)3311d609992SMaximilian Luz static long __surface_dtx_ioctl(struct sdtx_client *client, unsigned int cmd, unsigned long arg)
3321d609992SMaximilian Luz {
3331d609992SMaximilian Luz 	struct sdtx_device *ddev = client->ddev;
3341d609992SMaximilian Luz 
3351d609992SMaximilian Luz 	lockdep_assert_held_read(&ddev->lock);
3361d609992SMaximilian Luz 
3371d609992SMaximilian Luz 	switch (cmd) {
3381d609992SMaximilian Luz 	case SDTX_IOCTL_EVENTS_ENABLE:
3391d609992SMaximilian Luz 		set_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags);
3401d609992SMaximilian Luz 		return 0;
3411d609992SMaximilian Luz 
3421d609992SMaximilian Luz 	case SDTX_IOCTL_EVENTS_DISABLE:
3431d609992SMaximilian Luz 		clear_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags);
3441d609992SMaximilian Luz 		return 0;
3451d609992SMaximilian Luz 
3461d609992SMaximilian Luz 	case SDTX_IOCTL_LATCH_LOCK:
3471d609992SMaximilian Luz 		return ssam_retry(ssam_bas_latch_lock, ddev->ctrl);
3481d609992SMaximilian Luz 
3491d609992SMaximilian Luz 	case SDTX_IOCTL_LATCH_UNLOCK:
3501d609992SMaximilian Luz 		return ssam_retry(ssam_bas_latch_unlock, ddev->ctrl);
3511d609992SMaximilian Luz 
3521d609992SMaximilian Luz 	case SDTX_IOCTL_LATCH_REQUEST:
3531d609992SMaximilian Luz 		return ssam_retry(ssam_bas_latch_request, ddev->ctrl);
3541d609992SMaximilian Luz 
3551d609992SMaximilian Luz 	case SDTX_IOCTL_LATCH_CONFIRM:
3561d609992SMaximilian Luz 		return ssam_retry(ssam_bas_latch_confirm, ddev->ctrl);
3571d609992SMaximilian Luz 
3581d609992SMaximilian Luz 	case SDTX_IOCTL_LATCH_HEARTBEAT:
3591d609992SMaximilian Luz 		return ssam_retry(ssam_bas_latch_heartbeat, ddev->ctrl);
3601d609992SMaximilian Luz 
3611d609992SMaximilian Luz 	case SDTX_IOCTL_LATCH_CANCEL:
3621d609992SMaximilian Luz 		return ssam_retry(ssam_bas_latch_cancel, ddev->ctrl);
3631d609992SMaximilian Luz 
3641d609992SMaximilian Luz 	case SDTX_IOCTL_GET_BASE_INFO:
3651d609992SMaximilian Luz 		return sdtx_ioctl_get_base_info(ddev, (struct sdtx_base_info __user *)arg);
3661d609992SMaximilian Luz 
3671d609992SMaximilian Luz 	case SDTX_IOCTL_GET_DEVICE_MODE:
3681d609992SMaximilian Luz 		return sdtx_ioctl_get_device_mode(ddev, (u16 __user *)arg);
3691d609992SMaximilian Luz 
3701d609992SMaximilian Luz 	case SDTX_IOCTL_GET_LATCH_STATUS:
3711d609992SMaximilian Luz 		return sdtx_ioctl_get_latch_status(ddev, (u16 __user *)arg);
3721d609992SMaximilian Luz 
3731d609992SMaximilian Luz 	default:
3741d609992SMaximilian Luz 		return -EINVAL;
3751d609992SMaximilian Luz 	}
3761d609992SMaximilian Luz }
3771d609992SMaximilian Luz 
surface_dtx_ioctl(struct file * file,unsigned int cmd,unsigned long arg)3781d609992SMaximilian Luz static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
3791d609992SMaximilian Luz {
3801d609992SMaximilian Luz 	struct sdtx_client *client = file->private_data;
3811d609992SMaximilian Luz 	long status;
3821d609992SMaximilian Luz 
3831d609992SMaximilian Luz 	if (down_read_killable(&client->ddev->lock))
3841d609992SMaximilian Luz 		return -ERESTARTSYS;
3851d609992SMaximilian Luz 
3861d609992SMaximilian Luz 	if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) {
3871d609992SMaximilian Luz 		up_read(&client->ddev->lock);
3881d609992SMaximilian Luz 		return -ENODEV;
3891d609992SMaximilian Luz 	}
3901d609992SMaximilian Luz 
3911d609992SMaximilian Luz 	status = __surface_dtx_ioctl(client, cmd, arg);
3921d609992SMaximilian Luz 
3931d609992SMaximilian Luz 	up_read(&client->ddev->lock);
3941d609992SMaximilian Luz 	return status;
3951d609992SMaximilian Luz }
3961d609992SMaximilian Luz 
3971d609992SMaximilian Luz 
3981d609992SMaximilian Luz /* -- File operations. ------------------------------------------------------ */
3991d609992SMaximilian Luz 
surface_dtx_open(struct inode * inode,struct file * file)4001d609992SMaximilian Luz static int surface_dtx_open(struct inode *inode, struct file *file)
4011d609992SMaximilian Luz {
4021d609992SMaximilian Luz 	struct sdtx_device *ddev = container_of(file->private_data, struct sdtx_device, mdev);
4031d609992SMaximilian Luz 	struct sdtx_client *client;
4041d609992SMaximilian Luz 
4051d609992SMaximilian Luz 	/* Initialize client. */
4061d609992SMaximilian Luz 	client = kzalloc(sizeof(*client), GFP_KERNEL);
4071d609992SMaximilian Luz 	if (!client)
4081d609992SMaximilian Luz 		return -ENOMEM;
4091d609992SMaximilian Luz 
4101d609992SMaximilian Luz 	client->ddev = sdtx_device_get(ddev);
4111d609992SMaximilian Luz 
4121d609992SMaximilian Luz 	INIT_LIST_HEAD(&client->node);
4131d609992SMaximilian Luz 
4141d609992SMaximilian Luz 	mutex_init(&client->read_lock);
4151d609992SMaximilian Luz 	INIT_KFIFO(client->buffer);
4161d609992SMaximilian Luz 
4171d609992SMaximilian Luz 	file->private_data = client;
4181d609992SMaximilian Luz 
4191d609992SMaximilian Luz 	/* Attach client. */
4201d609992SMaximilian Luz 	down_write(&ddev->client_lock);
4211d609992SMaximilian Luz 
4221d609992SMaximilian Luz 	/*
4231d609992SMaximilian Luz 	 * Do not add a new client if the device has been shut down. Note that
4241d609992SMaximilian Luz 	 * it's enough to hold the client_lock here as, during shutdown, we
4251d609992SMaximilian Luz 	 * only acquire that lock and remove clients after marking the device
4261d609992SMaximilian Luz 	 * as shut down.
4271d609992SMaximilian Luz 	 */
4281d609992SMaximilian Luz 	if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
4291d609992SMaximilian Luz 		up_write(&ddev->client_lock);
4306325ce15SMaximilian Luz 		mutex_destroy(&client->read_lock);
4311d609992SMaximilian Luz 		sdtx_device_put(client->ddev);
4321d609992SMaximilian Luz 		kfree(client);
4331d609992SMaximilian Luz 		return -ENODEV;
4341d609992SMaximilian Luz 	}
4351d609992SMaximilian Luz 
4361d609992SMaximilian Luz 	list_add_tail(&client->node, &ddev->client_list);
4371d609992SMaximilian Luz 	up_write(&ddev->client_lock);
4381d609992SMaximilian Luz 
4391d609992SMaximilian Luz 	stream_open(inode, file);
4401d609992SMaximilian Luz 	return 0;
4411d609992SMaximilian Luz }
4421d609992SMaximilian Luz 
surface_dtx_release(struct inode * inode,struct file * file)4431d609992SMaximilian Luz static int surface_dtx_release(struct inode *inode, struct file *file)
4441d609992SMaximilian Luz {
4451d609992SMaximilian Luz 	struct sdtx_client *client = file->private_data;
4461d609992SMaximilian Luz 
4471d609992SMaximilian Luz 	/* Detach client. */
4481d609992SMaximilian Luz 	down_write(&client->ddev->client_lock);
4491d609992SMaximilian Luz 	list_del(&client->node);
4501d609992SMaximilian Luz 	up_write(&client->ddev->client_lock);
4511d609992SMaximilian Luz 
4521d609992SMaximilian Luz 	/* Free client. */
4531d609992SMaximilian Luz 	sdtx_device_put(client->ddev);
4541d609992SMaximilian Luz 	mutex_destroy(&client->read_lock);
4551d609992SMaximilian Luz 	kfree(client);
4561d609992SMaximilian Luz 
4571d609992SMaximilian Luz 	return 0;
4581d609992SMaximilian Luz }
4591d609992SMaximilian Luz 
surface_dtx_read(struct file * file,char __user * buf,size_t count,loff_t * offs)4601d609992SMaximilian Luz static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs)
4611d609992SMaximilian Luz {
4621d609992SMaximilian Luz 	struct sdtx_client *client = file->private_data;
4631d609992SMaximilian Luz 	struct sdtx_device *ddev = client->ddev;
4641d609992SMaximilian Luz 	unsigned int copied;
4651d609992SMaximilian Luz 	int status = 0;
4661d609992SMaximilian Luz 
4671d609992SMaximilian Luz 	if (down_read_killable(&ddev->lock))
4681d609992SMaximilian Luz 		return -ERESTARTSYS;
4691d609992SMaximilian Luz 
4701d609992SMaximilian Luz 	/* Make sure we're not shut down. */
4711d609992SMaximilian Luz 	if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
4721d609992SMaximilian Luz 		up_read(&ddev->lock);
4731d609992SMaximilian Luz 		return -ENODEV;
4741d609992SMaximilian Luz 	}
4751d609992SMaximilian Luz 
4761d609992SMaximilian Luz 	do {
4771d609992SMaximilian Luz 		/* Check availability, wait if necessary. */
4781d609992SMaximilian Luz 		if (kfifo_is_empty(&client->buffer)) {
4791d609992SMaximilian Luz 			up_read(&ddev->lock);
4801d609992SMaximilian Luz 
4811d609992SMaximilian Luz 			if (file->f_flags & O_NONBLOCK)
4821d609992SMaximilian Luz 				return -EAGAIN;
4831d609992SMaximilian Luz 
4841d609992SMaximilian Luz 			status = wait_event_interruptible(ddev->waitq,
4851d609992SMaximilian Luz 							  !kfifo_is_empty(&client->buffer) ||
4861d609992SMaximilian Luz 							  test_bit(SDTX_DEVICE_SHUTDOWN_BIT,
4871d609992SMaximilian Luz 								   &ddev->flags));
4881d609992SMaximilian Luz 			if (status < 0)
4891d609992SMaximilian Luz 				return status;
4901d609992SMaximilian Luz 
4914d7ddd8dSDan Carpenter 			if (down_read_killable(&ddev->lock))
4921d609992SMaximilian Luz 				return -ERESTARTSYS;
4931d609992SMaximilian Luz 
4941d609992SMaximilian Luz 			/* Need to check that we're not shut down again. */
4951d609992SMaximilian Luz 			if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
4961d609992SMaximilian Luz 				up_read(&ddev->lock);
4971d609992SMaximilian Luz 				return -ENODEV;
4981d609992SMaximilian Luz 			}
4991d609992SMaximilian Luz 		}
5001d609992SMaximilian Luz 
5011d609992SMaximilian Luz 		/* Try to read from FIFO. */
5021d609992SMaximilian Luz 		if (mutex_lock_interruptible(&client->read_lock)) {
5031d609992SMaximilian Luz 			up_read(&ddev->lock);
5041d609992SMaximilian Luz 			return -ERESTARTSYS;
5051d609992SMaximilian Luz 		}
5061d609992SMaximilian Luz 
5071d609992SMaximilian Luz 		status = kfifo_to_user(&client->buffer, buf, count, &copied);
5081d609992SMaximilian Luz 		mutex_unlock(&client->read_lock);
5091d609992SMaximilian Luz 
5101d609992SMaximilian Luz 		if (status < 0) {
5111d609992SMaximilian Luz 			up_read(&ddev->lock);
5121d609992SMaximilian Luz 			return status;
5131d609992SMaximilian Luz 		}
5141d609992SMaximilian Luz 
5151d609992SMaximilian Luz 		/* We might not have gotten anything, check this here. */
5161d609992SMaximilian Luz 		if (copied == 0 && (file->f_flags & O_NONBLOCK)) {
5171d609992SMaximilian Luz 			up_read(&ddev->lock);
5181d609992SMaximilian Luz 			return -EAGAIN;
5191d609992SMaximilian Luz 		}
5201d609992SMaximilian Luz 	} while (copied == 0);
5211d609992SMaximilian Luz 
5221d609992SMaximilian Luz 	up_read(&ddev->lock);
5231d609992SMaximilian Luz 	return copied;
5241d609992SMaximilian Luz }
5251d609992SMaximilian Luz 
surface_dtx_poll(struct file * file,struct poll_table_struct * pt)5261d609992SMaximilian Luz static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt)
5271d609992SMaximilian Luz {
5281d609992SMaximilian Luz 	struct sdtx_client *client = file->private_data;
5291d609992SMaximilian Luz 	__poll_t events = 0;
5301d609992SMaximilian Luz 
5319795d823SMaximilian Luz 	if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags))
5321d609992SMaximilian Luz 		return EPOLLHUP | EPOLLERR;
5331d609992SMaximilian Luz 
5341d609992SMaximilian Luz 	poll_wait(file, &client->ddev->waitq, pt);
5351d609992SMaximilian Luz 
5361d609992SMaximilian Luz 	if (!kfifo_is_empty(&client->buffer))
5371d609992SMaximilian Luz 		events |= EPOLLIN | EPOLLRDNORM;
5381d609992SMaximilian Luz 
5391d609992SMaximilian Luz 	return events;
5401d609992SMaximilian Luz }
5411d609992SMaximilian Luz 
surface_dtx_fasync(int fd,struct file * file,int on)5421d609992SMaximilian Luz static int surface_dtx_fasync(int fd, struct file *file, int on)
5431d609992SMaximilian Luz {
5441d609992SMaximilian Luz 	struct sdtx_client *client = file->private_data;
5451d609992SMaximilian Luz 
5461d609992SMaximilian Luz 	return fasync_helper(fd, file, on, &client->fasync);
5471d609992SMaximilian Luz }
5481d609992SMaximilian Luz 
5491d609992SMaximilian Luz static const struct file_operations surface_dtx_fops = {
5501d609992SMaximilian Luz 	.owner          = THIS_MODULE,
5511d609992SMaximilian Luz 	.open           = surface_dtx_open,
5521d609992SMaximilian Luz 	.release        = surface_dtx_release,
5531d609992SMaximilian Luz 	.read           = surface_dtx_read,
5541d609992SMaximilian Luz 	.poll           = surface_dtx_poll,
5551d609992SMaximilian Luz 	.fasync         = surface_dtx_fasync,
5561d609992SMaximilian Luz 	.unlocked_ioctl = surface_dtx_ioctl,
5571d609992SMaximilian Luz 	.compat_ioctl   = surface_dtx_ioctl,
5581d609992SMaximilian Luz };
5591d609992SMaximilian Luz 
5601d609992SMaximilian Luz 
5611d609992SMaximilian Luz /* -- Event handling/forwarding. -------------------------------------------- */
5621d609992SMaximilian Luz 
5631d609992SMaximilian Luz /*
5641d609992SMaximilian Luz  * The device operation mode is not immediately updated on the EC when the
5651d609992SMaximilian Luz  * base has been connected, i.e. querying the device mode inside the
5661d609992SMaximilian Luz  * connection event callback yields an outdated value. Thus, we can only
5671d609992SMaximilian Luz  * determine the new tablet-mode switch and device mode values after some
5681d609992SMaximilian Luz  * time.
5691d609992SMaximilian Luz  *
5701d609992SMaximilian Luz  * These delays have been chosen by experimenting. We first delay on connect
5711d609992SMaximilian Luz  * events, then check and validate the device mode against the base state and
5721d609992SMaximilian Luz  * if invalid delay again by the "recheck" delay.
5731d609992SMaximilian Luz  */
5741d609992SMaximilian Luz #define SDTX_DEVICE_MODE_DELAY_CONNECT	msecs_to_jiffies(100)
5751d609992SMaximilian Luz #define SDTX_DEVICE_MODE_DELAY_RECHECK	msecs_to_jiffies(100)
5761d609992SMaximilian Luz 
5771d609992SMaximilian Luz struct sdtx_status_event {
5781d609992SMaximilian Luz 	struct sdtx_event e;
5791d609992SMaximilian Luz 	__u16 v;
5801d609992SMaximilian Luz } __packed;
5811d609992SMaximilian Luz 
5821d609992SMaximilian Luz struct sdtx_base_info_event {
5831d609992SMaximilian Luz 	struct sdtx_event e;
5841d609992SMaximilian Luz 	struct sdtx_base_info v;
5851d609992SMaximilian Luz } __packed;
5861d609992SMaximilian Luz 
5871d609992SMaximilian Luz union sdtx_generic_event {
5881d609992SMaximilian Luz 	struct sdtx_event common;
5891d609992SMaximilian Luz 	struct sdtx_status_event status;
5901d609992SMaximilian Luz 	struct sdtx_base_info_event base;
5911d609992SMaximilian Luz };
5921d609992SMaximilian Luz 
5931d609992SMaximilian Luz static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay);
5941d609992SMaximilian Luz 
5951d609992SMaximilian Luz /* Must be executed with ddev->write_lock held. */
sdtx_push_event(struct sdtx_device * ddev,struct sdtx_event * evt)5961d609992SMaximilian Luz static void sdtx_push_event(struct sdtx_device *ddev, struct sdtx_event *evt)
5971d609992SMaximilian Luz {
5981d609992SMaximilian Luz 	const size_t len = sizeof(struct sdtx_event) + evt->length;
5991d609992SMaximilian Luz 	struct sdtx_client *client;
6001d609992SMaximilian Luz 
6011d609992SMaximilian Luz 	lockdep_assert_held(&ddev->write_lock);
6021d609992SMaximilian Luz 
6031d609992SMaximilian Luz 	down_read(&ddev->client_lock);
6041d609992SMaximilian Luz 	list_for_each_entry(client, &ddev->client_list, node) {
6051d609992SMaximilian Luz 		if (!test_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags))
6061d609992SMaximilian Luz 			continue;
6071d609992SMaximilian Luz 
6081d609992SMaximilian Luz 		if (likely(kfifo_avail(&client->buffer) >= len))
6091d609992SMaximilian Luz 			kfifo_in(&client->buffer, (const u8 *)evt, len);
6101d609992SMaximilian Luz 		else
6111d609992SMaximilian Luz 			dev_warn(ddev->dev, "event buffer overrun\n");
6121d609992SMaximilian Luz 
6131d609992SMaximilian Luz 		kill_fasync(&client->fasync, SIGIO, POLL_IN);
6141d609992SMaximilian Luz 	}
6151d609992SMaximilian Luz 	up_read(&ddev->client_lock);
6161d609992SMaximilian Luz 
6171d609992SMaximilian Luz 	wake_up_interruptible(&ddev->waitq);
6181d609992SMaximilian Luz }
6191d609992SMaximilian Luz 
sdtx_notifier(struct ssam_event_notifier * nf,const struct ssam_event * in)6201d609992SMaximilian Luz static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in)
6211d609992SMaximilian Luz {
6221d609992SMaximilian Luz 	struct sdtx_device *ddev = container_of(nf, struct sdtx_device, notif);
6231d609992SMaximilian Luz 	union sdtx_generic_event event;
6241d609992SMaximilian Luz 	size_t len;
6251d609992SMaximilian Luz 
6261d609992SMaximilian Luz 	/* Validate event payload length. */
6271d609992SMaximilian Luz 	switch (in->command_id) {
6281d609992SMaximilian Luz 	case SAM_EVENT_CID_DTX_CONNECTION:
6291d609992SMaximilian Luz 		len = 2 * sizeof(u8);
6301d609992SMaximilian Luz 		break;
6311d609992SMaximilian Luz 
6321d609992SMaximilian Luz 	case SAM_EVENT_CID_DTX_REQUEST:
6331d609992SMaximilian Luz 		len = 0;
6341d609992SMaximilian Luz 		break;
6351d609992SMaximilian Luz 
6361d609992SMaximilian Luz 	case SAM_EVENT_CID_DTX_CANCEL:
6371d609992SMaximilian Luz 		len = sizeof(u8);
6381d609992SMaximilian Luz 		break;
6391d609992SMaximilian Luz 
6401d609992SMaximilian Luz 	case SAM_EVENT_CID_DTX_LATCH_STATUS:
6411d609992SMaximilian Luz 		len = sizeof(u8);
6421d609992SMaximilian Luz 		break;
6431d609992SMaximilian Luz 
6441d609992SMaximilian Luz 	default:
6451d609992SMaximilian Luz 		return 0;
646e4899ff6Skernel test robot 	}
6471d609992SMaximilian Luz 
6481d609992SMaximilian Luz 	if (in->length != len) {
6491d609992SMaximilian Luz 		dev_err(ddev->dev,
6501d609992SMaximilian Luz 			"unexpected payload size for event %#04x: got %u, expected %zu\n",
6511d609992SMaximilian Luz 			in->command_id, in->length, len);
6521d609992SMaximilian Luz 		return 0;
6531d609992SMaximilian Luz 	}
6541d609992SMaximilian Luz 
6551d609992SMaximilian Luz 	mutex_lock(&ddev->write_lock);
6561d609992SMaximilian Luz 
6571d609992SMaximilian Luz 	/* Translate event. */
6581d609992SMaximilian Luz 	switch (in->command_id) {
6591d609992SMaximilian Luz 	case SAM_EVENT_CID_DTX_CONNECTION:
6601d609992SMaximilian Luz 		clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags);
6611d609992SMaximilian Luz 
6621d609992SMaximilian Luz 		/* If state has not changed: do not send new event. */
6631d609992SMaximilian Luz 		if (ddev->state.base.state == in->data[0] &&
6641d609992SMaximilian Luz 		    ddev->state.base.base_id == in->data[1])
6651d609992SMaximilian Luz 			goto out;
6661d609992SMaximilian Luz 
6671d609992SMaximilian Luz 		ddev->state.base.state = in->data[0];
6681d609992SMaximilian Luz 		ddev->state.base.base_id = in->data[1];
6691d609992SMaximilian Luz 
6701d609992SMaximilian Luz 		event.base.e.length = sizeof(struct sdtx_base_info);
6711d609992SMaximilian Luz 		event.base.e.code = SDTX_EVENT_BASE_CONNECTION;
6721d609992SMaximilian Luz 		event.base.v.state = sdtx_translate_base_state(ddev, in->data[0]);
6731d609992SMaximilian Luz 		event.base.v.base_id = SDTX_BASE_TYPE_SSH(in->data[1]);
6741d609992SMaximilian Luz 		break;
6751d609992SMaximilian Luz 
6761d609992SMaximilian Luz 	case SAM_EVENT_CID_DTX_REQUEST:
6771d609992SMaximilian Luz 		event.common.code = SDTX_EVENT_REQUEST;
6781d609992SMaximilian Luz 		event.common.length = 0;
6791d609992SMaximilian Luz 		break;
6801d609992SMaximilian Luz 
6811d609992SMaximilian Luz 	case SAM_EVENT_CID_DTX_CANCEL:
6821d609992SMaximilian Luz 		event.status.e.length = sizeof(u16);
6831d609992SMaximilian Luz 		event.status.e.code = SDTX_EVENT_CANCEL;
6841d609992SMaximilian Luz 		event.status.v = sdtx_translate_cancel_reason(ddev, in->data[0]);
6851d609992SMaximilian Luz 		break;
6861d609992SMaximilian Luz 
6871d609992SMaximilian Luz 	case SAM_EVENT_CID_DTX_LATCH_STATUS:
6881d609992SMaximilian Luz 		clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags);
6891d609992SMaximilian Luz 
6901d609992SMaximilian Luz 		/* If state has not changed: do not send new event. */
6911d609992SMaximilian Luz 		if (ddev->state.latch_status == in->data[0])
6921d609992SMaximilian Luz 			goto out;
6931d609992SMaximilian Luz 
6941d609992SMaximilian Luz 		ddev->state.latch_status = in->data[0];
6951d609992SMaximilian Luz 
6961d609992SMaximilian Luz 		event.status.e.length = sizeof(u16);
6971d609992SMaximilian Luz 		event.status.e.code = SDTX_EVENT_LATCH_STATUS;
6981d609992SMaximilian Luz 		event.status.v = sdtx_translate_latch_status(ddev, in->data[0]);
6991d609992SMaximilian Luz 		break;
7001d609992SMaximilian Luz 	}
7011d609992SMaximilian Luz 
7021d609992SMaximilian Luz 	sdtx_push_event(ddev, &event.common);
7031d609992SMaximilian Luz 
7041d609992SMaximilian Luz 	/* Update device mode on base connection change. */
7051d609992SMaximilian Luz 	if (in->command_id == SAM_EVENT_CID_DTX_CONNECTION) {
7061d609992SMaximilian Luz 		unsigned long delay;
7071d609992SMaximilian Luz 
7081d609992SMaximilian Luz 		delay = in->data[0] ? SDTX_DEVICE_MODE_DELAY_CONNECT : 0;
7091d609992SMaximilian Luz 		sdtx_update_device_mode(ddev, delay);
7101d609992SMaximilian Luz 	}
7111d609992SMaximilian Luz 
7121d609992SMaximilian Luz out:
7131d609992SMaximilian Luz 	mutex_unlock(&ddev->write_lock);
7141d609992SMaximilian Luz 	return SSAM_NOTIF_HANDLED;
7151d609992SMaximilian Luz }
7161d609992SMaximilian Luz 
7171d609992SMaximilian Luz 
7181d609992SMaximilian Luz /* -- State update functions. ----------------------------------------------- */
7191d609992SMaximilian Luz 
sdtx_device_mode_invalid(u8 mode,u8 base_state)7201d609992SMaximilian Luz static bool sdtx_device_mode_invalid(u8 mode, u8 base_state)
7211d609992SMaximilian Luz {
7221d609992SMaximilian Luz 	return ((base_state == SSAM_BAS_BASE_STATE_ATTACHED) &&
7231d609992SMaximilian Luz 		(mode == SDTX_DEVICE_MODE_TABLET)) ||
7241d609992SMaximilian Luz 	       ((base_state == SSAM_BAS_BASE_STATE_DETACH_SUCCESS) &&
7251d609992SMaximilian Luz 		(mode != SDTX_DEVICE_MODE_TABLET));
7261d609992SMaximilian Luz }
7271d609992SMaximilian Luz 
sdtx_device_mode_workfn(struct work_struct * work)7281d609992SMaximilian Luz static void sdtx_device_mode_workfn(struct work_struct *work)
7291d609992SMaximilian Luz {
7301d609992SMaximilian Luz 	struct sdtx_device *ddev = container_of(work, struct sdtx_device, mode_work.work);
7311d609992SMaximilian Luz 	struct sdtx_status_event event;
7321d609992SMaximilian Luz 	struct ssam_bas_base_info base;
7331d609992SMaximilian Luz 	int status, tablet;
7341d609992SMaximilian Luz 	u8 mode;
7351d609992SMaximilian Luz 
7361d609992SMaximilian Luz 	/* Get operation mode. */
7371d609992SMaximilian Luz 	status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
7381d609992SMaximilian Luz 	if (status) {
7391d609992SMaximilian Luz 		dev_err(ddev->dev, "failed to get device mode: %d\n", status);
7401d609992SMaximilian Luz 		return;
7411d609992SMaximilian Luz 	}
7421d609992SMaximilian Luz 
7431d609992SMaximilian Luz 	/* Get base info. */
7441d609992SMaximilian Luz 	status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base);
7451d609992SMaximilian Luz 	if (status) {
7461d609992SMaximilian Luz 		dev_err(ddev->dev, "failed to get base info: %d\n", status);
7471d609992SMaximilian Luz 		return;
7481d609992SMaximilian Luz 	}
7491d609992SMaximilian Luz 
7501d609992SMaximilian Luz 	/*
7511d609992SMaximilian Luz 	 * In some cases (specifically when attaching the base), the device
7521d609992SMaximilian Luz 	 * mode isn't updated right away. Thus we check if the device mode
7531d609992SMaximilian Luz 	 * makes sense for the given base state and try again later if it
7541d609992SMaximilian Luz 	 * doesn't.
7551d609992SMaximilian Luz 	 */
7561d609992SMaximilian Luz 	if (sdtx_device_mode_invalid(mode, base.state)) {
7571d609992SMaximilian Luz 		dev_dbg(ddev->dev, "device mode is invalid, trying again\n");
7581d609992SMaximilian Luz 		sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK);
7591d609992SMaximilian Luz 		return;
7601d609992SMaximilian Luz 	}
7611d609992SMaximilian Luz 
7621d609992SMaximilian Luz 	mutex_lock(&ddev->write_lock);
7631d609992SMaximilian Luz 	clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags);
7641d609992SMaximilian Luz 
7651d609992SMaximilian Luz 	/* Avoid sending duplicate device-mode events. */
7661d609992SMaximilian Luz 	if (ddev->state.device_mode == mode) {
7671d609992SMaximilian Luz 		mutex_unlock(&ddev->write_lock);
7681d609992SMaximilian Luz 		return;
7691d609992SMaximilian Luz 	}
7701d609992SMaximilian Luz 
7711d609992SMaximilian Luz 	ddev->state.device_mode = mode;
7721d609992SMaximilian Luz 
7731d609992SMaximilian Luz 	event.e.length = sizeof(u16);
7741d609992SMaximilian Luz 	event.e.code = SDTX_EVENT_DEVICE_MODE;
7751d609992SMaximilian Luz 	event.v = mode;
7761d609992SMaximilian Luz 
7771d609992SMaximilian Luz 	sdtx_push_event(ddev, &event.e);
7781d609992SMaximilian Luz 
7791d609992SMaximilian Luz 	/* Send SW_TABLET_MODE event. */
7801d609992SMaximilian Luz 	tablet = mode != SDTX_DEVICE_MODE_LAPTOP;
7811d609992SMaximilian Luz 	input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet);
7821d609992SMaximilian Luz 	input_sync(ddev->mode_switch);
7831d609992SMaximilian Luz 
7841d609992SMaximilian Luz 	mutex_unlock(&ddev->write_lock);
7851d609992SMaximilian Luz }
7861d609992SMaximilian Luz 
sdtx_update_device_mode(struct sdtx_device * ddev,unsigned long delay)7871d609992SMaximilian Luz static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay)
7881d609992SMaximilian Luz {
7891d609992SMaximilian Luz 	schedule_delayed_work(&ddev->mode_work, delay);
7901d609992SMaximilian Luz }
7911d609992SMaximilian Luz 
7921d609992SMaximilian Luz /* Must be executed with ddev->write_lock held. */
__sdtx_device_state_update_base(struct sdtx_device * ddev,struct ssam_bas_base_info info)7931d609992SMaximilian Luz static void __sdtx_device_state_update_base(struct sdtx_device *ddev,
7941d609992SMaximilian Luz 					    struct ssam_bas_base_info info)
7951d609992SMaximilian Luz {
7961d609992SMaximilian Luz 	struct sdtx_base_info_event event;
7971d609992SMaximilian Luz 
7981d609992SMaximilian Luz 	lockdep_assert_held(&ddev->write_lock);
7991d609992SMaximilian Luz 
8001d609992SMaximilian Luz 	/* Prevent duplicate events. */
8011d609992SMaximilian Luz 	if (ddev->state.base.state == info.state &&
8021d609992SMaximilian Luz 	    ddev->state.base.base_id == info.base_id)
8031d609992SMaximilian Luz 		return;
8041d609992SMaximilian Luz 
8051d609992SMaximilian Luz 	ddev->state.base = info;
8061d609992SMaximilian Luz 
8071d609992SMaximilian Luz 	event.e.length = sizeof(struct sdtx_base_info);
8081d609992SMaximilian Luz 	event.e.code = SDTX_EVENT_BASE_CONNECTION;
8091d609992SMaximilian Luz 	event.v.state = sdtx_translate_base_state(ddev, info.state);
8101d609992SMaximilian Luz 	event.v.base_id = SDTX_BASE_TYPE_SSH(info.base_id);
8111d609992SMaximilian Luz 
8121d609992SMaximilian Luz 	sdtx_push_event(ddev, &event.e);
8131d609992SMaximilian Luz }
8141d609992SMaximilian Luz 
8151d609992SMaximilian Luz /* Must be executed with ddev->write_lock held. */
__sdtx_device_state_update_mode(struct sdtx_device * ddev,u8 mode)8161d609992SMaximilian Luz static void __sdtx_device_state_update_mode(struct sdtx_device *ddev, u8 mode)
8171d609992SMaximilian Luz {
8181d609992SMaximilian Luz 	struct sdtx_status_event event;
8191d609992SMaximilian Luz 	int tablet;
8201d609992SMaximilian Luz 
8211d609992SMaximilian Luz 	/*
8221d609992SMaximilian Luz 	 * Note: This function must be called after updating the base state
8231d609992SMaximilian Luz 	 * via __sdtx_device_state_update_base(), as we rely on the updated
8241d609992SMaximilian Luz 	 * base state value in the validity check below.
8251d609992SMaximilian Luz 	 */
8261d609992SMaximilian Luz 
8271d609992SMaximilian Luz 	lockdep_assert_held(&ddev->write_lock);
8281d609992SMaximilian Luz 
8291d609992SMaximilian Luz 	if (sdtx_device_mode_invalid(mode, ddev->state.base.state)) {
8301d609992SMaximilian Luz 		dev_dbg(ddev->dev, "device mode is invalid, trying again\n");
8311d609992SMaximilian Luz 		sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK);
8321d609992SMaximilian Luz 		return;
8331d609992SMaximilian Luz 	}
8341d609992SMaximilian Luz 
8351d609992SMaximilian Luz 	/* Prevent duplicate events. */
8361d609992SMaximilian Luz 	if (ddev->state.device_mode == mode)
8371d609992SMaximilian Luz 		return;
8381d609992SMaximilian Luz 
8391d609992SMaximilian Luz 	ddev->state.device_mode = mode;
8401d609992SMaximilian Luz 
8411d609992SMaximilian Luz 	/* Send event. */
8421d609992SMaximilian Luz 	event.e.length = sizeof(u16);
8431d609992SMaximilian Luz 	event.e.code = SDTX_EVENT_DEVICE_MODE;
8441d609992SMaximilian Luz 	event.v = mode;
8451d609992SMaximilian Luz 
8461d609992SMaximilian Luz 	sdtx_push_event(ddev, &event.e);
8471d609992SMaximilian Luz 
8481d609992SMaximilian Luz 	/* Send SW_TABLET_MODE event. */
8491d609992SMaximilian Luz 	tablet = mode != SDTX_DEVICE_MODE_LAPTOP;
8501d609992SMaximilian Luz 	input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet);
8511d609992SMaximilian Luz 	input_sync(ddev->mode_switch);
8521d609992SMaximilian Luz }
8531d609992SMaximilian Luz 
8541d609992SMaximilian Luz /* Must be executed with ddev->write_lock held. */
__sdtx_device_state_update_latch(struct sdtx_device * ddev,u8 status)8551d609992SMaximilian Luz static void __sdtx_device_state_update_latch(struct sdtx_device *ddev, u8 status)
8561d609992SMaximilian Luz {
8571d609992SMaximilian Luz 	struct sdtx_status_event event;
8581d609992SMaximilian Luz 
8591d609992SMaximilian Luz 	lockdep_assert_held(&ddev->write_lock);
8601d609992SMaximilian Luz 
8611d609992SMaximilian Luz 	/* Prevent duplicate events. */
8621d609992SMaximilian Luz 	if (ddev->state.latch_status == status)
8631d609992SMaximilian Luz 		return;
8641d609992SMaximilian Luz 
8651d609992SMaximilian Luz 	ddev->state.latch_status = status;
8661d609992SMaximilian Luz 
8671d609992SMaximilian Luz 	event.e.length = sizeof(struct sdtx_base_info);
8681d609992SMaximilian Luz 	event.e.code = SDTX_EVENT_BASE_CONNECTION;
8691d609992SMaximilian Luz 	event.v = sdtx_translate_latch_status(ddev, status);
8701d609992SMaximilian Luz 
8711d609992SMaximilian Luz 	sdtx_push_event(ddev, &event.e);
8721d609992SMaximilian Luz }
8731d609992SMaximilian Luz 
sdtx_device_state_workfn(struct work_struct * work)8741d609992SMaximilian Luz static void sdtx_device_state_workfn(struct work_struct *work)
8751d609992SMaximilian Luz {
8761d609992SMaximilian Luz 	struct sdtx_device *ddev = container_of(work, struct sdtx_device, state_work.work);
8771d609992SMaximilian Luz 	struct ssam_bas_base_info base;
8781d609992SMaximilian Luz 	u8 mode, latch;
8791d609992SMaximilian Luz 	int status;
8801d609992SMaximilian Luz 
8811d609992SMaximilian Luz 	/* Mark everything as dirty. */
8821d609992SMaximilian Luz 	set_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags);
8831d609992SMaximilian Luz 	set_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags);
8841d609992SMaximilian Luz 	set_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags);
8851d609992SMaximilian Luz 
8861d609992SMaximilian Luz 	/*
8871d609992SMaximilian Luz 	 * Ensure that the state gets marked as dirty before continuing to
8881d609992SMaximilian Luz 	 * query it. Necessary to ensure that clear_bit() calls in
8891d609992SMaximilian Luz 	 * sdtx_notifier() and sdtx_device_mode_workfn() actually clear these
8901d609992SMaximilian Luz 	 * bits if an event is received while updating the state here.
8911d609992SMaximilian Luz 	 */
8921d609992SMaximilian Luz 	smp_mb__after_atomic();
8931d609992SMaximilian Luz 
8941d609992SMaximilian Luz 	status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base);
8951d609992SMaximilian Luz 	if (status) {
8961d609992SMaximilian Luz 		dev_err(ddev->dev, "failed to get base state: %d\n", status);
8971d609992SMaximilian Luz 		return;
8981d609992SMaximilian Luz 	}
8991d609992SMaximilian Luz 
9001d609992SMaximilian Luz 	status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
9011d609992SMaximilian Luz 	if (status) {
9021d609992SMaximilian Luz 		dev_err(ddev->dev, "failed to get device mode: %d\n", status);
9031d609992SMaximilian Luz 		return;
9041d609992SMaximilian Luz 	}
9051d609992SMaximilian Luz 
9061d609992SMaximilian Luz 	status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch);
9071d609992SMaximilian Luz 	if (status) {
9081d609992SMaximilian Luz 		dev_err(ddev->dev, "failed to get latch status: %d\n", status);
9091d609992SMaximilian Luz 		return;
9101d609992SMaximilian Luz 	}
9111d609992SMaximilian Luz 
9121d609992SMaximilian Luz 	mutex_lock(&ddev->write_lock);
9131d609992SMaximilian Luz 
9141d609992SMaximilian Luz 	/*
9151d609992SMaximilian Luz 	 * If the respective dirty-bit has been cleared, an event has been
9161d609992SMaximilian Luz 	 * received, updating this state. The queried state may thus be out of
9171d609992SMaximilian Luz 	 * date. At this point, we can safely assume that the state provided
9181d609992SMaximilian Luz 	 * by the event is either up to date, or we're about to receive
9191d609992SMaximilian Luz 	 * another event updating it.
9201d609992SMaximilian Luz 	 */
9211d609992SMaximilian Luz 
9221d609992SMaximilian Luz 	if (test_and_clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags))
9231d609992SMaximilian Luz 		__sdtx_device_state_update_base(ddev, base);
9241d609992SMaximilian Luz 
9251d609992SMaximilian Luz 	if (test_and_clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags))
9261d609992SMaximilian Luz 		__sdtx_device_state_update_mode(ddev, mode);
9271d609992SMaximilian Luz 
9281d609992SMaximilian Luz 	if (test_and_clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags))
9291d609992SMaximilian Luz 		__sdtx_device_state_update_latch(ddev, latch);
9301d609992SMaximilian Luz 
9311d609992SMaximilian Luz 	mutex_unlock(&ddev->write_lock);
9321d609992SMaximilian Luz }
9331d609992SMaximilian Luz 
sdtx_update_device_state(struct sdtx_device * ddev,unsigned long delay)9341d609992SMaximilian Luz static void sdtx_update_device_state(struct sdtx_device *ddev, unsigned long delay)
9351d609992SMaximilian Luz {
9361d609992SMaximilian Luz 	schedule_delayed_work(&ddev->state_work, delay);
9371d609992SMaximilian Luz }
9381d609992SMaximilian Luz 
9391d609992SMaximilian Luz 
9401d609992SMaximilian Luz /* -- Common device initialization. ----------------------------------------- */
9411d609992SMaximilian Luz 
sdtx_device_init(struct sdtx_device * ddev,struct device * dev,struct ssam_controller * ctrl)9421d609992SMaximilian Luz static int sdtx_device_init(struct sdtx_device *ddev, struct device *dev,
9431d609992SMaximilian Luz 			    struct ssam_controller *ctrl)
9441d609992SMaximilian Luz {
9451d609992SMaximilian Luz 	int status, tablet_mode;
9461d609992SMaximilian Luz 
9471d609992SMaximilian Luz 	/* Basic initialization. */
9481d609992SMaximilian Luz 	kref_init(&ddev->kref);
9491d609992SMaximilian Luz 	init_rwsem(&ddev->lock);
9501d609992SMaximilian Luz 	ddev->dev = dev;
9511d609992SMaximilian Luz 	ddev->ctrl = ctrl;
9521d609992SMaximilian Luz 
9531d609992SMaximilian Luz 	ddev->mdev.minor = MISC_DYNAMIC_MINOR;
9541d609992SMaximilian Luz 	ddev->mdev.name = "surface_dtx";
9551d609992SMaximilian Luz 	ddev->mdev.nodename = "surface/dtx";
9561d609992SMaximilian Luz 	ddev->mdev.fops = &surface_dtx_fops;
9571d609992SMaximilian Luz 
9581d609992SMaximilian Luz 	ddev->notif.base.priority = 1;
9591d609992SMaximilian Luz 	ddev->notif.base.fn = sdtx_notifier;
9601d609992SMaximilian Luz 	ddev->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
9611d609992SMaximilian Luz 	ddev->notif.event.id.target_category = SSAM_SSH_TC_BAS;
9621d609992SMaximilian Luz 	ddev->notif.event.id.instance = 0;
9631d609992SMaximilian Luz 	ddev->notif.event.mask = SSAM_EVENT_MASK_NONE;
9641d609992SMaximilian Luz 	ddev->notif.event.flags = SSAM_EVENT_SEQUENCED;
9651d609992SMaximilian Luz 
9661d609992SMaximilian Luz 	init_waitqueue_head(&ddev->waitq);
9671d609992SMaximilian Luz 	mutex_init(&ddev->write_lock);
9681d609992SMaximilian Luz 	init_rwsem(&ddev->client_lock);
9691d609992SMaximilian Luz 	INIT_LIST_HEAD(&ddev->client_list);
9701d609992SMaximilian Luz 
9711d609992SMaximilian Luz 	INIT_DELAYED_WORK(&ddev->mode_work, sdtx_device_mode_workfn);
9721d609992SMaximilian Luz 	INIT_DELAYED_WORK(&ddev->state_work, sdtx_device_state_workfn);
9731d609992SMaximilian Luz 
9741d609992SMaximilian Luz 	/*
9751d609992SMaximilian Luz 	 * Get current device state. We want to guarantee that events are only
9761d609992SMaximilian Luz 	 * sent when state actually changes. Thus we cannot use special
9771d609992SMaximilian Luz 	 * "uninitialized" values, as that would cause problems when manually
9781d609992SMaximilian Luz 	 * querying the state in surface_dtx_pm_complete(). I.e. we would not
9791d609992SMaximilian Luz 	 * be able to detect state changes there if no change event has been
9801d609992SMaximilian Luz 	 * received between driver initialization and first device suspension.
9811d609992SMaximilian Luz 	 *
9821d609992SMaximilian Luz 	 * Note that we also need to do this before registering the event
9831d609992SMaximilian Luz 	 * notifier, as that may access the state values.
9841d609992SMaximilian Luz 	 */
9851d609992SMaximilian Luz 	status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &ddev->state.base);
9861d609992SMaximilian Luz 	if (status)
9871d609992SMaximilian Luz 		return status;
9881d609992SMaximilian Luz 
9891d609992SMaximilian Luz 	status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &ddev->state.device_mode);
9901d609992SMaximilian Luz 	if (status)
9911d609992SMaximilian Luz 		return status;
9921d609992SMaximilian Luz 
9931d609992SMaximilian Luz 	status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &ddev->state.latch_status);
9941d609992SMaximilian Luz 	if (status)
9951d609992SMaximilian Luz 		return status;
9961d609992SMaximilian Luz 
9971d609992SMaximilian Luz 	/* Set up tablet mode switch. */
9981d609992SMaximilian Luz 	ddev->mode_switch = input_allocate_device();
9991d609992SMaximilian Luz 	if (!ddev->mode_switch)
10001d609992SMaximilian Luz 		return -ENOMEM;
10011d609992SMaximilian Luz 
10021d609992SMaximilian Luz 	ddev->mode_switch->name = "Microsoft Surface DTX Device Mode Switch";
10031d609992SMaximilian Luz 	ddev->mode_switch->phys = "ssam/01:11:01:00:00/input0";
10041d609992SMaximilian Luz 	ddev->mode_switch->id.bustype = BUS_HOST;
10051d609992SMaximilian Luz 	ddev->mode_switch->dev.parent = ddev->dev;
10061d609992SMaximilian Luz 
10071d609992SMaximilian Luz 	tablet_mode = (ddev->state.device_mode != SDTX_DEVICE_MODE_LAPTOP);
10081d609992SMaximilian Luz 	input_set_capability(ddev->mode_switch, EV_SW, SW_TABLET_MODE);
10091d609992SMaximilian Luz 	input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet_mode);
10101d609992SMaximilian Luz 
10111d609992SMaximilian Luz 	status = input_register_device(ddev->mode_switch);
10121d609992SMaximilian Luz 	if (status) {
10131d609992SMaximilian Luz 		input_free_device(ddev->mode_switch);
10141d609992SMaximilian Luz 		return status;
10151d609992SMaximilian Luz 	}
10161d609992SMaximilian Luz 
10171d609992SMaximilian Luz 	/* Set up event notifier. */
10181d609992SMaximilian Luz 	status = ssam_notifier_register(ddev->ctrl, &ddev->notif);
10191d609992SMaximilian Luz 	if (status)
10201d609992SMaximilian Luz 		goto err_notif;
10211d609992SMaximilian Luz 
10221d609992SMaximilian Luz 	/* Register miscdevice. */
10231d609992SMaximilian Luz 	status = misc_register(&ddev->mdev);
10241d609992SMaximilian Luz 	if (status)
10251d609992SMaximilian Luz 		goto err_mdev;
10261d609992SMaximilian Luz 
10271d609992SMaximilian Luz 	/*
10281d609992SMaximilian Luz 	 * Update device state in case it has changed between getting the
10291d609992SMaximilian Luz 	 * initial mode and registering the event notifier.
10301d609992SMaximilian Luz 	 */
10311d609992SMaximilian Luz 	sdtx_update_device_state(ddev, 0);
10321d609992SMaximilian Luz 	return 0;
10331d609992SMaximilian Luz 
10341d609992SMaximilian Luz err_notif:
10351d609992SMaximilian Luz 	ssam_notifier_unregister(ddev->ctrl, &ddev->notif);
10361d609992SMaximilian Luz 	cancel_delayed_work_sync(&ddev->mode_work);
10371d609992SMaximilian Luz err_mdev:
10381d609992SMaximilian Luz 	input_unregister_device(ddev->mode_switch);
10391d609992SMaximilian Luz 	return status;
10401d609992SMaximilian Luz }
10411d609992SMaximilian Luz 
sdtx_device_create(struct device * dev,struct ssam_controller * ctrl)10421d609992SMaximilian Luz static struct sdtx_device *sdtx_device_create(struct device *dev, struct ssam_controller *ctrl)
10431d609992SMaximilian Luz {
10441d609992SMaximilian Luz 	struct sdtx_device *ddev;
10451d609992SMaximilian Luz 	int status;
10461d609992SMaximilian Luz 
10471d609992SMaximilian Luz 	ddev = kzalloc(sizeof(*ddev), GFP_KERNEL);
10481d609992SMaximilian Luz 	if (!ddev)
10491d609992SMaximilian Luz 		return ERR_PTR(-ENOMEM);
10501d609992SMaximilian Luz 
10511d609992SMaximilian Luz 	status = sdtx_device_init(ddev, dev, ctrl);
10521d609992SMaximilian Luz 	if (status) {
10531d609992SMaximilian Luz 		sdtx_device_put(ddev);
10541d609992SMaximilian Luz 		return ERR_PTR(status);
10551d609992SMaximilian Luz 	}
10561d609992SMaximilian Luz 
10571d609992SMaximilian Luz 	return ddev;
10581d609992SMaximilian Luz }
10591d609992SMaximilian Luz 
sdtx_device_destroy(struct sdtx_device * ddev)10601d609992SMaximilian Luz static void sdtx_device_destroy(struct sdtx_device *ddev)
10611d609992SMaximilian Luz {
10621d609992SMaximilian Luz 	struct sdtx_client *client;
10631d609992SMaximilian Luz 
10641d609992SMaximilian Luz 	/*
10651d609992SMaximilian Luz 	 * Mark device as shut-down. Prevent new clients from being added and
10661d609992SMaximilian Luz 	 * new operations from being executed.
10671d609992SMaximilian Luz 	 */
10681d609992SMaximilian Luz 	set_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags);
10691d609992SMaximilian Luz 
10701d609992SMaximilian Luz 	/* Disable notifiers, prevent new events from arriving. */
10711d609992SMaximilian Luz 	ssam_notifier_unregister(ddev->ctrl, &ddev->notif);
10721d609992SMaximilian Luz 
10731d609992SMaximilian Luz 	/* Stop mode_work, prevent access to mode_switch. */
10741d609992SMaximilian Luz 	cancel_delayed_work_sync(&ddev->mode_work);
10751d609992SMaximilian Luz 
10761d609992SMaximilian Luz 	/* Stop state_work. */
10771d609992SMaximilian Luz 	cancel_delayed_work_sync(&ddev->state_work);
10781d609992SMaximilian Luz 
10791d609992SMaximilian Luz 	/* With mode_work canceled, we can unregister the mode_switch. */
10801d609992SMaximilian Luz 	input_unregister_device(ddev->mode_switch);
10811d609992SMaximilian Luz 
10821d609992SMaximilian Luz 	/* Wake up async clients. */
10831d609992SMaximilian Luz 	down_write(&ddev->client_lock);
10841d609992SMaximilian Luz 	list_for_each_entry(client, &ddev->client_list, node) {
10851d609992SMaximilian Luz 		kill_fasync(&client->fasync, SIGIO, POLL_HUP);
10861d609992SMaximilian Luz 	}
10871d609992SMaximilian Luz 	up_write(&ddev->client_lock);
10881d609992SMaximilian Luz 
10891d609992SMaximilian Luz 	/* Wake up blocking clients. */
10901d609992SMaximilian Luz 	wake_up_interruptible(&ddev->waitq);
10911d609992SMaximilian Luz 
10921d609992SMaximilian Luz 	/*
10931d609992SMaximilian Luz 	 * Wait for clients to finish their current operation. After this, the
10941d609992SMaximilian Luz 	 * controller and device references are guaranteed to be no longer in
10951d609992SMaximilian Luz 	 * use.
10961d609992SMaximilian Luz 	 */
10971d609992SMaximilian Luz 	down_write(&ddev->lock);
10981d609992SMaximilian Luz 	ddev->dev = NULL;
10991d609992SMaximilian Luz 	ddev->ctrl = NULL;
11001d609992SMaximilian Luz 	up_write(&ddev->lock);
11011d609992SMaximilian Luz 
11021d609992SMaximilian Luz 	/* Finally remove the misc-device. */
11031d609992SMaximilian Luz 	misc_deregister(&ddev->mdev);
11041d609992SMaximilian Luz 
11051d609992SMaximilian Luz 	/*
11061d609992SMaximilian Luz 	 * We're now guaranteed that sdtx_device_open() won't be called any
11071d609992SMaximilian Luz 	 * more, so we can now drop out reference.
11081d609992SMaximilian Luz 	 */
11091d609992SMaximilian Luz 	sdtx_device_put(ddev);
11101d609992SMaximilian Luz }
11111d609992SMaximilian Luz 
11121d609992SMaximilian Luz 
11131d609992SMaximilian Luz /* -- PM ops. --------------------------------------------------------------- */
11141d609992SMaximilian Luz 
11151d609992SMaximilian Luz #ifdef CONFIG_PM_SLEEP
11161d609992SMaximilian Luz 
surface_dtx_pm_complete(struct device * dev)11171d609992SMaximilian Luz static void surface_dtx_pm_complete(struct device *dev)
11181d609992SMaximilian Luz {
11191d609992SMaximilian Luz 	struct sdtx_device *ddev = dev_get_drvdata(dev);
11201d609992SMaximilian Luz 
11211d609992SMaximilian Luz 	/*
11221d609992SMaximilian Luz 	 * Normally, the EC will store events while suspended (i.e. in
11231d609992SMaximilian Luz 	 * display-off state) and release them when resumed (i.e. transitioned
11241d609992SMaximilian Luz 	 * to display-on state). During hibernation, however, the EC will be
11251d609992SMaximilian Luz 	 * shut down and does not store events. Furthermore, events might be
11261d609992SMaximilian Luz 	 * dropped during prolonged suspension (it is currently unknown how
11271d609992SMaximilian Luz 	 * big this event buffer is and how it behaves on overruns).
11281d609992SMaximilian Luz 	 *
11291d609992SMaximilian Luz 	 * To prevent any problems, we update the device state here. We do
11301d609992SMaximilian Luz 	 * this delayed to ensure that any events sent by the EC directly
11311d609992SMaximilian Luz 	 * after resuming will be handled first. The delay below has been
11321d609992SMaximilian Luz 	 * chosen (experimentally), so that there should be ample time for
11331d609992SMaximilian Luz 	 * these events to be handled, before we check and, if necessary,
11341d609992SMaximilian Luz 	 * update the state.
11351d609992SMaximilian Luz 	 */
11361d609992SMaximilian Luz 	sdtx_update_device_state(ddev, msecs_to_jiffies(1000));
11371d609992SMaximilian Luz }
11381d609992SMaximilian Luz 
11391d609992SMaximilian Luz static const struct dev_pm_ops surface_dtx_pm_ops = {
11401d609992SMaximilian Luz 	.complete = surface_dtx_pm_complete,
11411d609992SMaximilian Luz };
11421d609992SMaximilian Luz 
11431d609992SMaximilian Luz #else /* CONFIG_PM_SLEEP */
11441d609992SMaximilian Luz 
11451d609992SMaximilian Luz static const struct dev_pm_ops surface_dtx_pm_ops = {};
11461d609992SMaximilian Luz 
11471d609992SMaximilian Luz #endif /* CONFIG_PM_SLEEP */
11481d609992SMaximilian Luz 
11491d609992SMaximilian Luz 
11501d609992SMaximilian Luz /* -- Platform driver. ------------------------------------------------------ */
11511d609992SMaximilian Luz 
surface_dtx_platform_probe(struct platform_device * pdev)11521d609992SMaximilian Luz static int surface_dtx_platform_probe(struct platform_device *pdev)
11531d609992SMaximilian Luz {
11541d609992SMaximilian Luz 	struct ssam_controller *ctrl;
11551d609992SMaximilian Luz 	struct sdtx_device *ddev;
11561d609992SMaximilian Luz 
11571d609992SMaximilian Luz 	/* Link to EC. */
11581d609992SMaximilian Luz 	ctrl = ssam_client_bind(&pdev->dev);
11591d609992SMaximilian Luz 	if (IS_ERR(ctrl))
11601d609992SMaximilian Luz 		return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
11611d609992SMaximilian Luz 
11621d609992SMaximilian Luz 	ddev = sdtx_device_create(&pdev->dev, ctrl);
11631d609992SMaximilian Luz 	if (IS_ERR(ddev))
11641d609992SMaximilian Luz 		return PTR_ERR(ddev);
11651d609992SMaximilian Luz 
11661d609992SMaximilian Luz 	platform_set_drvdata(pdev, ddev);
11671d609992SMaximilian Luz 	return 0;
11681d609992SMaximilian Luz }
11691d609992SMaximilian Luz 
surface_dtx_platform_remove(struct platform_device * pdev)11700c845611SUwe Kleine-König static void surface_dtx_platform_remove(struct platform_device *pdev)
11711d609992SMaximilian Luz {
11721d609992SMaximilian Luz 	sdtx_device_destroy(platform_get_drvdata(pdev));
11731d609992SMaximilian Luz }
11741d609992SMaximilian Luz 
11751d609992SMaximilian Luz static const struct acpi_device_id surface_dtx_acpi_match[] = {
11761d609992SMaximilian Luz 	{ "MSHW0133", 0 },
11771d609992SMaximilian Luz 	{ },
11781d609992SMaximilian Luz };
11791d609992SMaximilian Luz MODULE_DEVICE_TABLE(acpi, surface_dtx_acpi_match);
11801d609992SMaximilian Luz 
11811d609992SMaximilian Luz static struct platform_driver surface_dtx_platform_driver = {
11821d609992SMaximilian Luz 	.probe = surface_dtx_platform_probe,
1183*e70140baSLinus Torvalds 	.remove = surface_dtx_platform_remove,
11841d609992SMaximilian Luz 	.driver = {
11851d609992SMaximilian Luz 		.name = "surface_dtx_pltf",
11861d609992SMaximilian Luz 		.acpi_match_table = surface_dtx_acpi_match,
11871d609992SMaximilian Luz 		.pm = &surface_dtx_pm_ops,
11881d609992SMaximilian Luz 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
11891d609992SMaximilian Luz 	},
11901d609992SMaximilian Luz };
1191e893d45fSMaximilian Luz 
1192e893d45fSMaximilian Luz 
1193e893d45fSMaximilian Luz /* -- SSAM device driver. --------------------------------------------------- */
1194e893d45fSMaximilian Luz 
1195e893d45fSMaximilian Luz #ifdef CONFIG_SURFACE_AGGREGATOR_BUS
1196e893d45fSMaximilian Luz 
surface_dtx_ssam_probe(struct ssam_device * sdev)1197e893d45fSMaximilian Luz static int surface_dtx_ssam_probe(struct ssam_device *sdev)
1198e893d45fSMaximilian Luz {
1199e893d45fSMaximilian Luz 	struct sdtx_device *ddev;
1200e893d45fSMaximilian Luz 
1201e893d45fSMaximilian Luz 	ddev = sdtx_device_create(&sdev->dev, sdev->ctrl);
1202e893d45fSMaximilian Luz 	if (IS_ERR(ddev))
1203e893d45fSMaximilian Luz 		return PTR_ERR(ddev);
1204e893d45fSMaximilian Luz 
1205e893d45fSMaximilian Luz 	ssam_device_set_drvdata(sdev, ddev);
1206e893d45fSMaximilian Luz 	return 0;
1207e893d45fSMaximilian Luz }
1208e893d45fSMaximilian Luz 
surface_dtx_ssam_remove(struct ssam_device * sdev)1209e893d45fSMaximilian Luz static void surface_dtx_ssam_remove(struct ssam_device *sdev)
1210e893d45fSMaximilian Luz {
1211e893d45fSMaximilian Luz 	sdtx_device_destroy(ssam_device_get_drvdata(sdev));
1212e893d45fSMaximilian Luz }
1213e893d45fSMaximilian Luz 
1214e893d45fSMaximilian Luz static const struct ssam_device_id surface_dtx_ssam_match[] = {
121578abf1b5SMaximilian Luz 	{ SSAM_SDEV(BAS, SAM, 0x00, 0x00) },
1216e893d45fSMaximilian Luz 	{ },
1217e893d45fSMaximilian Luz };
1218e893d45fSMaximilian Luz MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match);
1219e893d45fSMaximilian Luz 
1220e893d45fSMaximilian Luz static struct ssam_device_driver surface_dtx_ssam_driver = {
1221e893d45fSMaximilian Luz 	.probe = surface_dtx_ssam_probe,
1222e893d45fSMaximilian Luz 	.remove = surface_dtx_ssam_remove,
1223e893d45fSMaximilian Luz 	.match_table = surface_dtx_ssam_match,
1224e893d45fSMaximilian Luz 	.driver = {
1225e893d45fSMaximilian Luz 		.name = "surface_dtx",
1226e893d45fSMaximilian Luz 		.pm = &surface_dtx_pm_ops,
1227e893d45fSMaximilian Luz 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
1228e893d45fSMaximilian Luz 	},
1229e893d45fSMaximilian Luz };
1230e893d45fSMaximilian Luz 
ssam_dtx_driver_register(void)1231e893d45fSMaximilian Luz static int ssam_dtx_driver_register(void)
1232e893d45fSMaximilian Luz {
1233e893d45fSMaximilian Luz 	return ssam_device_driver_register(&surface_dtx_ssam_driver);
1234e893d45fSMaximilian Luz }
1235e893d45fSMaximilian Luz 
ssam_dtx_driver_unregister(void)1236e893d45fSMaximilian Luz static void ssam_dtx_driver_unregister(void)
1237e893d45fSMaximilian Luz {
1238e893d45fSMaximilian Luz 	ssam_device_driver_unregister(&surface_dtx_ssam_driver);
1239e893d45fSMaximilian Luz }
1240e893d45fSMaximilian Luz 
1241e893d45fSMaximilian Luz #else /* CONFIG_SURFACE_AGGREGATOR_BUS */
1242e893d45fSMaximilian Luz 
ssam_dtx_driver_register(void)1243e893d45fSMaximilian Luz static int ssam_dtx_driver_register(void)
1244e893d45fSMaximilian Luz {
1245e893d45fSMaximilian Luz 	return 0;
1246e893d45fSMaximilian Luz }
1247e893d45fSMaximilian Luz 
ssam_dtx_driver_unregister(void)1248e893d45fSMaximilian Luz static void ssam_dtx_driver_unregister(void)
1249e893d45fSMaximilian Luz {
1250e893d45fSMaximilian Luz }
1251e893d45fSMaximilian Luz 
1252e893d45fSMaximilian Luz #endif /* CONFIG_SURFACE_AGGREGATOR_BUS */
1253e893d45fSMaximilian Luz 
1254e893d45fSMaximilian Luz 
1255e893d45fSMaximilian Luz /* -- Module setup. --------------------------------------------------------- */
1256e893d45fSMaximilian Luz 
surface_dtx_init(void)1257e893d45fSMaximilian Luz static int __init surface_dtx_init(void)
1258e893d45fSMaximilian Luz {
1259e893d45fSMaximilian Luz 	int status;
1260e893d45fSMaximilian Luz 
1261e893d45fSMaximilian Luz 	status = ssam_dtx_driver_register();
1262e893d45fSMaximilian Luz 	if (status)
1263e893d45fSMaximilian Luz 		return status;
1264e893d45fSMaximilian Luz 
1265e893d45fSMaximilian Luz 	status = platform_driver_register(&surface_dtx_platform_driver);
1266e893d45fSMaximilian Luz 	if (status)
1267e893d45fSMaximilian Luz 		ssam_dtx_driver_unregister();
1268e893d45fSMaximilian Luz 
1269e893d45fSMaximilian Luz 	return status;
1270e893d45fSMaximilian Luz }
1271e893d45fSMaximilian Luz module_init(surface_dtx_init);
1272e893d45fSMaximilian Luz 
surface_dtx_exit(void)1273e893d45fSMaximilian Luz static void __exit surface_dtx_exit(void)
1274e893d45fSMaximilian Luz {
1275e893d45fSMaximilian Luz 	platform_driver_unregister(&surface_dtx_platform_driver);
1276e893d45fSMaximilian Luz 	ssam_dtx_driver_unregister();
1277e893d45fSMaximilian Luz }
1278e893d45fSMaximilian Luz module_exit(surface_dtx_exit);
12791d609992SMaximilian Luz 
12801d609992SMaximilian Luz MODULE_AUTHOR("Maximilian Luz <[email protected]>");
12811d609992SMaximilian Luz MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module");
12821d609992SMaximilian Luz MODULE_LICENSE("GPL");
1283