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