| .. | .. |
|---|
| 1 | | -/* |
|---|
| 2 | | - * ChromeOS EC multi-function device (SPI) |
|---|
| 3 | | - * |
|---|
| 4 | | - * Copyright (C) 2012 Google, Inc |
|---|
| 5 | | - * |
|---|
| 6 | | - * This software is licensed under the terms of the GNU General Public |
|---|
| 7 | | - * License version 2, as published by the Free Software Foundation, and |
|---|
| 8 | | - * may be copied, distributed, and modified under those terms. |
|---|
| 9 | | - * |
|---|
| 10 | | - * This program is distributed in the hope that it will be useful, |
|---|
| 11 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 12 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 13 | | - * GNU General Public License for more details. |
|---|
| 14 | | - */ |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
|---|
| 2 | +// SPI interface for ChromeOS Embedded Controller |
|---|
| 3 | +// |
|---|
| 4 | +// Copyright (C) 2012 Google, Inc |
|---|
| 15 | 5 | |
|---|
| 16 | 6 | #include <linux/delay.h> |
|---|
| 17 | 7 | #include <linux/kernel.h> |
|---|
| 18 | 8 | #include <linux/module.h> |
|---|
| 19 | | -#include <linux/mfd/cros_ec.h> |
|---|
| 20 | | -#include <linux/mfd/cros_ec_commands.h> |
|---|
| 21 | 9 | #include <linux/of.h> |
|---|
| 10 | +#include <linux/platform_data/cros_ec_commands.h> |
|---|
| 11 | +#include <linux/platform_data/cros_ec_proto.h> |
|---|
| 22 | 12 | #include <linux/platform_device.h> |
|---|
| 23 | 13 | #include <linux/slab.h> |
|---|
| 24 | 14 | #include <linux/spi/spi.h> |
|---|
| 15 | +#include <uapi/linux/sched/types.h> |
|---|
| 25 | 16 | |
|---|
| 17 | +#include "cros_ec.h" |
|---|
| 26 | 18 | |
|---|
| 27 | 19 | /* The header byte, which follows the preamble */ |
|---|
| 28 | 20 | #define EC_MSG_HEADER 0xec |
|---|
| .. | .. |
|---|
| 77 | 69 | * is sent when we want to turn on CS at the start of a transaction. |
|---|
| 78 | 70 | * @end_of_msg_delay: used to set the delay_usecs on the spi_transfer that |
|---|
| 79 | 71 | * is sent when we want to turn off CS at the end of a transaction. |
|---|
| 72 | + * @high_pri_worker: Used to schedule high priority work. |
|---|
| 80 | 73 | */ |
|---|
| 81 | 74 | struct cros_ec_spi { |
|---|
| 82 | 75 | struct spi_device *spi; |
|---|
| 83 | 76 | s64 last_transfer_ns; |
|---|
| 84 | 77 | unsigned int start_of_msg_delay; |
|---|
| 85 | 78 | unsigned int end_of_msg_delay; |
|---|
| 79 | + struct kthread_worker *high_pri_worker; |
|---|
| 80 | +}; |
|---|
| 81 | + |
|---|
| 82 | +typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev, |
|---|
| 83 | + struct cros_ec_command *ec_msg); |
|---|
| 84 | + |
|---|
| 85 | +/** |
|---|
| 86 | + * struct cros_ec_xfer_work_params - params for our high priority workers |
|---|
| 87 | + * |
|---|
| 88 | + * @work: The work_struct needed to queue work |
|---|
| 89 | + * @fn: The function to use to transfer |
|---|
| 90 | + * @ec_dev: ChromeOS EC device |
|---|
| 91 | + * @ec_msg: Message to transfer |
|---|
| 92 | + * @ret: The return value of the function |
|---|
| 93 | + */ |
|---|
| 94 | + |
|---|
| 95 | +struct cros_ec_xfer_work_params { |
|---|
| 96 | + struct kthread_work work; |
|---|
| 97 | + cros_ec_xfer_fn_t fn; |
|---|
| 98 | + struct cros_ec_device *ec_dev; |
|---|
| 99 | + struct cros_ec_command *ec_msg; |
|---|
| 100 | + int ret; |
|---|
| 86 | 101 | }; |
|---|
| 87 | 102 | |
|---|
| 88 | 103 | static void debug_packet(struct device *dev, const char *name, u8 *ptr, |
|---|
| .. | .. |
|---|
| 112 | 127 | */ |
|---|
| 113 | 128 | spi_message_init(&msg); |
|---|
| 114 | 129 | memset(&trans, 0, sizeof(trans)); |
|---|
| 115 | | - trans.delay_usecs = ec_spi->end_of_msg_delay; |
|---|
| 130 | + trans.delay.value = ec_spi->end_of_msg_delay; |
|---|
| 131 | + trans.delay.unit = SPI_DELAY_UNIT_USECS; |
|---|
| 116 | 132 | spi_message_add_tail(&trans, &msg); |
|---|
| 117 | 133 | |
|---|
| 118 | 134 | ret = spi_sync_locked(ec_spi->spi, &msg); |
|---|
| .. | .. |
|---|
| 132 | 148 | * receive_n_bytes - receive n bytes from the EC. |
|---|
| 133 | 149 | * |
|---|
| 134 | 150 | * Assumes buf is a pointer into the ec_dev->din buffer |
|---|
| 151 | + * |
|---|
| 152 | + * @ec_dev: ChromeOS EC device. |
|---|
| 153 | + * @buf: Pointer to the buffer receiving the data. |
|---|
| 154 | + * @n: Number of bytes received. |
|---|
| 135 | 155 | */ |
|---|
| 136 | 156 | static int receive_n_bytes(struct cros_ec_device *ec_dev, u8 *buf, int n) |
|---|
| 137 | 157 | { |
|---|
| .. | .. |
|---|
| 360 | 380 | } |
|---|
| 361 | 381 | |
|---|
| 362 | 382 | /** |
|---|
| 363 | | - * cros_ec_pkt_xfer_spi - Transfer a packet over SPI and receive the reply |
|---|
| 383 | + * do_cros_ec_pkt_xfer_spi - Transfer a packet over SPI and receive the reply |
|---|
| 364 | 384 | * |
|---|
| 365 | 385 | * @ec_dev: ChromeOS EC device |
|---|
| 366 | 386 | * @ec_msg: Message to transfer |
|---|
| 367 | 387 | */ |
|---|
| 368 | | -static int cros_ec_pkt_xfer_spi(struct cros_ec_device *ec_dev, |
|---|
| 369 | | - struct cros_ec_command *ec_msg) |
|---|
| 388 | +static int do_cros_ec_pkt_xfer_spi(struct cros_ec_device *ec_dev, |
|---|
| 389 | + struct cros_ec_command *ec_msg) |
|---|
| 370 | 390 | { |
|---|
| 371 | 391 | struct ec_host_response *response; |
|---|
| 372 | 392 | struct cros_ec_spi *ec_spi = ec_dev->priv; |
|---|
| .. | .. |
|---|
| 401 | 421 | spi_message_init(&msg); |
|---|
| 402 | 422 | if (ec_spi->start_of_msg_delay) { |
|---|
| 403 | 423 | memset(&trans_delay, 0, sizeof(trans_delay)); |
|---|
| 404 | | - trans_delay.delay_usecs = ec_spi->start_of_msg_delay; |
|---|
| 424 | + trans_delay.delay.value = ec_spi->start_of_msg_delay; |
|---|
| 425 | + trans_delay.delay.unit = SPI_DELAY_UNIT_USECS; |
|---|
| 405 | 426 | spi_message_add_tail(&trans_delay, &msg); |
|---|
| 406 | 427 | } |
|---|
| 407 | 428 | |
|---|
| .. | .. |
|---|
| 503 | 524 | } |
|---|
| 504 | 525 | |
|---|
| 505 | 526 | /** |
|---|
| 506 | | - * cros_ec_cmd_xfer_spi - Transfer a message over SPI and receive the reply |
|---|
| 527 | + * do_cros_ec_cmd_xfer_spi - Transfer a message over SPI and receive the reply |
|---|
| 507 | 528 | * |
|---|
| 508 | 529 | * @ec_dev: ChromeOS EC device |
|---|
| 509 | 530 | * @ec_msg: Message to transfer |
|---|
| 510 | 531 | */ |
|---|
| 511 | | -static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, |
|---|
| 512 | | - struct cros_ec_command *ec_msg) |
|---|
| 532 | +static int do_cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, |
|---|
| 533 | + struct cros_ec_command *ec_msg) |
|---|
| 513 | 534 | { |
|---|
| 514 | 535 | struct cros_ec_spi *ec_spi = ec_dev->priv; |
|---|
| 515 | 536 | struct spi_transfer trans; |
|---|
| .. | .. |
|---|
| 621 | 642 | return ret; |
|---|
| 622 | 643 | } |
|---|
| 623 | 644 | |
|---|
| 645 | +static void cros_ec_xfer_high_pri_work(struct kthread_work *work) |
|---|
| 646 | +{ |
|---|
| 647 | + struct cros_ec_xfer_work_params *params; |
|---|
| 648 | + |
|---|
| 649 | + params = container_of(work, struct cros_ec_xfer_work_params, work); |
|---|
| 650 | + params->ret = params->fn(params->ec_dev, params->ec_msg); |
|---|
| 651 | +} |
|---|
| 652 | + |
|---|
| 653 | +static int cros_ec_xfer_high_pri(struct cros_ec_device *ec_dev, |
|---|
| 654 | + struct cros_ec_command *ec_msg, |
|---|
| 655 | + cros_ec_xfer_fn_t fn) |
|---|
| 656 | +{ |
|---|
| 657 | + struct cros_ec_spi *ec_spi = ec_dev->priv; |
|---|
| 658 | + struct cros_ec_xfer_work_params params = { |
|---|
| 659 | + .work = KTHREAD_WORK_INIT(params.work, |
|---|
| 660 | + cros_ec_xfer_high_pri_work), |
|---|
| 661 | + .ec_dev = ec_dev, |
|---|
| 662 | + .ec_msg = ec_msg, |
|---|
| 663 | + .fn = fn, |
|---|
| 664 | + }; |
|---|
| 665 | + |
|---|
| 666 | + /* |
|---|
| 667 | + * This looks a bit ridiculous. Why do the work on a |
|---|
| 668 | + * different thread if we're just going to block waiting for |
|---|
| 669 | + * the thread to finish? The key here is that the thread is |
|---|
| 670 | + * running at high priority but the calling context might not |
|---|
| 671 | + * be. We need to be at high priority to avoid getting |
|---|
| 672 | + * context switched out for too long and the EC giving up on |
|---|
| 673 | + * the transfer. |
|---|
| 674 | + */ |
|---|
| 675 | + kthread_queue_work(ec_spi->high_pri_worker, ¶ms.work); |
|---|
| 676 | + kthread_flush_work(¶ms.work); |
|---|
| 677 | + |
|---|
| 678 | + return params.ret; |
|---|
| 679 | +} |
|---|
| 680 | + |
|---|
| 681 | +static int cros_ec_pkt_xfer_spi(struct cros_ec_device *ec_dev, |
|---|
| 682 | + struct cros_ec_command *ec_msg) |
|---|
| 683 | +{ |
|---|
| 684 | + return cros_ec_xfer_high_pri(ec_dev, ec_msg, do_cros_ec_pkt_xfer_spi); |
|---|
| 685 | +} |
|---|
| 686 | + |
|---|
| 687 | +static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, |
|---|
| 688 | + struct cros_ec_command *ec_msg) |
|---|
| 689 | +{ |
|---|
| 690 | + return cros_ec_xfer_high_pri(ec_dev, ec_msg, do_cros_ec_cmd_xfer_spi); |
|---|
| 691 | +} |
|---|
| 692 | + |
|---|
| 624 | 693 | static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev) |
|---|
| 625 | 694 | { |
|---|
| 626 | 695 | struct device_node *np = dev->of_node; |
|---|
| .. | .. |
|---|
| 636 | 705 | ec_spi->end_of_msg_delay = val; |
|---|
| 637 | 706 | } |
|---|
| 638 | 707 | |
|---|
| 708 | +static void cros_ec_spi_high_pri_release(void *worker) |
|---|
| 709 | +{ |
|---|
| 710 | + kthread_destroy_worker(worker); |
|---|
| 711 | +} |
|---|
| 712 | + |
|---|
| 713 | +static int cros_ec_spi_devm_high_pri_alloc(struct device *dev, |
|---|
| 714 | + struct cros_ec_spi *ec_spi) |
|---|
| 715 | +{ |
|---|
| 716 | + int err; |
|---|
| 717 | + |
|---|
| 718 | + ec_spi->high_pri_worker = |
|---|
| 719 | + kthread_create_worker(0, "cros_ec_spi_high_pri"); |
|---|
| 720 | + |
|---|
| 721 | + if (IS_ERR(ec_spi->high_pri_worker)) { |
|---|
| 722 | + err = PTR_ERR(ec_spi->high_pri_worker); |
|---|
| 723 | + dev_err(dev, "Can't create cros_ec high pri worker: %d\n", err); |
|---|
| 724 | + return err; |
|---|
| 725 | + } |
|---|
| 726 | + |
|---|
| 727 | + err = devm_add_action_or_reset(dev, cros_ec_spi_high_pri_release, |
|---|
| 728 | + ec_spi->high_pri_worker); |
|---|
| 729 | + if (err) |
|---|
| 730 | + return err; |
|---|
| 731 | + |
|---|
| 732 | + sched_set_fifo(ec_spi->high_pri_worker->task); |
|---|
| 733 | + |
|---|
| 734 | + return 0; |
|---|
| 735 | +} |
|---|
| 736 | + |
|---|
| 639 | 737 | static int cros_ec_spi_probe(struct spi_device *spi) |
|---|
| 640 | 738 | { |
|---|
| 641 | 739 | struct device *dev = &spi->dev; |
|---|
| .. | .. |
|---|
| 644 | 742 | int err; |
|---|
| 645 | 743 | |
|---|
| 646 | 744 | spi->bits_per_word = 8; |
|---|
| 647 | | - spi->mode = SPI_MODE_0; |
|---|
| 745 | + spi->rt = true; |
|---|
| 648 | 746 | err = spi_setup(spi); |
|---|
| 649 | 747 | if (err < 0) |
|---|
| 650 | 748 | return err; |
|---|
| .. | .. |
|---|
| 674 | 772 | |
|---|
| 675 | 773 | ec_spi->last_transfer_ns = ktime_get_ns(); |
|---|
| 676 | 774 | |
|---|
| 775 | + err = cros_ec_spi_devm_high_pri_alloc(dev, ec_spi); |
|---|
| 776 | + if (err) |
|---|
| 777 | + return err; |
|---|
| 778 | + |
|---|
| 677 | 779 | err = cros_ec_register(ec_dev); |
|---|
| 678 | 780 | if (err) { |
|---|
| 679 | 781 | dev_err(dev, "cannot register EC\n"); |
|---|
| .. | .. |
|---|
| 687 | 789 | |
|---|
| 688 | 790 | static int cros_ec_spi_remove(struct spi_device *spi) |
|---|
| 689 | 791 | { |
|---|
| 690 | | - struct cros_ec_device *ec_dev; |
|---|
| 792 | + struct cros_ec_device *ec_dev = spi_get_drvdata(spi); |
|---|
| 691 | 793 | |
|---|
| 692 | | - ec_dev = spi_get_drvdata(spi); |
|---|
| 693 | | - cros_ec_remove(ec_dev); |
|---|
| 694 | | - |
|---|
| 695 | | - return 0; |
|---|
| 794 | + return cros_ec_unregister(ec_dev); |
|---|
| 696 | 795 | } |
|---|
| 697 | 796 | |
|---|
| 698 | 797 | #ifdef CONFIG_PM_SLEEP |
|---|
| .. | .. |
|---|
| 729 | 828 | static struct spi_driver cros_ec_driver_spi = { |
|---|
| 730 | 829 | .driver = { |
|---|
| 731 | 830 | .name = "cros-ec-spi", |
|---|
| 732 | | - .of_match_table = of_match_ptr(cros_ec_spi_of_match), |
|---|
| 831 | + .of_match_table = cros_ec_spi_of_match, |
|---|
| 733 | 832 | .pm = &cros_ec_spi_pm_ops, |
|---|
| 734 | 833 | }, |
|---|
| 735 | 834 | .probe = cros_ec_spi_probe, |
|---|
| .. | .. |
|---|
| 740 | 839 | module_spi_driver(cros_ec_driver_spi); |
|---|
| 741 | 840 | |
|---|
| 742 | 841 | MODULE_LICENSE("GPL v2"); |
|---|
| 743 | | -MODULE_DESCRIPTION("ChromeOS EC multi function device (SPI)"); |
|---|
| 842 | +MODULE_DESCRIPTION("SPI interface for ChromeOS Embedded Controller"); |
|---|