| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright (c) 2023, Intel Corporation.
- * Intel Visual Sensing Controller Interface Linux driver
- */
- #include <linux/align.h>
- #include <linux/cache.h>
- #include <linux/cleanup.h>
- #include <linux/iopoll.h>
- #include <linux/list.h>
- #include <linux/mei.h>
- #include <linux/module.h>
- #include <linux/mutex.h>
- #include <linux/overflow.h>
- #include <linux/platform_device.h>
- #include <linux/pm_runtime.h>
- #include <linux/timekeeping.h>
- #include <linux/types.h>
- #include <asm-generic/bug.h>
- #include <linux/unaligned.h>
- #include "mei_dev.h"
- #include "vsc-tp.h"
- #define MEI_VSC_DRV_NAME "intel_vsc"
- #define MEI_VSC_MAX_MSG_SIZE 512
- #define MEI_VSC_POLL_DELAY_US (100 * USEC_PER_MSEC)
- #define MEI_VSC_POLL_TIMEOUT_US (400 * USEC_PER_MSEC)
- #define mei_dev_to_vsc_hw(dev) ((struct mei_vsc_hw *)((dev)->hw))
- struct mei_vsc_host_timestamp {
- u64 realtime;
- u64 boottime;
- };
- struct mei_vsc_hw {
- struct vsc_tp *tp;
- bool fw_ready;
- bool host_ready;
- atomic_t write_lock_cnt;
- u32 rx_len;
- u32 rx_hdr;
- /* buffer for tx */
- char tx_buf[MEI_VSC_MAX_MSG_SIZE + sizeof(struct mei_msg_hdr)] ____cacheline_aligned;
- /* buffer for rx */
- char rx_buf[MEI_VSC_MAX_MSG_SIZE + sizeof(struct mei_msg_hdr)] ____cacheline_aligned;
- };
- static int mei_vsc_read_helper(struct mei_vsc_hw *hw, u8 *buf,
- u32 max_len)
- {
- struct mei_vsc_host_timestamp ts = {
- .realtime = ktime_to_ns(ktime_get_real()),
- .boottime = ktime_to_ns(ktime_get_boottime()),
- };
- return vsc_tp_xfer(hw->tp, VSC_TP_CMD_READ, &ts, sizeof(ts),
- buf, max_len);
- }
- static int mei_vsc_write_helper(struct mei_vsc_hw *hw, u8 *buf, u32 len)
- {
- u8 status;
- return vsc_tp_xfer(hw->tp, VSC_TP_CMD_WRITE, buf, len, &status,
- sizeof(status));
- }
- static int mei_vsc_fw_status(struct mei_device *mei_dev,
- struct mei_fw_status *fw_status)
- {
- if (!fw_status)
- return -EINVAL;
- fw_status->count = 0;
- return 0;
- }
- static inline enum mei_pg_state mei_vsc_pg_state(struct mei_device *mei_dev)
- {
- return MEI_PG_OFF;
- }
- static void mei_vsc_intr_enable(struct mei_device *mei_dev)
- {
- struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
- vsc_tp_intr_enable(hw->tp);
- }
- static void mei_vsc_intr_disable(struct mei_device *mei_dev)
- {
- struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
- vsc_tp_intr_disable(hw->tp);
- }
- /* mei framework requires this ops */
- static void mei_vsc_intr_clear(struct mei_device *mei_dev)
- {
- }
- /* wait for pending irq handler */
- static void mei_vsc_synchronize_irq(struct mei_device *mei_dev)
- {
- struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
- vsc_tp_intr_synchronize(hw->tp);
- }
- static int mei_vsc_hw_config(struct mei_device *mei_dev)
- {
- return 0;
- }
- static bool mei_vsc_host_is_ready(struct mei_device *mei_dev)
- {
- struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
- return hw->host_ready;
- }
- static bool mei_vsc_hw_is_ready(struct mei_device *mei_dev)
- {
- struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
- return hw->fw_ready;
- }
- static int mei_vsc_hw_start(struct mei_device *mei_dev)
- {
- struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
- int ret, rlen;
- u8 buf;
- hw->host_ready = true;
- vsc_tp_intr_enable(hw->tp);
- ret = read_poll_timeout(mei_vsc_read_helper, rlen,
- rlen >= 0, MEI_VSC_POLL_DELAY_US,
- MEI_VSC_POLL_TIMEOUT_US, true,
- hw, &buf, sizeof(buf));
- if (ret) {
- dev_err(&mei_dev->dev, "wait fw ready failed: %d\n", ret);
- return ret;
- }
- hw->fw_ready = true;
- return 0;
- }
- static bool mei_vsc_hbuf_is_ready(struct mei_device *mei_dev)
- {
- struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
- return atomic_read(&hw->write_lock_cnt) == 0;
- }
- static int mei_vsc_hbuf_empty_slots(struct mei_device *mei_dev)
- {
- return MEI_VSC_MAX_MSG_SIZE / MEI_SLOT_SIZE;
- }
- static u32 mei_vsc_hbuf_depth(const struct mei_device *mei_dev)
- {
- return MEI_VSC_MAX_MSG_SIZE / MEI_SLOT_SIZE;
- }
- static int mei_vsc_write(struct mei_device *mei_dev,
- const void *hdr, size_t hdr_len,
- const void *data, size_t data_len)
- {
- struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
- char *buf = hw->tx_buf;
- int ret;
- if (WARN_ON(!hdr || !IS_ALIGNED(hdr_len, 4)))
- return -EINVAL;
- if (!data || data_len > MEI_VSC_MAX_MSG_SIZE)
- return -EINVAL;
- atomic_inc(&hw->write_lock_cnt);
- memcpy(buf, hdr, hdr_len);
- memcpy(buf + hdr_len, data, data_len);
- ret = mei_vsc_write_helper(hw, buf, hdr_len + data_len);
- atomic_dec_if_positive(&hw->write_lock_cnt);
- return ret < 0 ? ret : 0;
- }
- static inline u32 mei_vsc_read(const struct mei_device *mei_dev)
- {
- struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
- int ret;
- ret = mei_vsc_read_helper(hw, hw->rx_buf, sizeof(hw->rx_buf));
- if (ret < 0 || ret < sizeof(u32))
- return 0;
- hw->rx_len = ret;
- hw->rx_hdr = get_unaligned_le32(hw->rx_buf);
- return hw->rx_hdr;
- }
- static int mei_vsc_count_full_read_slots(struct mei_device *mei_dev)
- {
- return MEI_VSC_MAX_MSG_SIZE / MEI_SLOT_SIZE;
- }
- static int mei_vsc_read_slots(struct mei_device *mei_dev, unsigned char *buf,
- unsigned long len)
- {
- struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
- struct mei_msg_hdr *hdr;
- hdr = (struct mei_msg_hdr *)&hw->rx_hdr;
- if (len != hdr->length || hdr->length + sizeof(*hdr) != hw->rx_len)
- return -EINVAL;
- memcpy(buf, hw->rx_buf + sizeof(*hdr), len);
- return 0;
- }
- static bool mei_vsc_pg_in_transition(struct mei_device *mei_dev)
- {
- return mei_dev->pg_event >= MEI_PG_EVENT_WAIT &&
- mei_dev->pg_event <= MEI_PG_EVENT_INTR_WAIT;
- }
- static bool mei_vsc_pg_is_enabled(struct mei_device *mei_dev)
- {
- return false;
- }
- static int mei_vsc_hw_reset(struct mei_device *mei_dev, bool intr_enable)
- {
- struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
- vsc_tp_reset(hw->tp);
- if (!intr_enable)
- return 0;
- return vsc_tp_init(hw->tp, mei_dev->parent);
- }
- static const struct mei_hw_ops mei_vsc_hw_ops = {
- .fw_status = mei_vsc_fw_status,
- .pg_state = mei_vsc_pg_state,
- .host_is_ready = mei_vsc_host_is_ready,
- .hw_is_ready = mei_vsc_hw_is_ready,
- .hw_reset = mei_vsc_hw_reset,
- .hw_config = mei_vsc_hw_config,
- .hw_start = mei_vsc_hw_start,
- .pg_in_transition = mei_vsc_pg_in_transition,
- .pg_is_enabled = mei_vsc_pg_is_enabled,
- .intr_clear = mei_vsc_intr_clear,
- .intr_enable = mei_vsc_intr_enable,
- .intr_disable = mei_vsc_intr_disable,
- .synchronize_irq = mei_vsc_synchronize_irq,
- .hbuf_free_slots = mei_vsc_hbuf_empty_slots,
- .hbuf_is_ready = mei_vsc_hbuf_is_ready,
- .hbuf_depth = mei_vsc_hbuf_depth,
- .write = mei_vsc_write,
- .rdbuf_full_slots = mei_vsc_count_full_read_slots,
- .read_hdr = mei_vsc_read,
- .read = mei_vsc_read_slots,
- };
- static void mei_vsc_event_cb(void *context)
- {
- struct mei_device *mei_dev = context;
- struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
- struct list_head cmpl_list;
- s32 slots;
- int ret;
- if (mei_dev->dev_state == MEI_DEV_RESETTING ||
- mei_dev->dev_state == MEI_DEV_INITIALIZING)
- return;
- INIT_LIST_HEAD(&cmpl_list);
- guard(mutex)(&mei_dev->device_lock);
- while (vsc_tp_need_read(hw->tp)) {
- /* check slots available for reading */
- slots = mei_count_full_read_slots(mei_dev);
- ret = mei_irq_read_handler(mei_dev, &cmpl_list, &slots);
- if (ret) {
- if (ret != -ENODATA) {
- if (mei_dev->dev_state != MEI_DEV_RESETTING &&
- mei_dev->dev_state != MEI_DEV_POWER_DOWN)
- schedule_work(&mei_dev->reset_work);
- }
- return;
- }
- }
- mei_dev->hbuf_is_ready = mei_hbuf_is_ready(mei_dev);
- ret = mei_irq_write_handler(mei_dev, &cmpl_list);
- if (ret)
- dev_err(&mei_dev->dev, "dispatch write request failed: %d\n", ret);
- mei_dev->hbuf_is_ready = mei_hbuf_is_ready(mei_dev);
- mei_irq_compl_handler(mei_dev, &cmpl_list);
- }
- static int mei_vsc_probe(struct platform_device *pdev)
- {
- struct device *dev = &pdev->dev;
- struct mei_device *mei_dev;
- struct mei_vsc_hw *hw;
- struct vsc_tp *tp;
- int ret;
- tp = *(struct vsc_tp **)dev_get_platdata(dev);
- if (!tp)
- return dev_err_probe(dev, -ENODEV, "no platform data\n");
- mei_dev = kzalloc(size_add(sizeof(*mei_dev), sizeof(*hw)), GFP_KERNEL);
- if (!mei_dev)
- return -ENOMEM;
- mei_device_init(mei_dev, dev, false, &mei_vsc_hw_ops);
- mei_dev->fw_f_fw_ver_supported = 0;
- mei_dev->kind = "ivsc";
- hw = mei_dev_to_vsc_hw(mei_dev);
- atomic_set(&hw->write_lock_cnt, 0);
- hw->tp = tp;
- platform_set_drvdata(pdev, mei_dev);
- vsc_tp_register_event_cb(tp, mei_vsc_event_cb, mei_dev);
- ret = mei_register(mei_dev, dev);
- if (ret)
- goto err;
- ret = mei_start(mei_dev);
- if (ret) {
- dev_err_probe(dev, ret, "init hw failed\n");
- goto err;
- }
- pm_runtime_enable(mei_dev->parent);
- return 0;
- err:
- mei_cancel_work(mei_dev);
- vsc_tp_register_event_cb(tp, NULL, NULL);
- mei_disable_interrupts(mei_dev);
- mei_deregister(mei_dev);
- return ret;
- }
- static void mei_vsc_remove(struct platform_device *pdev)
- {
- struct mei_device *mei_dev = platform_get_drvdata(pdev);
- struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
- pm_runtime_disable(mei_dev->parent);
- mei_stop(mei_dev);
- vsc_tp_register_event_cb(hw->tp, NULL, NULL);
- mei_disable_interrupts(mei_dev);
- mei_deregister(mei_dev);
- }
- static int mei_vsc_suspend(struct device *dev)
- {
- struct mei_device *mei_dev;
- int ret = 0;
- mei_dev = dev_get_drvdata(dev);
- if (!mei_dev)
- return -ENODEV;
- mutex_lock(&mei_dev->device_lock);
- if (!mei_write_is_idle(mei_dev))
- ret = -EAGAIN;
- mutex_unlock(&mei_dev->device_lock);
- return ret;
- }
- static int mei_vsc_resume(struct device *dev)
- {
- struct mei_device *mei_dev;
- mei_dev = dev_get_drvdata(dev);
- if (!mei_dev)
- return -ENODEV;
- return 0;
- }
- static DEFINE_SIMPLE_DEV_PM_OPS(mei_vsc_pm_ops, mei_vsc_suspend, mei_vsc_resume);
- static const struct platform_device_id mei_vsc_id_table[] = {
- { MEI_VSC_DRV_NAME },
- { /* sentinel */ }
- };
- MODULE_DEVICE_TABLE(platform, mei_vsc_id_table);
- static struct platform_driver mei_vsc_drv = {
- .probe = mei_vsc_probe,
- .remove = mei_vsc_remove,
- .id_table = mei_vsc_id_table,
- .driver = {
- .name = MEI_VSC_DRV_NAME,
- .pm = &mei_vsc_pm_ops,
- .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- },
- };
- module_platform_driver(mei_vsc_drv);
- MODULE_AUTHOR("Wentong Wu <wentong.wu@intel.com>");
- MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@intel.com>");
- MODULE_DESCRIPTION("Intel Visual Sensing Controller Interface");
- MODULE_LICENSE("GPL");
- MODULE_IMPORT_NS("VSC_TP");
|