linux/drivers/hid/intel-ish-hid/ipc/pci-ish.c
<<
>>
Prefs
   1/*
   2 * PCI glue for ISHTP provider device (ISH) driver
   3 *
   4 * Copyright (c) 2014-2016, Intel Corporation.
   5 *
   6 * This program is free software; you can redistribute it and/or modify it
   7 * under the terms and conditions of the GNU General Public License,
   8 * version 2, as published by the Free Software Foundation.
   9 *
  10 * This program is distributed in the hope it will be useful, but WITHOUT
  11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  13 * more details.
  14 */
  15
  16#include <linux/module.h>
  17#include <linux/moduleparam.h>
  18#include <linux/kernel.h>
  19#include <linux/device.h>
  20#include <linux/fs.h>
  21#include <linux/errno.h>
  22#include <linux/types.h>
  23#include <linux/pci.h>
  24#include <linux/sched.h>
  25#include <linux/interrupt.h>
  26#include <linux/workqueue.h>
  27#define CREATE_TRACE_POINTS
  28#include <trace/events/intel_ish.h>
  29#include "ishtp-dev.h"
  30#include "hw-ish.h"
  31
  32static const struct pci_device_id ish_pci_tbl[] = {
  33        {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CHV_DEVICE_ID)},
  34        {PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Ax_DEVICE_ID)},
  35        {PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Bx_DEVICE_ID)},
  36        {PCI_DEVICE(PCI_VENDOR_ID_INTEL, APL_Ax_DEVICE_ID)},
  37        {PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_Ax_DEVICE_ID)},
  38        {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_Ax_DEVICE_ID)},
  39        {PCI_DEVICE(PCI_VENDOR_ID_INTEL, GLK_Ax_DEVICE_ID)},
  40        {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_H_DEVICE_ID)},
  41        {0, }
  42};
  43MODULE_DEVICE_TABLE(pci, ish_pci_tbl);
  44
  45/**
  46 * ish_event_tracer() - Callback function to dump trace messages
  47 * @dev:        ishtp device
  48 * @format:     printf style format
  49 *
  50 * Callback to direct log messages to Linux trace buffers
  51 */
  52static __printf(2, 3)
  53void ish_event_tracer(struct ishtp_device *dev, const char *format, ...)
  54{
  55        if (trace_ishtp_dump_enabled()) {
  56                va_list args;
  57                char tmp_buf[100];
  58
  59                va_start(args, format);
  60                vsnprintf(tmp_buf, sizeof(tmp_buf), format, args);
  61                va_end(args);
  62
  63                trace_ishtp_dump(tmp_buf);
  64        }
  65}
  66
  67/**
  68 * ish_init() - Init function
  69 * @dev:        ishtp device
  70 *
  71 * This function initialize wait queues for suspend/resume and call
  72 * calls hadware initialization function. This will initiate
  73 * startup sequence
  74 *
  75 * Return: 0 for success or error code for failure
  76 */
  77static int ish_init(struct ishtp_device *dev)
  78{
  79        int ret;
  80
  81        /* Set the state of ISH HW to start */
  82        ret = ish_hw_start(dev);
  83        if (ret) {
  84                dev_err(dev->devc, "ISH: hw start failed.\n");
  85                return ret;
  86        }
  87
  88        /* Start the inter process communication to ISH processor */
  89        ret = ishtp_start(dev);
  90        if (ret) {
  91                dev_err(dev->devc, "ISHTP: Protocol init failed.\n");
  92                return ret;
  93        }
  94
  95        return 0;
  96}
  97
  98/**
  99 * ish_probe() - PCI driver probe callback
 100 * @pdev:       pci device
 101 * @ent:        pci device id
 102 *
 103 * Initialize PCI function, setup interrupt and call for ISH initialization
 104 *
 105 * Return: 0 for success or error code for failure
 106 */
 107static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 108{
 109        struct ishtp_device *dev;
 110        struct ish_hw *hw;
 111        int     ret;
 112
 113        /* enable pci dev */
 114        ret = pci_enable_device(pdev);
 115        if (ret) {
 116                dev_err(&pdev->dev, "ISH: Failed to enable PCI device\n");
 117                return ret;
 118        }
 119
 120        /* set PCI host mastering */
 121        pci_set_master(pdev);
 122
 123        /* pci request regions for ISH driver */
 124        ret = pci_request_regions(pdev, KBUILD_MODNAME);
 125        if (ret) {
 126                dev_err(&pdev->dev, "ISH: Failed to get PCI regions\n");
 127                goto disable_device;
 128        }
 129
 130        /* allocates and initializes the ISH dev structure */
 131        dev = ish_dev_init(pdev);
 132        if (!dev) {
 133                ret = -ENOMEM;
 134                goto release_regions;
 135        }
 136        hw = to_ish_hw(dev);
 137        dev->print_log = ish_event_tracer;
 138
 139        /* mapping IO device memory */
 140        hw->mem_addr = pci_iomap(pdev, 0, 0);
 141        if (!hw->mem_addr) {
 142                dev_err(&pdev->dev, "ISH: mapping I/O range failure\n");
 143                ret = -ENOMEM;
 144                goto free_device;
 145        }
 146
 147        dev->pdev = pdev;
 148
 149        pdev->dev_flags |= PCI_DEV_FLAGS_NO_D3;
 150
 151        /* request and enable interrupt */
 152        ret = request_irq(pdev->irq, ish_irq_handler, IRQF_SHARED,
 153                          KBUILD_MODNAME, dev);
 154        if (ret) {
 155                dev_err(&pdev->dev, "ISH: request IRQ failure (%d)\n",
 156                        pdev->irq);
 157                goto free_device;
 158        }
 159
 160        dev_set_drvdata(dev->devc, dev);
 161
 162        init_waitqueue_head(&dev->suspend_wait);
 163        init_waitqueue_head(&dev->resume_wait);
 164
 165        ret = ish_init(dev);
 166        if (ret)
 167                goto free_irq;
 168
 169        return 0;
 170
 171free_irq:
 172        free_irq(pdev->irq, dev);
 173free_device:
 174        pci_iounmap(pdev, hw->mem_addr);
 175        kfree(dev);
 176release_regions:
 177        pci_release_regions(pdev);
 178disable_device:
 179        pci_clear_master(pdev);
 180        pci_disable_device(pdev);
 181        dev_err(&pdev->dev, "ISH: PCI driver initialization failed.\n");
 182
 183        return ret;
 184}
 185
 186/**
 187 * ish_remove() - PCI driver remove callback
 188 * @pdev:       pci device
 189 *
 190 * This function does cleanup of ISH on pci remove callback
 191 */
 192static void ish_remove(struct pci_dev *pdev)
 193{
 194        struct ishtp_device *ishtp_dev = pci_get_drvdata(pdev);
 195        struct ish_hw *hw = to_ish_hw(ishtp_dev);
 196
 197        ishtp_bus_remove_all_clients(ishtp_dev, false);
 198        ish_device_disable(ishtp_dev);
 199
 200        free_irq(pdev->irq, ishtp_dev);
 201        pci_iounmap(pdev, hw->mem_addr);
 202        pci_release_regions(pdev);
 203        pci_clear_master(pdev);
 204        pci_disable_device(pdev);
 205        kfree(ishtp_dev);
 206}
 207
 208#ifdef CONFIG_PM
 209static struct device *ish_resume_device;
 210
 211/* 50ms to get resume response */
 212#define WAIT_FOR_RESUME_ACK_MS          50
 213
 214/**
 215 * ish_resume_handler() - Work function to complete resume
 216 * @work:       work struct
 217 *
 218 * The resume work function to complete resume function asynchronously.
 219 * There are two resume paths, one where ISH is not powered off,
 220 * in that case a simple resume message is enough, others we need
 221 * a reset sequence.
 222 */
 223static void ish_resume_handler(struct work_struct *work)
 224{
 225        struct pci_dev *pdev = to_pci_dev(ish_resume_device);
 226        struct ishtp_device *dev = pci_get_drvdata(pdev);
 227        uint32_t fwsts;
 228        int ret;
 229
 230        /* Get ISH FW status */
 231        fwsts = IPC_GET_ISH_FWSTS(dev->ops->get_fw_status(dev));
 232
 233        /*
 234         * If currently, in ISH FW, sensor app is loaded or beyond that,
 235         * it means ISH isn't powered off, in this case, send a resume message.
 236         */
 237        if (fwsts >= FWSTS_SENSOR_APP_LOADED) {
 238                ishtp_send_resume(dev);
 239
 240                /* Waiting to get resume response */
 241                if (dev->resume_flag)
 242                        ret = wait_event_interruptible_timeout(dev->resume_wait,
 243                                !dev->resume_flag,
 244                                msecs_to_jiffies(WAIT_FOR_RESUME_ACK_MS));
 245        }
 246
 247        /*
 248         * If in ISH FW, sensor app isn't loaded yet, or no resume response.
 249         * That means this platform is not S0ix compatible, or something is
 250         * wrong with ISH FW. So on resume, full reboot of ISH processor will
 251         * happen, so need to go through init sequence again.
 252         */
 253        if (dev->resume_flag)
 254                ish_init(dev);
 255}
 256
 257/**
 258 * ish_suspend() - ISH suspend callback
 259 * @device:     device pointer
 260 *
 261 * ISH suspend callback
 262 *
 263 * Return: 0 to the pm core
 264 */
 265static int ish_suspend(struct device *device)
 266{
 267        struct pci_dev *pdev = to_pci_dev(device);
 268        struct ishtp_device *dev = pci_get_drvdata(pdev);
 269
 270        enable_irq_wake(pdev->irq);
 271        /*
 272         * If previous suspend hasn't been asnwered then ISH is likely dead,
 273         * don't attempt nested notification
 274         */
 275        if (dev->suspend_flag)
 276                return  0;
 277
 278        dev->resume_flag = 0;
 279        dev->suspend_flag = 1;
 280        ishtp_send_suspend(dev);
 281
 282        /* 25 ms should be enough for live ISH to flush all IPC buf */
 283        if (dev->suspend_flag)
 284                wait_event_interruptible_timeout(dev->suspend_wait,
 285                                                 !dev->suspend_flag,
 286                                                  msecs_to_jiffies(25));
 287
 288        return 0;
 289}
 290
 291static DECLARE_WORK(resume_work, ish_resume_handler);
 292/**
 293 * ish_resume() - ISH resume callback
 294 * @device:     device pointer
 295 *
 296 * ISH resume callback
 297 *
 298 * Return: 0 to the pm core
 299 */
 300static int ish_resume(struct device *device)
 301{
 302        struct pci_dev *pdev = to_pci_dev(device);
 303        struct ishtp_device *dev = pci_get_drvdata(pdev);
 304
 305        ish_resume_device = device;
 306        dev->resume_flag = 1;
 307
 308        disable_irq_wake(pdev->irq);
 309        schedule_work(&resume_work);
 310
 311        return 0;
 312}
 313
 314static const struct dev_pm_ops ish_pm_ops = {
 315        .suspend = ish_suspend,
 316        .resume = ish_resume,
 317};
 318#define ISHTP_ISH_PM_OPS        (&ish_pm_ops)
 319#else
 320#define ISHTP_ISH_PM_OPS        NULL
 321#endif /* CONFIG_PM */
 322
 323static struct pci_driver ish_driver = {
 324        .name = KBUILD_MODNAME,
 325        .id_table = ish_pci_tbl,
 326        .probe = ish_probe,
 327        .remove = ish_remove,
 328        .driver.pm = ISHTP_ISH_PM_OPS,
 329};
 330
 331module_pci_driver(ish_driver);
 332
 333/* Original author */
 334MODULE_AUTHOR("Daniel Drubin <daniel.drubin@intel.com>");
 335/* Adoption to upstream Linux kernel */
 336MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
 337
 338MODULE_DESCRIPTION("Intel(R) Integrated Sensor Hub PCI Device Driver");
 339MODULE_LICENSE("GPL");
 340