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