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 20 struct ucsi_acpi { 21 struct device *dev; 22 struct ucsi *ucsi; 23 void *base; 24 struct completion complete; 25 unsigned long flags; 26 guid_t guid; 27 u64 cmd; 28 }; 29 30 static int ucsi_acpi_dsm(struct ucsi_acpi *ua, int func) 31 { 32 union acpi_object *obj; 33 34 obj = acpi_evaluate_dsm(ACPI_HANDLE(ua->dev), &ua->guid, 1, func, 35 NULL); 36 if (!obj) { 37 dev_err(ua->dev, "%s: failed to evaluate _DSM %d\n", 38 __func__, func); 39 return -EIO; 40 } 41 42 ACPI_FREE(obj); 43 return 0; 44 } 45 46 static int ucsi_acpi_read(struct ucsi *ucsi, unsigned int offset, 47 void *val, size_t val_len) 48 { 49 struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); 50 int ret; 51 52 ret = ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_READ); 53 if (ret) 54 return ret; 55 56 memcpy(val, ua->base + offset, val_len); 57 58 return 0; 59 } 60 61 static int ucsi_acpi_async_write(struct ucsi *ucsi, unsigned int offset, 62 const void *val, size_t val_len) 63 { 64 struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); 65 66 memcpy(ua->base + offset, val, val_len); 67 ua->cmd = *(u64 *)val; 68 69 return ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_WRITE); 70 } 71 72 static int ucsi_acpi_sync_write(struct ucsi *ucsi, unsigned int offset, 73 const void *val, size_t val_len) 74 { 75 struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); 76 int ret; 77 78 set_bit(COMMAND_PENDING, &ua->flags); 79 80 ret = ucsi_acpi_async_write(ucsi, offset, val, val_len); 81 if (ret) 82 goto out_clear_bit; 83 84 if (!wait_for_completion_timeout(&ua->complete, 5 * HZ)) 85 ret = -ETIMEDOUT; 86 87 out_clear_bit: 88 clear_bit(COMMAND_PENDING, &ua->flags); 89 90 return ret; 91 } 92 93 static const struct ucsi_operations ucsi_acpi_ops = { 94 .read = ucsi_acpi_read, 95 .sync_write = ucsi_acpi_sync_write, 96 .async_write = ucsi_acpi_async_write 97 }; 98 99 static int 100 ucsi_zenbook_read(struct ucsi *ucsi, unsigned int offset, void *val, size_t val_len) 101 { 102 struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); 103 int ret; 104 105 if (offset == UCSI_VERSION || UCSI_COMMAND(ua->cmd) == UCSI_PPM_RESET) { 106 ret = ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_READ); 107 if (ret) 108 return ret; 109 } 110 111 memcpy(val, ua->base + offset, val_len); 112 113 return 0; 114 } 115 116 static const struct ucsi_operations ucsi_zenbook_ops = { 117 .read = ucsi_zenbook_read, 118 .sync_write = ucsi_acpi_sync_write, 119 .async_write = ucsi_acpi_async_write 120 }; 121 122 static const struct dmi_system_id zenbook_dmi_id[] = { 123 { 124 .matches = { 125 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), 126 DMI_MATCH(DMI_PRODUCT_NAME, "ZenBook UX325UA_UM325UA"), 127 }, 128 }, 129 { } 130 }; 131 132 static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data) 133 { 134 struct ucsi_acpi *ua = data; 135 u32 cci; 136 int ret; 137 138 ret = ua->ucsi->ops->read(ua->ucsi, UCSI_CCI, &cci, sizeof(cci)); 139 if (ret) 140 return; 141 142 if (UCSI_CCI_CONNECTOR(cci)) 143 ucsi_connector_change(ua->ucsi, UCSI_CCI_CONNECTOR(cci)); 144 145 if (test_bit(COMMAND_PENDING, &ua->flags) && 146 cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE)) 147 complete(&ua->complete); 148 } 149 150 static int ucsi_acpi_probe(struct platform_device *pdev) 151 { 152 struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 153 const struct ucsi_operations *ops = &ucsi_acpi_ops; 154 struct ucsi_acpi *ua; 155 struct resource *res; 156 acpi_status status; 157 int ret; 158 159 if (adev->dep_unmet) 160 return -EPROBE_DEFER; 161 162 ua = devm_kzalloc(&pdev->dev, sizeof(*ua), GFP_KERNEL); 163 if (!ua) 164 return -ENOMEM; 165 166 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 167 if (!res) { 168 dev_err(&pdev->dev, "missing memory resource\n"); 169 return -ENODEV; 170 } 171 172 ua->base = devm_memremap(&pdev->dev, res->start, resource_size(res), MEMREMAP_WB); 173 if (IS_ERR(ua->base)) 174 return PTR_ERR(ua->base); 175 176 ret = guid_parse(UCSI_DSM_UUID, &ua->guid); 177 if (ret) 178 return ret; 179 180 init_completion(&ua->complete); 181 ua->dev = &pdev->dev; 182 183 if (dmi_check_system(zenbook_dmi_id)) 184 ops = &ucsi_zenbook_ops; 185 186 ua->ucsi = ucsi_create(&pdev->dev, ops); 187 if (IS_ERR(ua->ucsi)) 188 return PTR_ERR(ua->ucsi); 189 190 ucsi_set_drvdata(ua->ucsi, ua); 191 192 status = acpi_install_notify_handler(ACPI_HANDLE(&pdev->dev), 193 ACPI_DEVICE_NOTIFY, 194 ucsi_acpi_notify, ua); 195 if (ACPI_FAILURE(status)) { 196 dev_err(&pdev->dev, "failed to install notify handler\n"); 197 ucsi_destroy(ua->ucsi); 198 return -ENODEV; 199 } 200 201 ret = ucsi_register(ua->ucsi); 202 if (ret) { 203 acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), 204 ACPI_DEVICE_NOTIFY, 205 ucsi_acpi_notify); 206 ucsi_destroy(ua->ucsi); 207 return ret; 208 } 209 210 platform_set_drvdata(pdev, ua); 211 212 return 0; 213 } 214 215 static void ucsi_acpi_remove(struct platform_device *pdev) 216 { 217 struct ucsi_acpi *ua = platform_get_drvdata(pdev); 218 219 ucsi_unregister(ua->ucsi); 220 ucsi_destroy(ua->ucsi); 221 222 acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), ACPI_DEVICE_NOTIFY, 223 ucsi_acpi_notify); 224 } 225 226 static int ucsi_acpi_resume(struct device *dev) 227 { 228 struct ucsi_acpi *ua = dev_get_drvdata(dev); 229 230 return ucsi_resume(ua->ucsi); 231 } 232 233 static DEFINE_SIMPLE_DEV_PM_OPS(ucsi_acpi_pm_ops, NULL, ucsi_acpi_resume); 234 235 static const struct acpi_device_id ucsi_acpi_match[] = { 236 { "PNP0CA0", 0 }, 237 { }, 238 }; 239 MODULE_DEVICE_TABLE(acpi, ucsi_acpi_match); 240 241 static struct platform_driver ucsi_acpi_platform_driver = { 242 .driver = { 243 .name = "ucsi_acpi", 244 .pm = pm_ptr(&ucsi_acpi_pm_ops), 245 .acpi_match_table = ACPI_PTR(ucsi_acpi_match), 246 }, 247 .probe = ucsi_acpi_probe, 248 .remove_new = ucsi_acpi_remove, 249 }; 250 251 module_platform_driver(ucsi_acpi_platform_driver); 252 253 MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); 254 MODULE_LICENSE("GPL v2"); 255 MODULE_DESCRIPTION("UCSI ACPI driver"); 256