1// SPDX-License-Identifier: GPL-2.0 2/* 3 * UCSI ACPI driver 4 * 5 * Copyright (C) 2017, Intel Corporation 6 * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> 7 */ 8 9#include <linux/platform_device.h> 10#include <linux/module.h> 11#include <linux/acpi.h> 12#include <linux/dmi.h> 13 14#include "ucsi.h" 15 16#define UCSI_DSM_UUID "6f8398c2-7ca4-11e4-ad36-631042b5008f" 17#define UCSI_DSM_FUNC_WRITE 1 18#define UCSI_DSM_FUNC_READ 2 19 20struct ucsi_acpi { 21 struct device *dev; 22 struct ucsi *ucsi; 23 void *base; 24 struct completion complete; 25 unsigned long flags; 26#define UCSI_ACPI_SUPPRESS_EVENT 0 27#define UCSI_ACPI_COMMAND_PENDING 1 28#define UCSI_ACPI_ACK_PENDING 2 29 guid_t guid; 30 u64 cmd; 31}; 32 33static int ucsi_acpi_dsm(struct ucsi_acpi *ua, int func) 34{ 35 union acpi_object *obj; 36 37 obj = acpi_evaluate_dsm(ACPI_HANDLE(ua->dev), &ua->guid, 1, func, 38 NULL); 39 if (!obj) { 40 dev_err(ua->dev, "%s: failed to evaluate _DSM %d\n", 41 __func__, func); 42 return -EIO; 43 } 44 45 ACPI_FREE(obj); 46 return 0; 47} 48 49static int ucsi_acpi_read(struct ucsi *ucsi, unsigned int offset, 50 void *val, size_t val_len) 51{ 52 struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); 53 int ret; 54 55 ret = ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_READ); 56 if (ret) 57 return ret; 58 59 memcpy(val, ua->base + offset, val_len); 60 61 return 0; 62} 63 64static int ucsi_acpi_async_write(struct ucsi *ucsi, unsigned int offset, 65 const void *val, size_t val_len) 66{ 67 struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); 68 69 memcpy(ua->base + offset, val, val_len); 70 ua->cmd = *(u64 *)val; 71 72 return ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_WRITE); 73} 74 75static int ucsi_acpi_sync_write(struct ucsi *ucsi, unsigned int offset, 76 const void *val, size_t val_len) 77{ 78 struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); 79 bool ack = UCSI_COMMAND(*(u64 *)val) == UCSI_ACK_CC_CI; 80 int ret; 81 82 if (ack) 83 set_bit(UCSI_ACPI_ACK_PENDING, &ua->flags); 84 else 85 set_bit(UCSI_ACPI_COMMAND_PENDING, &ua->flags); 86 87 ret = ucsi_acpi_async_write(ucsi, offset, val, val_len); 88 if (ret) 89 goto out_clear_bit; 90 91 if (!wait_for_completion_timeout(&ua->complete, 5 * HZ)) 92 ret = -ETIMEDOUT; 93 94out_clear_bit: 95 if (ack) 96 clear_bit(UCSI_ACPI_ACK_PENDING, &ua->flags); 97 else 98 clear_bit(UCSI_ACPI_COMMAND_PENDING, &ua->flags); 99 100 return ret; 101} 102 103static const struct ucsi_operations ucsi_acpi_ops = { 104 .read = ucsi_acpi_read, 105 .sync_write = ucsi_acpi_sync_write, 106 .async_write = ucsi_acpi_async_write 107}; 108 109static int 110ucsi_zenbook_read(struct ucsi *ucsi, unsigned int offset, void *val, size_t val_len) 111{ 112 struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); 113 int ret; 114 115 if (offset == UCSI_VERSION || UCSI_COMMAND(ua->cmd) == UCSI_PPM_RESET) { 116 ret = ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_READ); 117 if (ret) 118 return ret; 119 } 120 121 memcpy(val, ua->base + offset, val_len); 122 123 return 0; 124} 125 126static const struct ucsi_operations ucsi_zenbook_ops = { 127 .read = ucsi_zenbook_read, 128 .sync_write = ucsi_acpi_sync_write, 129 .async_write = ucsi_acpi_async_write 130}; 131 132/* 133 * Some Dell laptops don't like ACK commands with the 134 * UCSI_ACK_CONNECTOR_CHANGE but not the UCSI_ACK_COMMAND_COMPLETE 135 * bit set. To work around this send a dummy command and bundle the 136 * UCSI_ACK_CONNECTOR_CHANGE with the UCSI_ACK_COMMAND_COMPLETE 137 * for the dummy command. 138 */ 139static int 140ucsi_dell_sync_write(struct ucsi *ucsi, unsigned int offset, 141 const void *val, size_t val_len) 142{ 143 struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); 144 u64 cmd = *(u64 *)val; 145 u64 dummycmd = UCSI_GET_CAPABILITY; 146 int ret; 147 148 if (cmd == (UCSI_ACK_CC_CI | UCSI_ACK_CONNECTOR_CHANGE)) { 149 cmd |= UCSI_ACK_COMMAND_COMPLETE; 150 151 /* 152 * The UCSI core thinks it is sending a connector change ack 153 * and will accept new connector change events. We don't want 154 * this to happen for the dummy command as its response will 155 * still report the very event that the core is trying to clear. 156 */ 157 set_bit(UCSI_ACPI_SUPPRESS_EVENT, &ua->flags); 158 ret = ucsi_acpi_sync_write(ucsi, UCSI_CONTROL, &dummycmd, 159 sizeof(dummycmd)); 160 clear_bit(UCSI_ACPI_SUPPRESS_EVENT, &ua->flags); 161 162 if (ret < 0) 163 return ret; 164 } 165 166 return ucsi_acpi_sync_write(ucsi, UCSI_CONTROL, &cmd, sizeof(cmd)); 167} 168 169static const struct ucsi_operations ucsi_dell_ops = { 170 .read = ucsi_acpi_read, 171 .sync_write = ucsi_dell_sync_write, 172 .async_write = ucsi_acpi_async_write 173}; 174 175static const struct dmi_system_id ucsi_acpi_quirks[] = { 176 { 177 .matches = { 178 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 179 DMI_MATCH(DMI_PRODUCT_NAME, "ZenBook UX325UA_UM325UA"), 180 }, 181 .driver_data = (void *)&ucsi_zenbook_ops, 182 }, 183 { 184 .matches = { 185 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 186 }, 187 .driver_data = (void *)&ucsi_dell_ops, 188 }, 189 { } 190}; 191 192static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data) 193{ 194 struct ucsi_acpi *ua = data; 195 u32 cci; 196 int ret; 197 198 ret = ua->ucsi->ops->read(ua->ucsi, UCSI_CCI, &cci, sizeof(cci)); 199 if (ret) 200 return; 201 202 if (UCSI_CCI_CONNECTOR(cci) && 203 !test_bit(UCSI_ACPI_SUPPRESS_EVENT, &ua->flags)) 204 ucsi_connector_change(ua->ucsi, UCSI_CCI_CONNECTOR(cci)); 205 206 if (cci & UCSI_CCI_ACK_COMPLETE && test_bit(ACK_PENDING, &ua->flags)) 207 complete(&ua->complete); 208 if (cci & UCSI_CCI_COMMAND_COMPLETE && 209 test_bit(UCSI_ACPI_COMMAND_PENDING, &ua->flags)) 210 complete(&ua->complete); 211} 212 213static int ucsi_acpi_probe(struct platform_device *pdev) 214{ 215 struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 216 const struct ucsi_operations *ops = &ucsi_acpi_ops; 217 const struct dmi_system_id *id; 218 struct ucsi_acpi *ua; 219 struct resource *res; 220 acpi_status status; 221 int ret; 222 223 if (adev->dep_unmet) 224 return -EPROBE_DEFER; 225 226 ua = devm_kzalloc(&pdev->dev, sizeof(*ua), GFP_KERNEL); 227 if (!ua) 228 return -ENOMEM; 229 230 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 231 if (!res) { 232 dev_err(&pdev->dev, "missing memory resource\n"); 233 return -ENODEV; 234 } 235 236 ua->base = devm_memremap(&pdev->dev, res->start, resource_size(res), MEMREMAP_WB); 237 if (IS_ERR(ua->base)) 238 return PTR_ERR(ua->base); 239 240 ret = guid_parse(UCSI_DSM_UUID, &ua->guid); 241 if (ret) 242 return ret; 243 244 init_completion(&ua->complete); 245 ua->dev = &pdev->dev; 246 247 id = dmi_first_match(ucsi_acpi_quirks); 248 if (id) 249 ops = id->driver_data; 250 251 ua->ucsi = ucsi_create(&pdev->dev, ops); 252 if (IS_ERR(ua->ucsi)) 253 return PTR_ERR(ua->ucsi); 254 255 ucsi_set_drvdata(ua->ucsi, ua); 256 257 status = acpi_install_notify_handler(ACPI_HANDLE(&pdev->dev), 258 ACPI_DEVICE_NOTIFY, 259 ucsi_acpi_notify, ua); 260 if (ACPI_FAILURE(status)) { 261 dev_err(&pdev->dev, "failed to install notify handler\n"); 262 ucsi_destroy(ua->ucsi); 263 return -ENODEV; 264 } 265 266 ret = ucsi_register(ua->ucsi); 267 if (ret) { 268 acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), 269 ACPI_DEVICE_NOTIFY, 270 ucsi_acpi_notify); 271 ucsi_destroy(ua->ucsi); 272 return ret; 273 } 274 275 platform_set_drvdata(pdev, ua); 276 277 return 0; 278} 279 280static void ucsi_acpi_remove(struct platform_device *pdev) 281{ 282 struct ucsi_acpi *ua = platform_get_drvdata(pdev); 283 284 ucsi_unregister(ua->ucsi); 285 ucsi_destroy(ua->ucsi); 286 287 acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), ACPI_DEVICE_NOTIFY, 288 ucsi_acpi_notify); 289} 290 291static int ucsi_acpi_resume(struct device *dev) 292{ 293 struct ucsi_acpi *ua = dev_get_drvdata(dev); 294 295 return ucsi_resume(ua->ucsi); 296} 297 298static DEFINE_SIMPLE_DEV_PM_OPS(ucsi_acpi_pm_ops, NULL, ucsi_acpi_resume); 299 300static const struct acpi_device_id ucsi_acpi_match[] = { 301 { "PNP0CA0", 0 }, 302 { }, 303}; 304MODULE_DEVICE_TABLE(acpi, ucsi_acpi_match); 305 306static struct platform_driver ucsi_acpi_platform_driver = { 307 .driver = { 308 .name = "ucsi_acpi", 309 .pm = pm_ptr(&ucsi_acpi_pm_ops), 310 .acpi_match_table = ACPI_PTR(ucsi_acpi_match), 311 }, 312 .probe = ucsi_acpi_probe, 313 .remove_new = ucsi_acpi_remove, 314}; 315 316module_platform_driver(ucsi_acpi_platform_driver); 317 318MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); 319MODULE_LICENSE("GPL v2"); 320MODULE_DESCRIPTION("UCSI ACPI driver"); 321