root/drivers/hid/intel-ish-hid/ipc/pci-ish.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. __printf
  2. ish_init
  3. ish_should_enter_d0i3
  4. ish_probe
  5. ish_remove
  6. ish_resume_handler
  7. ish_suspend
  8. ish_resume

   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 
  25 static 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 };
  42 MODULE_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  */
  51 static __printf(2, 3)
  52 void 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  */
  76 static 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 
  97 static 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 
 104 static 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  */
 118 static 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  */
 190 static 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 
 198 static 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  */
 212 static 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  */
 254 static 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 
 300 static __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  */
 309 static 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 
 322 static SIMPLE_DEV_PM_OPS(ish_pm_ops, ish_suspend, ish_resume);
 323 
 324 static 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 
 332 module_pci_driver(ish_driver);
 333 
 334 /* Original author */
 335 MODULE_AUTHOR("Daniel Drubin <daniel.drubin@intel.com>");
 336 /* Adoption to upstream Linux kernel */
 337 MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
 338 
 339 MODULE_DESCRIPTION("Intel(R) Integrated Sensor Hub PCI Device Driver");
 340 MODULE_LICENSE("GPL");

/* [<][>][^][v][top][bottom][index][help] */