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