1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Driver for the Intel P-Unit Mailbox IPC mechanism 4 * 5 * (C) Copyright 2015 Intel Corporation 6 * 7 * The heart of the P-Unit is the Foxton microcontroller and its firmware, 8 * which provide mailbox interface for power management usage. 9 */ 10 11 #include <linux/bitops.h> 12 #include <linux/delay.h> 13 #include <linux/device.h> 14 #include <linux/interrupt.h> 15 #include <linux/io.h> 16 #include <linux/module.h> 17 #include <linux/platform_device.h> 18 19 #include <asm/intel_punit_ipc.h> 20 21 /* IPC Mailbox registers */ 22 #define OFFSET_DATA_LOW 0x0 23 #define OFFSET_DATA_HIGH 0x4 24 /* bit field of interface register */ 25 #define CMD_RUN BIT(31) 26 #define CMD_ERRCODE_MASK GENMASK(7, 0) 27 #define CMD_PARA1_SHIFT 8 28 #define CMD_PARA2_SHIFT 16 29 30 #define CMD_TIMEOUT_SECONDS 1 31 32 enum { 33 BASE_DATA = 0, 34 BASE_IFACE, 35 BASE_MAX, 36 }; 37 38 typedef struct { 39 struct device *dev; 40 struct mutex lock; 41 int irq; 42 struct completion cmd_complete; 43 /* base of interface and data registers */ 44 void __iomem *base[RESERVED_IPC][BASE_MAX]; 45 IPC_TYPE type; 46 } IPC_DEV; 47 48 static IPC_DEV *punit_ipcdev; 49 50 static inline u32 ipc_read_status(IPC_DEV *ipcdev, IPC_TYPE type) 51 { 52 return readl(ipcdev->base[type][BASE_IFACE]); 53 } 54 55 static inline void ipc_write_cmd(IPC_DEV *ipcdev, IPC_TYPE type, u32 cmd) 56 { 57 writel(cmd, ipcdev->base[type][BASE_IFACE]); 58 } 59 60 static inline u32 ipc_read_data_low(IPC_DEV *ipcdev, IPC_TYPE type) 61 { 62 return readl(ipcdev->base[type][BASE_DATA] + OFFSET_DATA_LOW); 63 } 64 65 static inline u32 ipc_read_data_high(IPC_DEV *ipcdev, IPC_TYPE type) 66 { 67 return readl(ipcdev->base[type][BASE_DATA] + OFFSET_DATA_HIGH); 68 } 69 70 static inline void ipc_write_data_low(IPC_DEV *ipcdev, IPC_TYPE type, u32 data) 71 { 72 writel(data, ipcdev->base[type][BASE_DATA] + OFFSET_DATA_LOW); 73 } 74 75 static inline void ipc_write_data_high(IPC_DEV *ipcdev, IPC_TYPE type, u32 data) 76 { 77 writel(data, ipcdev->base[type][BASE_DATA] + OFFSET_DATA_HIGH); 78 } 79 80 static const char *ipc_err_string(int error) 81 { 82 if (error == IPC_PUNIT_ERR_SUCCESS) 83 return "no error"; 84 else if (error == IPC_PUNIT_ERR_INVALID_CMD) 85 return "invalid command"; 86 else if (error == IPC_PUNIT_ERR_INVALID_PARAMETER) 87 return "invalid parameter"; 88 else if (error == IPC_PUNIT_ERR_CMD_TIMEOUT) 89 return "command timeout"; 90 else if (error == IPC_PUNIT_ERR_CMD_LOCKED) 91 return "command locked"; 92 else if (error == IPC_PUNIT_ERR_INVALID_VR_ID) 93 return "invalid vr id"; 94 else if (error == IPC_PUNIT_ERR_VR_ERR) 95 return "vr error"; 96 else 97 return "unknown error"; 98 } 99 100 static int intel_punit_ipc_check_status(IPC_DEV *ipcdev, IPC_TYPE type) 101 { 102 int loops = CMD_TIMEOUT_SECONDS * USEC_PER_SEC; 103 int errcode; 104 int status; 105 106 if (ipcdev->irq) { 107 if (!wait_for_completion_timeout(&ipcdev->cmd_complete, 108 CMD_TIMEOUT_SECONDS * HZ)) { 109 dev_err(ipcdev->dev, "IPC timed out\n"); 110 return -ETIMEDOUT; 111 } 112 } else { 113 while ((ipc_read_status(ipcdev, type) & CMD_RUN) && --loops) 114 udelay(1); 115 if (!loops) { 116 dev_err(ipcdev->dev, "IPC timed out\n"); 117 return -ETIMEDOUT; 118 } 119 } 120 121 status = ipc_read_status(ipcdev, type); 122 errcode = status & CMD_ERRCODE_MASK; 123 if (errcode) { 124 dev_err(ipcdev->dev, "IPC failed: %s, IPC_STS=0x%x\n", 125 ipc_err_string(errcode), status); 126 return -EIO; 127 } 128 129 return 0; 130 } 131 132 /** 133 * intel_punit_ipc_command() - IPC command with data and pointers 134 * @cmd: IPC command code. 135 * @para1: First 8bit parameter, set 0 if not used. 136 * @para2: Second 8bit parameter, set 0 if not used. 137 * @in: Input data, 32bit for BIOS cmd, two 32bit for GTD and ISPD. 138 * @out: Output data. 139 * 140 * Send a IPC command to P-Unit with data transaction 141 * 142 * Return: IPC error code or 0 on success. 143 */ 144 int intel_punit_ipc_command(u32 cmd, u32 para1, u32 para2, u32 *in, u32 *out) 145 { 146 IPC_DEV *ipcdev = punit_ipcdev; 147 IPC_TYPE type; 148 u32 val; 149 int ret; 150 151 mutex_lock(&ipcdev->lock); 152 153 reinit_completion(&ipcdev->cmd_complete); 154 type = (cmd & IPC_PUNIT_CMD_TYPE_MASK) >> IPC_TYPE_OFFSET; 155 156 if (in) { 157 ipc_write_data_low(ipcdev, type, *in); 158 if (type == GTDRIVER_IPC || type == ISPDRIVER_IPC) 159 ipc_write_data_high(ipcdev, type, *++in); 160 } 161 162 val = cmd & ~IPC_PUNIT_CMD_TYPE_MASK; 163 val |= CMD_RUN | para2 << CMD_PARA2_SHIFT | para1 << CMD_PARA1_SHIFT; 164 ipc_write_cmd(ipcdev, type, val); 165 166 ret = intel_punit_ipc_check_status(ipcdev, type); 167 if (ret) 168 goto out; 169 170 if (out) { 171 *out = ipc_read_data_low(ipcdev, type); 172 if (type == GTDRIVER_IPC || type == ISPDRIVER_IPC) 173 *++out = ipc_read_data_high(ipcdev, type); 174 } 175 176 out: 177 mutex_unlock(&ipcdev->lock); 178 return ret; 179 } 180 EXPORT_SYMBOL_GPL(intel_punit_ipc_command); 181 182 static irqreturn_t intel_punit_ioc(int irq, void *dev_id) 183 { 184 IPC_DEV *ipcdev = dev_id; 185 186 complete(&ipcdev->cmd_complete); 187 return IRQ_HANDLED; 188 } 189 190 static int intel_punit_get_bars(struct platform_device *pdev) 191 { 192 void __iomem *addr; 193 194 /* 195 * The following resources are required 196 * - BIOS_IPC BASE_DATA 197 * - BIOS_IPC BASE_IFACE 198 */ 199 addr = devm_platform_ioremap_resource(pdev, 0); 200 if (IS_ERR(addr)) 201 return PTR_ERR(addr); 202 punit_ipcdev->base[BIOS_IPC][BASE_DATA] = addr; 203 204 addr = devm_platform_ioremap_resource(pdev, 1); 205 if (IS_ERR(addr)) 206 return PTR_ERR(addr); 207 punit_ipcdev->base[BIOS_IPC][BASE_IFACE] = addr; 208 209 /* 210 * The following resources are optional 211 * - ISPDRIVER_IPC BASE_DATA 212 * - ISPDRIVER_IPC BASE_IFACE 213 * - GTDRIVER_IPC BASE_DATA 214 * - GTDRIVER_IPC BASE_IFACE 215 */ 216 addr = devm_platform_ioremap_resource(pdev, 2); 217 if (!IS_ERR(addr)) 218 punit_ipcdev->base[ISPDRIVER_IPC][BASE_DATA] = addr; 219 220 addr = devm_platform_ioremap_resource(pdev, 3); 221 if (!IS_ERR(addr)) 222 punit_ipcdev->base[ISPDRIVER_IPC][BASE_IFACE] = addr; 223 224 addr = devm_platform_ioremap_resource(pdev, 4); 225 if (!IS_ERR(addr)) 226 punit_ipcdev->base[GTDRIVER_IPC][BASE_DATA] = addr; 227 228 addr = devm_platform_ioremap_resource(pdev, 5); 229 if (!IS_ERR(addr)) 230 punit_ipcdev->base[GTDRIVER_IPC][BASE_IFACE] = addr; 231 232 return 0; 233 } 234 235 static int intel_punit_ipc_probe(struct platform_device *pdev) 236 { 237 int irq, ret; 238 239 punit_ipcdev = devm_kzalloc(&pdev->dev, 240 sizeof(*punit_ipcdev), GFP_KERNEL); 241 if (!punit_ipcdev) 242 return -ENOMEM; 243 244 platform_set_drvdata(pdev, punit_ipcdev); 245 246 irq = platform_get_irq_optional(pdev, 0); 247 if (irq < 0) { 248 dev_warn(&pdev->dev, "Invalid IRQ, using polling mode\n"); 249 } else { 250 ret = devm_request_irq(&pdev->dev, irq, intel_punit_ioc, 251 IRQF_NO_SUSPEND, "intel_punit_ipc", 252 punit_ipcdev); 253 if (ret) { 254 dev_err(&pdev->dev, "Failed to request irq: %d\n", irq); 255 return ret; 256 } 257 punit_ipcdev->irq = irq; 258 } 259 260 ret = intel_punit_get_bars(pdev); 261 if (ret) 262 return ret; 263 264 punit_ipcdev->dev = &pdev->dev; 265 mutex_init(&punit_ipcdev->lock); 266 init_completion(&punit_ipcdev->cmd_complete); 267 268 return 0; 269 } 270 271 static const struct acpi_device_id punit_ipc_acpi_ids[] = { 272 { "INT34D4", 0 }, 273 { } 274 }; 275 MODULE_DEVICE_TABLE(acpi, punit_ipc_acpi_ids); 276 277 static struct platform_driver intel_punit_ipc_driver = { 278 .probe = intel_punit_ipc_probe, 279 .driver = { 280 .name = "intel_punit_ipc", 281 .acpi_match_table = punit_ipc_acpi_ids, 282 }, 283 }; 284 285 static int __init intel_punit_ipc_init(void) 286 { 287 return platform_driver_register(&intel_punit_ipc_driver); 288 } 289 290 static void __exit intel_punit_ipc_exit(void) 291 { 292 platform_driver_unregister(&intel_punit_ipc_driver); 293 } 294 295 MODULE_AUTHOR("Zha Qipeng <qipeng.zha@intel.com>"); 296 MODULE_DESCRIPTION("Intel P-Unit IPC driver"); 297 MODULE_LICENSE("GPL v2"); 298 299 /* Some modules are dependent on this, so init earlier */ 300 fs_initcall(intel_punit_ipc_init); 301 module_exit(intel_punit_ipc_exit); 302