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