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