From patchwork Wed Jun 13 14:37:17 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Jonas Mark (BT-FIR/ENG1-Grb)" X-Patchwork-Id: 928905 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=de.bosch.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=de.bosch.com header.i=@de.bosch.com header.b="RLkj8dx+"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 415Tsy4BQvz9s19 for ; Thu, 14 Jun 2018 00:39:34 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S935847AbeFMOiS (ORCPT ); Wed, 13 Jun 2018 10:38:18 -0400 Received: from de-out1.bosch-org.com ([139.15.230.186]:33598 "EHLO de-out1.bosch-org.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S935691AbeFMOiP (ORCPT ); Wed, 13 Jun 2018 10:38:15 -0400 Received: from fe0vm1650.rbesz01.com (unknown [139.15.230.188]) by fe0vms0187.rbdmz01.com (Postfix) with ESMTPS id 415TrP52QXz1XLFZG; Wed, 13 Jun 2018 16:38:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=de.bosch.com; s=2015-01-21; t=1528900693; bh=xKWvSJAExCFYwQPkF9cpeARYs1KgGM4+MQ0/Lt2hetU=; l=10; h=From:From:Reply-To:Sender; b=RLkj8dx+dOJ3KDBVIWL0VNQNBYwISv4mQy9d4KwGc8ynMzpKRjtlABy+y4ATL9iWM xbLameKnDxnNoSicsHy+5SMefQ3MyrHUkJFfJ/+eeF6eUCaLhE4EIg3NXlRcdjoSAL olsBIml6L02CqNXNhk/VX+nMGhiFeGjqsKPGSTAI= Received: from fe0vm7918.rbesz01.com (unknown [10.58.172.176]) by fe0vm1650.rbesz01.com (Postfix) with ESMTPS id 415TrP4cx2z1Cb; Wed, 13 Jun 2018 16:38:13 +0200 (CEST) X-AuditID: 0a3aad10-40fff70000000ee7-f3-5b212c637049 Received: from fe0vm1652.rbesz01.com ( [10.58.173.29]) (using TLS with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by fe0vm7918.rbesz01.com (SMG Outbound) with SMTP id C4.7E.03815.36C212B5; Wed, 13 Jun 2018 16:38:27 +0200 (CEST) Received: from SI-HUB1000.de.bosch.com (si-hub1000.de.bosch.com [10.4.103.106]) by fe0vm1652.rbesz01.com (Postfix) with ESMTPS id 415TrP32Z6zBpBg; Wed, 13 Jun 2018 16:38:13 +0200 (CEST) Received: from luchador.grb-fir.grb.de.bosch.com (10.19.187.97) by SI-HUB1000.de.bosch.com (10.4.103.106) with Microsoft SMTP Server id 14.3.319.2; Wed, 13 Jun 2018 16:38:13 +0200 From: Mark Jonas To: Wolfgang Grandegger , Marc Kleine-Budde CC: , , , , , , , , , , Mark Jonas Subject: [PATCH v2 1/5] can: enable multi-queue for SocketCAN devices Date: Wed, 13 Jun 2018 16:37:17 +0200 Message-ID: <1528900641-18677-2-git-send-email-mark.jonas@de.bosch.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1528900641-18677-1-git-send-email-mark.jonas@de.bosch.com> References: <1528224240-30786-1-git-send-email-mark.jonas@de.bosch.com> <1528900641-18677-1-git-send-email-mark.jonas@de.bosch.com> MIME-Version: 1.0 X-Brightmail-Tracker: H4sIAAAAAAAAA22SfUwbdRzG+fWu7bVwelwp+9JZYKfLBugEFXcimpnMrP7DdIvGjRi9rkfb jALplWYQX+rmxsacgrDEdQ0jW8gmshkrho6ISHkJxbjsBRSGQ4kVy1tABrbb4vBKYe0f/vf8 vs/3c8/9njsCo3+UawhziY23lnDFjEyJK/Muap/Y9/iGwuym4Xh2sqYbsZ5ff8PY5uBJjL3R 7pKxX52tx9m+xmTWP4bY4an7crZp7LJ0m0LX4OzHdZedt+S6v6+PIt2h5R657tN/s3W33amv yvYq8w18sdnOW5988R2lqfvipKTMnXKg44ePMAc6rq5GCgKoZ2D5ahuqRkqCpj6XQF9DtSxy 6EAQOlEnjRzaEJxbWEBhREZlwki/BwvrJOp1OP3nETy8hFHfS2C04/jKkop6GWaDi/KwxqmN MOfuWdEktQNu1ZzHItmpMHLl2IpWUDr4tr1hhaWpwwiGet+I7CeC75QfD2uMAugKBLAa9LAz xnLGWI1I0ozURXy23bL1uRx2i1XPC5XZOVv2lVrcKNI05UFOn96LKAIxCeSptA2FtJSzCxUW L8olJIyaLKhJL6Qf0pcaKkycYHrbWl7MC4yGRHFxcbTqwVgo11vMgmAuLfEiIDAmiSwIiBxp 4CoqeWtpBPOi9QTOrCM/Nry7l6aMnI3fz/NlvHXNfZ4gGCCFLPEdEq28kT9QZC62rdmMNpKZ HOvExkoIhRc9TSSI2YrwI0ihjLMIZuMqnhLB6bVpFB1AWzXryL5MkaHCrqm85EGq5hGysFW8 iDrGiJJTaASJvanI98OBCeJPHM0D0qNy7aETV4dR6KkmkaFa4qHBs4Sg31OFQ8hRh8OlwBAO d2oncOgPtklhcfaeFK63O+VwbOaIAq4dnVPAknM5Huq+6yQhdNJFweC1X1QQcs8kQe1VRzLM fNmugcV7fVoY/CmghZ9bB1LB/6EjDfydwTRY+n0pHVzjoXTwdx1k4GzzJ5vANz22GboW5jeD b7IxAwKO1gzw1d/NgNtHz2VNiWVKxDJ7e8MfUrBxtv8pc3UavZ3GgSq/rt+5I2kga9C4/eb+ XP3hK0VV63dNu57FPqsNvmS4dP5G/u6M+Re2ffNa8ojlxNx8oJXuatl4xiS5MP/XH5qhvNnh cpnrNDedp51ombDfPbTb+EXK/fELj9m6O28WvKLYPvXBqP3RHsOePON7/7y5qecttDBeNba4 Kzct/w5tGFcfdDK4YOJyMjGrwP0H4pO/+14EAAA= Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Zhu Yi The existing SocketCAN implementation provides alloc_candev() to allocate a CAN device using a single Tx and Rx queue. This can lead to priority inversion in case the single Tx queue is already full with low priority messages and a high priority message needs to be sent while the bus is fully loaded with medium priority messages. This problem can be solved by using the existing multi-queue support of the network subsytem. The commit makes it possible to use multi-queue in the CAN subsystem in the same way it is used in the Ethernet subsystem by adding an alloc_candev_mqs() call and accompanying macros. With this support a CAN device can use multi-queue qdisc (e.g. mqprio) to avoid the aforementioned priority inversion. The exisiting functionality of alloc_candev() is the same as before. CAN devices need to have prioritized multiple hardware queues or are able to abort waiting for arbitration to make sensible use of multi-queues. Signed-off-by: Zhu Yi Signed-off-by: Mark Jonas Reviewed-by: Heiko Schocher --- drivers/net/can/dev.c | 8 +++++--- include/linux/can/dev.h | 7 ++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/net/can/dev.c b/drivers/net/can/dev.c index 365a8cc..ac8270c 100644 --- a/drivers/net/can/dev.c +++ b/drivers/net/can/dev.c @@ -702,7 +702,8 @@ EXPORT_SYMBOL_GPL(alloc_can_err_skb); /* * Allocate and setup space for the CAN network device */ -struct net_device *alloc_candev(int sizeof_priv, unsigned int echo_skb_max) +struct net_device *alloc_candev_mqs(int sizeof_priv, unsigned int echo_skb_max, + unsigned int txqs, unsigned int rxqs) { struct net_device *dev; struct can_priv *priv; @@ -714,7 +715,8 @@ struct net_device *alloc_candev(int sizeof_priv, unsigned int echo_skb_max) else size = sizeof_priv; - dev = alloc_netdev(size, "can%d", NET_NAME_UNKNOWN, can_setup); + dev = alloc_netdev_mqs(size, "can%d", NET_NAME_UNKNOWN, can_setup, + txqs, rxqs); if (!dev) return NULL; @@ -733,7 +735,7 @@ struct net_device *alloc_candev(int sizeof_priv, unsigned int echo_skb_max) return dev; } -EXPORT_SYMBOL_GPL(alloc_candev); +EXPORT_SYMBOL_GPL(alloc_candev_mqs); /* * Free space of the CAN network device diff --git a/include/linux/can/dev.h b/include/linux/can/dev.h index 61f1cf2..07b73d2 100644 --- a/include/linux/can/dev.h +++ b/include/linux/can/dev.h @@ -142,7 +142,12 @@ u8 can_dlc2len(u8 can_dlc); /* map the sanitized data length to an appropriate data length code */ u8 can_len2dlc(u8 len); -struct net_device *alloc_candev(int sizeof_priv, unsigned int echo_skb_max); +struct net_device *alloc_candev_mqs(int sizeof_priv, unsigned int echo_skb_max, + unsigned int txqs, unsigned int rxqs); +#define alloc_candev(sizeof_priv, echo_skb_max) \ + alloc_candev_mqs(sizeof_priv, echo_skb_max, 1, 1) +#define alloc_candev_mq(sizeof_priv, echo_skb_max, count) \ + alloc_candev_mqs(sizeof_priv, echo_skb_max, count, count) void free_candev(struct net_device *dev); /* a candev safe wrapper around netdev_priv */ From patchwork Wed Jun 13 14:37:18 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Jonas Mark (BT-FIR/ENG1-Grb)" X-Patchwork-Id: 928903 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=de.bosch.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=de.bosch.com header.i=@de.bosch.com header.b="L9Eexou2"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 415TsR5bxtz9s19 for ; Thu, 14 Jun 2018 00:39:07 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S935913AbeFMOit (ORCPT ); Wed, 13 Jun 2018 10:38:49 -0400 Received: from de-out1.bosch-org.com ([139.15.230.186]:33644 "EHLO de-out1.bosch-org.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S935816AbeFMOiS (ORCPT ); Wed, 13 Jun 2018 10:38:18 -0400 Received: from si0vm1948.rbesz01.com (unknown [139.15.230.188]) by fe0vms0187.rbdmz01.com (Postfix) with ESMTPS id 415TrR4VfFz1XLFZJ; Wed, 13 Jun 2018 16:38:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=de.bosch.com; s=2015-01-21; t=1528900695; bh=xKWvSJAExCFYwQPkF9cpeARYs1KgGM4+MQ0/Lt2hetU=; l=10; h=From:From:Reply-To:Sender; b=L9Eexou2SEqSzfQnYDeyVUSs/i2QSsgFVskCSKzJs0NJnsO/dHJXkVYYKXGJA0rMh 1Ct/cvyXHYNXI3UgR13fagg83L4oNbDslVVftKM77RBTfXZbIgA/pwLDERXdrQiBNS 3g0v+geWHxgNA4MAp0xGtZGOM4tUWyqMenyAFZGY= Received: from si0vm2083.rbesz01.com (unknown [10.58.172.176]) by si0vm1948.rbesz01.com (Postfix) with ESMTPS id 415TrR40N9z4fw; Wed, 13 Jun 2018 16:38:15 +0200 (CEST) X-AuditID: 0a3aad17-513ff70000000e32-d2-5b212c58af15 Received: from si0vm1949.rbesz01.com ( [10.58.173.29]) (using TLS with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by si0vm2083.rbesz01.com (SMG Outbound) with SMTP id 5B.4A.03634.85C212B5; Wed, 13 Jun 2018 16:38:16 +0200 (CEST) Received: from SI-HUB1000.de.bosch.com (si-hub1000.de.bosch.com [10.4.103.106]) by si0vm1949.rbesz01.com (Postfix) with ESMTPS id 415TrQ4bKNz6D44M5; Wed, 13 Jun 2018 16:38:14 +0200 (CEST) Received: from luchador.grb-fir.grb.de.bosch.com (10.19.187.97) by SI-HUB1000.de.bosch.com (10.4.103.106) with Microsoft SMTP Server id 14.3.319.2; Wed, 13 Jun 2018 16:38:14 +0200 From: Mark Jonas To: Wolfgang Grandegger , Marc Kleine-Budde CC: , , , , , , , , , , Mark Jonas Subject: [PATCH v2 2/5] spi: implement companion-spi driver Date: Wed, 13 Jun 2018 16:37:18 +0200 Message-ID: <1528900641-18677-3-git-send-email-mark.jonas@de.bosch.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1528900641-18677-1-git-send-email-mark.jonas@de.bosch.com> References: <1528224240-30786-1-git-send-email-mark.jonas@de.bosch.com> <1528900641-18677-1-git-send-email-mark.jonas@de.bosch.com> MIME-Version: 1.0 X-Brightmail-Tracker: H4sIAAAAAAAAA21Sf0wbZRjmu7u2R8Ox47rCuwpjO4M/wLEyNLm4xWiM4fYPm4nOjSbOw560 kbakV8ggi06GTAoK23BKIYVolugiYw513USWVcBONrJ0kqyCBec0HdQpMlamDry2sPYP//ny fu/zPO/zfc/3kThzRaUjzVaHaLcKlaxSTaif7M3ZtPuxjQZ90+wa7mbbt4jzTE7h3InI+zh3 9VyXkuv7qJ3gRnoyuRtBxF2bWVJxx4NnFU+n8m6Xj+DPun5S8XP+CcQfXB5S8a339Pz86fU7 lWXqbUax0lwj2jc/9Yra9N7Hy6qquZ+V+8b+9CkOoPFWhROlkkA/DpcOH0VOpCYZ+kMM3H9/ oohvziOojxwi4pszCBp/iaCoREnnQ8DnwaP1WvpF6Py1MUbC6UEMJr5pjpE09FaIdLliNUHn wVgoGPOj6BK47h9f8V4PgbGm2KBUmocvz7ljfIZ+G8H48K44PwMudtwgojVOA1wIhfA2tMaV BLmSoB6EnUBayayvsWzRc8WF9nJRqtMXFb5qs5xG8ayzPOj254IX0SRi06g3czcaGIVQI9Va vOgJEmO1VGnbBgOTXm4z1poEybTXXl0pSqyOQikpKYzmfluqLreYJclss3oRkDi7lioNyTrK KNTWiXZbXOZFD5AEm0W1GPeXMXSF4BBfF8Uq0b6KbiVJFiipQD5Dhl2sEPe9Zq50rMJsTtwz MxlJtsXIVC8qJtNk78HoCEqqEiySuWJFvi4uZ1a7Cen36Fly6Y/2FpwhrDarqMuiRvJlPR1l mqqt90+gy6YMX8iX0iYBiSkz6AqSM9RQo1FxmvylE95AeTRde5iMlWZCtKVL1tB3nAj8P6aD O5ILDc35cOQeD+G7uyA48hIMNu6BbqcJfKdqoenSG7B07CCCxYZ+BIFpeWnrH0DQOeVH0HE7 iODfjnkEbs8CgstfRRBcC/Ri8MPVkxjMzfZhEA58jcHiwHUMOkfDcvXXAgb+qbdw6Jl6F4fJ /sM41PtcOHTP9eHgv+DDZ+Q8MTnP4eHoW0oOwfE/ea50E5fSHUCmQEvD0NBsZvudnArtB2bl ds2sP3y0KnLx8hktuz2w8FwzPH/kGWbHpnUPZ9c7Cronvtt26NGTewceWr5b/Jux6Z2SMVtP Wavl1u8lpdnODNP0dOu8h+N5zaejRQ9yus3p5/8pKO27Rb0w+XJ4/yM3exc/Cxnycn3HeTFz tLAub8MSS0gmoSgft0vCf5QMzExjBAAA Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Zhu Yi The low level companion-spi driver encapsulates the communication details with the companion processor, and provides interface for the upper level drivers to access. Signed-off-by: Zhu Yi Signed-off-by: Mark Jonas --- drivers/spi/Kconfig | 2 + drivers/spi/Makefile | 2 + drivers/spi/companion/Kconfig | 5 + drivers/spi/companion/Makefile | 2 + drivers/spi/companion/core.c | 1185 ++++++++++++++++++++++++++++++ drivers/spi/companion/protocol-manager.c | 1032 ++++++++++++++++++++++++++ drivers/spi/companion/protocol-manager.h | 341 +++++++++ drivers/spi/companion/protocol.h | 273 +++++++ drivers/spi/companion/queue-manager.c | 144 ++++ drivers/spi/companion/queue-manager.h | 245 ++++++ include/linux/companion.h | 259 +++++++ 11 files changed, 3490 insertions(+) create mode 100644 drivers/spi/companion/Kconfig create mode 100644 drivers/spi/companion/Makefile create mode 100644 drivers/spi/companion/core.c create mode 100644 drivers/spi/companion/protocol-manager.c create mode 100644 drivers/spi/companion/protocol-manager.h create mode 100644 drivers/spi/companion/protocol.h create mode 100644 drivers/spi/companion/queue-manager.c create mode 100644 drivers/spi/companion/queue-manager.h create mode 100644 include/linux/companion.h diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index a75f2a2..8b575ec 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -799,6 +799,8 @@ config SPI_TLE62X0 # Add new SPI protocol masters in alphabetical order above this line # +source "drivers/spi/companion/Kconfig" + endif # SPI_MASTER # diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 8e0cda7..ae369d9 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -112,3 +112,5 @@ obj-$(CONFIG_SPI_ZYNQMP_GQSPI) += spi-zynqmp-gqspi.o # SPI slave protocol handlers obj-$(CONFIG_SPI_SLAVE_TIME) += spi-slave-time.o obj-$(CONFIG_SPI_SLAVE_SYSTEM_CONTROL) += spi-slave-system-control.o + +obj-y += companion/ diff --git a/drivers/spi/companion/Kconfig b/drivers/spi/companion/Kconfig new file mode 100644 index 0000000..490a273 --- /dev/null +++ b/drivers/spi/companion/Kconfig @@ -0,0 +1,5 @@ +config COMPANION_SPI + tristate "Low level driver for companion communication (Bosch)" + depends on SPI + help + This driver communicates with the companion processor via SPI. diff --git a/drivers/spi/companion/Makefile b/drivers/spi/companion/Makefile new file mode 100644 index 0000000..e60e733 --- /dev/null +++ b/drivers/spi/companion/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_COMPANION_SPI) += companion-spi.o +companion-spi-objs := core.o protocol-manager.o queue-manager.o diff --git a/drivers/spi/companion/core.c b/drivers/spi/companion/core.c new file mode 100644 index 0000000..515e5ec --- /dev/null +++ b/drivers/spi/companion/core.c @@ -0,0 +1,1185 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Companion low level init/core code + * + * Copyright (C) 2015-2018 Bosch Sicherheitssysteme GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "protocol-manager.h" + +#define DRIVER_NAME "companion-spi" + +#define READY_POLL_US 80 +#define READY_POLL_US_GRAN 1 +#define READY_POLL_MS 100 +#define READY_POLL_MS_GRAN 10 + +/** + * struct busy_signal_statistics - spi busy signal statistics + * @while_busy_ext: how many times while_busy loop been waited + * @while_busy_fail: how many times while_busy been timed out + * @until_busy_ext: how many times until_busy loop been waited + * @until_busy_fail: how many times until_busy been timed out + * @force_started: how many times of force started + * @force_started_failure: how many times of force started failure + * @ready_failure: how many times of ready failure + */ +struct busy_signal_statistics { + u32 while_busy_ext; + u32 while_busy_fail; + u32 until_busy_ext; + u32 until_busy_fail; + u32 force_started; + u32 force_started_failure; + u32 ready_failure; +}; + +/** + * struct companion_spi_priv - companion-spi private data structure + * @spi: address of spi device + * @task: address of task struct + * @wait: wait queue head + * @gpiod_request: gpio line connect to request signal + * @gpiod_busy: gpio line connect to busy signal + * @gpiod_cs: gpio line connect to cs signal + * @dump_packet: flag to control dump spi packet + * @stats: spi busy signal statistics + * @pm: companion protocol manager + */ +struct companion_spi_priv { + struct spi_device *spi; + struct task_struct *task; + wait_queue_head_t wait; + + struct gpio_desc *gpiod_request; + struct gpio_desc *gpiod_busy; + struct gpio_desc *gpiod_cs; + + bool dump_packets; + struct busy_signal_statistics stats; + struct companion_protocol_manager pm; +}; + +/** + * companion_io_ops_register() - register companion IO packets handler + * @parent: address of the parent device + * @ops: address of the IO callbacks + * @data: address of the data passed to the IO callbacks + */ +int companion_io_ops_register(struct device *parent, + struct companion_io_ops *ops, + void *data) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + + return pm_io_ops_register(&priv->pm, ops, data); +} +EXPORT_SYMBOL_GPL(companion_io_ops_register); + +/** + * companion_io_ops_unregister() - unregister companion IO packets handler + * @parent: address of the parent device + */ +int companion_io_ops_unregister(struct device *parent) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + + return pm_io_ops_unregister(&priv->pm); +} +EXPORT_SYMBOL_GPL(companion_io_ops_unregister); + +/** + * companion_can_ops_register() - register companion CAN packets handler + * @parent: address of the parent device + * @port: which CAN port to register + * @ops: address of the CAN callbacks + * @data: address of the data passed to the CAN callbacks + */ +int companion_can_ops_register(struct device *parent, + u8 port, + struct companion_can_ops *ops, + void *data) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + + return pm_can_ops_register(&priv->pm, port, ops, data); +} +EXPORT_SYMBOL_GPL(companion_can_ops_register); + +/** + * companion_can_ops_unregister() - unregister companion CAN packets handler + * @parent: address of the parent device + * @port: which CAN port to unregister + */ +int companion_can_ops_unregister(struct device *parent, u8 port) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + + return pm_can_ops_unregister(&priv->pm, port); +} +EXPORT_SYMBOL_GPL(companion_can_ops_unregister); + +/** + * companion_io_txq_is_full() - return true if IO tx queue is full + * @parent: address of the parent device + */ +bool companion_io_txq_is_full(struct device *parent) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + + return qm_io_txq_is_full(&priv->pm.qm); +} +EXPORT_SYMBOL_GPL(companion_io_txq_is_full); + +/** + * companion_io_rxq_is_empty() - return true if IO rx queue is empty + * @parent: address of the parent device + */ +bool companion_io_rxq_is_empty(struct device *parent) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + + return qm_io_rxq_is_empty(&priv->pm.qm); +} +EXPORT_SYMBOL_GPL(companion_io_rxq_is_empty); + +/** + * companion_do_io_tx() - send IO packet + * @parent: address of the parent device + * @buf: address of the user space buffer to send + * @count: number of bytes to copy + */ +int companion_do_io_tx(struct device *parent, + const char __user *buf, + size_t count) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + unsigned int copied; + int err; + struct companion_packet p; + + if (copy_from_user(p.data, buf, sizeof(p))) { + dev_err(parent, "copy from user not succeed in one call\n"); + return -EFAULT; + } + + if (is_can_type(&p)) + return -EINVAL; + + err = qm_io_txq_in(&priv->pm.qm, buf, count, &copied); + if (err) { + priv->pm.stats.io_tx_overflows++; + return err; + } + + wake_up_interruptible(&priv->wait); + priv->pm.stats.io_tx++; + return copied; +} +EXPORT_SYMBOL_GPL(companion_do_io_tx); + +/** + * companion_do_io_rx() - receive IO packet + * @parent: address of the parent device + * @buf: address of the user space buffer to receive + * @count: number of bytes to copy + */ +int companion_do_io_rx(struct device *parent, + char __user *buf, + size_t count) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + unsigned int copied; + int err; + + err = qm_io_rxq_out(&priv->pm.qm, buf, count, &copied); + return err ? err : copied; +} +EXPORT_SYMBOL_GPL(companion_do_io_rx); + +/** + * companion_do_can_tx() - send CAN packet + * @parent: address of the parent device + * @port: which CAN port to send + * @prio: priority of the CAN frame + * @cf: address of the CAN frame to send + */ +int companion_do_can_tx(struct device *parent, + u8 port, + u8 prio, + const struct can_frame *cf) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + int err; + + err = pm_can_data_tx(&priv->pm, port, prio, cf); + if (err) + return err; + + wake_up_interruptible(&priv->wait); + return 0; +} +EXPORT_SYMBOL_GPL(companion_do_can_tx); + +/** + * companion_do_can_rx() - receive CAN packet + * @parent: address of the parent device + * @port: which CAN port to receive + * @cf: address of the CAN frame to receive + */ +int companion_do_can_rx(struct device *parent, + u8 port, + struct can_frame *cf) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + + return pm_can_data_rx(&priv->pm, port, cf); +} +EXPORT_SYMBOL_GPL(companion_do_can_rx); + +/** + * companion_do_can_err() - receive CAN error packet + * @parent: address of the parent device + * @port: which CAN port to receive + * @bec: address to store CAN error counter + * @state: address to store CAN state + * @code: address to store CAN error code + */ +int companion_do_can_err(struct device *parent, + u8 port, + struct can_berr_counter *bec, + u8 *state, + u8 *code) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + + return pm_can_err(&priv->pm, port, bec, state, code); +} +EXPORT_SYMBOL_GPL(companion_do_can_err); + +/** + * companion_do_set_can_bittiming() - set CAN bittiming + * @parent: address of the parent device + * @port: which CAN port to set + * @bittiming: address of the bittiming to set + */ +int companion_do_set_can_bittiming(struct device *parent, + u8 port, + const struct can_bittiming *bittiming) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + int err; + + err = pm_can_set_bittiming(&priv->pm, port, bittiming); + if (err) + return err; + + wake_up_interruptible(&priv->wait); + return pm_wait_for_response(&priv->pm, port, bcp_can_bittiming); +} +EXPORT_SYMBOL_GPL(companion_do_set_can_bittiming); + +/** + * companion_do_set_can_mode() - set CAN mode + * @parent: address of the parent device + * @port: which CAN port to set + * @mode: the CAN mode to set + */ +int companion_do_set_can_mode(struct device *parent, + u8 port, + enum can_mode mode) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + int err; + + err = pm_can_set_mode(&priv->pm, port, mode); + if (err) + return err; + + wake_up_interruptible(&priv->wait); + return pm_wait_for_response(&priv->pm, port, bcp_can_mode); +} +EXPORT_SYMBOL_GPL(companion_do_set_can_mode); + +/** + * companion_do_set_can_ctrlmode() - set CAN control mode + * @parent: address of the parent device + * @port: which CAN port to set + * @ctrl: the CAN control mode to set + */ +int companion_do_set_can_ctrlmode(struct device *parent, + u8 port, + u32 ctrl) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + int err; + + err = pm_can_set_ctrlmode(&priv->pm, port, ctrl); + if (err) + return err; + + wake_up_interruptible(&priv->wait); + return pm_wait_for_response(&priv->pm, port, bcp_can_mode); +} +EXPORT_SYMBOL_GPL(companion_do_set_can_ctrlmode); + +/** + * companion_do_get_can_status() - get CAN status + * @parent: address of the parent device + * @port: which CAN port to receive + * @bec: address of the CAN error counter to store + */ +int companion_do_get_can_status(struct device *parent, + u8 port, + struct can_berr_counter *bec) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + int err; + + err = pm_can_get_status(&priv->pm, port); + if (err) + return err; + + wake_up_interruptible(&priv->wait); + err = pm_wait_for_response(&priv->pm, port, bcp_can_status); + if (err) + return err; + + bec->rxerr = priv->pm.rx_err[port]; + bec->txerr = priv->pm.tx_err[port]; + return 0; +} +EXPORT_SYMBOL_GPL(companion_do_get_can_status); + +/** + * companion_do_get_can_txq_status() - get single CAN tx queue status + * @parent: address of the parent device + * @port: which CAN port to inquiry + * @prio: which CAN queue to inquiry + * @lost_txq_sync: address of flag to store whether tx queue lost sync + */ +int companion_do_get_can_txq_status(struct device *parent, + u8 port, + u8 prio, + bool *lost_txq_sync) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + struct companion_protocol_manager *pm = &priv->pm; + u8 local, remote; + int err; + + if (prio >= BCP_CAN_PRIOS) + return -EINVAL; + + err = pm_can_get_txq_status(pm, port); + if (err) + return err; + + wake_up_interruptible(&priv->wait); + err = pm_wait_for_response(pm, port, bcp_can_txq_status); + if (err) + return err; + + local = pm->local_txq[port][prio]; + remote = pm->remote_txq[port][prio]; + + if (local != remote) { + *lost_txq_sync = true; + pm->stats.can_lost_txq_sync[port][prio]++; + } else { + *lost_txq_sync = false; + pm->stats.can_ack_timeout[port][prio]++; + } + + pm->local_txq[port][prio] = remote; + return 0; +} +EXPORT_SYMBOL_GPL(companion_do_get_can_txq_status); + +/** + * companion_do_get_can_txq_status_all() - get all CAN tx queue status + * @parent: address of the parent device + * @port: which CAN port to inquiry + */ +int companion_do_get_can_txq_status_all(struct device *parent, u8 port) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + int err; + + err = pm_can_get_txq_status(&priv->pm, port); + if (err) + return err; + + wake_up_interruptible(&priv->wait); + err = pm_wait_for_response(&priv->pm, port, bcp_can_txq_status); + if (err) + return err; + + memcpy(priv->pm.local_txq[port], + priv->pm.remote_txq[port], + BCP_CAN_PRIOS); + return 0; +} +EXPORT_SYMBOL_GPL(companion_do_get_can_txq_status_all); + +/** + * companion_do_can_txq_is_full() - inquiry CAN tx queue is full + * @parent: address of the parent device + * @port: which CAN port to inquiry + * @prio: which CAN queue to inquiry + * @is_full: address of flag to store is full or not + */ +int companion_do_can_txq_is_full(struct device *parent, + u8 port, + u8 prio, + bool *is_full) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + + return pm_can_txq_is_full(&priv->pm, port, prio, is_full); +} +EXPORT_SYMBOL_GPL(companion_do_can_txq_is_full); + +/** + * companion_do_can_txq_has_space() - inquiry CAN tx queue has space + * @parent: address of the parent device + * @port: which CAN port to inquiry + * @prio: which CAN queue to inquiry + * @has_space: address of flag to store has space or not + */ +int companion_do_can_txq_has_space(struct device *parent, + u8 port, + u8 prio, + bool *has_space) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + + return pm_can_txq_has_space(&priv->pm, port, prio, has_space); +} +EXPORT_SYMBOL_GPL(companion_do_can_txq_has_space); + +/** + * companion_do_can_start_tx_timer() - start CAN tx timeout detection + * @parent: address of the parent device + * @port: which CAN port to start + * @prio: which CAN queue to start + */ +int companion_do_can_start_tx_timer(struct device *parent, + u8 port, + u8 prio) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + + return pm_can_start_tx_timer(&priv->pm, port, prio); +} +EXPORT_SYMBOL_GPL(companion_do_can_start_tx_timer); + +/** + * companion_do_can_stop_tx_timer() - stop CAN tx timeout detection + * @parent: address of the parent device + * @port: which CAN port to stop + * @prio: which CAN queue to stop + */ +int companion_do_can_stop_tx_timer(struct device *parent, + u8 port, + u8 prio) +{ + struct companion_spi_priv *priv = dev_get_drvdata(parent); + + return pm_can_stop_tx_timer(&priv->pm, port, prio); +} +EXPORT_SYMBOL_GPL(companion_do_can_stop_tx_timer); + +/** + * dump_packets_show() - display dump_packets value in sysfs entry + * @dev: address of the device associated with sysfs entry + * @attr: address of the device attribute + * @buf: address of the buffer to encode value + */ +static ssize_t dump_packets_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct companion_spi_priv *priv = spi_get_drvdata(spi); + + return snprintf(buf, PAGE_SIZE, "%d\n", priv->dump_packets); +} + +/** + * dump_packets_store() - store dump_packets value from sysfs entry + * @dev: address of the device associated with sysfs entry + * @attr: address of the device attribute + * @buf: address of the buffer to decode value + * @count: number of bytes in the buffer + */ +static ssize_t dump_packets_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct spi_device *spi = to_spi_device(dev); + struct companion_spi_priv *priv = spi_get_drvdata(spi); + int err; + + err = kstrtobool(buf, &priv->dump_packets); + if (err) { + dev_err(&spi->dev, "input invalid value: %s\n", buf); + return err; + } + + return count; +} +static DEVICE_ATTR_RW(dump_packets); + +/** + * overflows_show() - display overflows value in sysfs entry + * @dev: address of the device associated with sysfs entry + * @attr: address of the device attribute + * @buf: address of the buffer to encode value + */ +static ssize_t overflows_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct companion_spi_priv *priv = spi_get_drvdata(spi); + int ret, pos, i, j, total = 0; + + ret = snprintf(buf, PAGE_SIZE, "io\ntx: %u, rx: %u\n\n", + priv->pm.stats.io_tx_overflows, + priv->pm.stats.io_rx_overflows); + pos = ret; + + for (i = 0; i < BCP_CAN_PORTS; ++i) { + ret = snprintf(buf + pos, PAGE_SIZE - pos, "can%u\n", i); + pos += ret; + + for (j = 0; j < BCP_CAN_PRIOS; ++j) { + ret = snprintf(buf + pos, PAGE_SIZE - pos, + "[%u]:%u ", j, + priv->pm.stats.can_tx_overflows[i][j]); + total += priv->pm.stats.can_tx_overflows[i][j]; + pos += ret; + } + + ret = snprintf(buf + pos, PAGE_SIZE - pos, + "\ntx: %u, rx: %u, err: %u\n\n", + total, + priv->pm.stats.can_rx_overflows[i], + priv->pm.stats.can_err_overflows[i]); + pos += ret; + } + return pos; +} +static DEVICE_ATTR_RO(overflows); + +/** + * traffic_show() - display traffic of IO and CAN in sysfs entry + * @dev: address of the device associated with sysfs entry + * @attr: address of the device attribute + * @buf: address of the buffer to encode value + */ +static ssize_t traffic_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct companion_spi_priv *priv = spi_get_drvdata(spi); + int ret, pos, i, j; + + ret = snprintf(buf, PAGE_SIZE, "io\ntx: %u, rx: %u\n\n", + priv->pm.stats.io_tx, priv->pm.stats.io_rx); + pos = ret; + + for (i = 0; i < BCP_CAN_PORTS; ++i) { + ret = snprintf(buf + pos, PAGE_SIZE - pos, "can%u\n", i); + pos += ret; + + ret = snprintf(buf + pos, PAGE_SIZE - pos, "tx : "); + pos += ret; + for (j = 0; j < BCP_CAN_PRIOS; ++j) { + ret = snprintf(buf + pos, PAGE_SIZE - pos, + "[%u]:%u ", j, + priv->pm.stats.can_tx[i][j]); + pos += ret; + } + + ret = snprintf(buf + pos, PAGE_SIZE - pos, "\nack success: "); + pos += ret; + for (j = 0; j < BCP_CAN_PRIOS; ++j) { + ret = snprintf(buf + pos, PAGE_SIZE - pos, + "[%u]:%u ", j, + priv->pm.stats.can_ack_success[i][j]); + pos += ret; + } + + ret = snprintf(buf + pos, PAGE_SIZE - pos, "\nack failure: "); + pos += ret; + for (j = 0; j < BCP_CAN_PRIOS; ++j) { + ret = snprintf(buf + pos, PAGE_SIZE - pos, + "[%u]:%u ", j, + priv->pm.stats.can_ack_failure[i][j]); + pos += ret; + } + + ret = snprintf(buf + pos, PAGE_SIZE - pos, "\nlost seq : "); + pos += ret; + for (j = 0; j < BCP_CAN_PRIOS; ++j) { + ret = snprintf(buf + pos, PAGE_SIZE - pos, + "[%u]:%u ", j, + priv->pm.stats.can_lost_seq_sync[i][j]); + pos += ret; + } + + ret = snprintf(buf + pos, PAGE_SIZE - pos, "\nlost txq : "); + pos += ret; + for (j = 0; j < BCP_CAN_PRIOS; ++j) { + ret = snprintf(buf + pos, PAGE_SIZE - pos, + "[%u]:%u ", j, + priv->pm.stats.can_lost_txq_sync[i][j]); + pos += ret; + } + + ret = snprintf(buf + pos, PAGE_SIZE - pos, "\nack timeout: "); + pos += ret; + for (j = 0; j < BCP_CAN_PRIOS; ++j) { + ret = snprintf(buf + pos, PAGE_SIZE - pos, + "[%u]:%u ", j, + priv->pm.stats.can_ack_timeout[i][j]); + pos += ret; + } + + ret = snprintf(buf + pos, PAGE_SIZE - pos, "\nack unexpect:"); + pos += ret; + for (j = 0; j < BCP_CAN_PRIOS; ++j) { + ret = snprintf(buf + pos, PAGE_SIZE - pos, + "[%u]:%u ", j, + priv->pm.stats.can_ack_unexpect[i][j]); + pos += ret; + } + + ret = snprintf(buf + pos, PAGE_SIZE - pos, + "\nrx : %u\nerr : %u\n\n", + priv->pm.stats.can_rx[i], + priv->pm.stats.can_err[i]); + pos += ret; + } + return pos; +} +static DEVICE_ATTR_RO(traffic); + +/** + * can_space_show() - display CAN queue space in sysfs entry + * @dev: address of the device associated with sysfs entry + * @attr: address of the device attribute + * @buf: address of the buffer to encode value + */ +static ssize_t can_space_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct companion_spi_priv *priv = spi_get_drvdata(spi); + int i, j, ret, pos = 0; + + for (i = 0; i < BCP_CAN_PORTS; ++i) { + ret = snprintf(buf + pos, PAGE_SIZE - pos, "can%u\n", i); + pos += ret; + + ret = snprintf(buf + pos, PAGE_SIZE - pos, "local : "); + pos += ret; + for (j = 0; j < BCP_CAN_PRIOS; ++j) { + ret = snprintf(buf + pos, PAGE_SIZE - pos, + "[%u]:%u ", j, + priv->pm.local_txq[i][j]); + pos += ret; + } + + ret = snprintf(buf + pos, PAGE_SIZE - pos, "\nremote: "); + pos += ret; + for (j = 0; j < BCP_CAN_PRIOS; ++j) { + ret = snprintf(buf + pos, PAGE_SIZE - pos, + "[%u]:%u ", j, + priv->pm.remote_txq[i][j]); + pos += ret; + } + + ret = snprintf(buf + pos, PAGE_SIZE - pos, "\n\n"); + pos += ret; + } + return pos; +} +static DEVICE_ATTR_RO(can_space); + +/** + * busy_show() - display busy signal statisitics in sysfs entry + * @dev: address of the device associated with sysfs entry + * @attr: address of the device attribute + * @buf: address of the buffer to encode value + */ +static ssize_t busy_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct companion_spi_priv *priv = spi_get_drvdata(spi); + + return snprintf(buf, PAGE_SIZE, + "while_busy_ext : %u\n" + "while_busy_fail : %u\n" + "until_busy_ext : %u\n" + "until_busy_fail : %u\n" + "force_started : %u\n" + "force_started_failure: %u\n" + "ready_failure : %u\n", + priv->stats.while_busy_ext, + priv->stats.while_busy_fail, + priv->stats.until_busy_ext, + priv->stats.until_busy_fail, + priv->stats.force_started, + priv->stats.force_started_failure, + priv->stats.ready_failure); +} +static DEVICE_ATTR_RO(busy); + +static struct attribute *companion_spi_sysfs_attrs[] = { + &dev_attr_dump_packets.attr, + &dev_attr_overflows.attr, + &dev_attr_traffic.attr, + &dev_attr_can_space.attr, + &dev_attr_busy.attr, + NULL +}; + +static struct attribute_group companion_spi_attribute_group = { + .attrs = companion_spi_sysfs_attrs +}; + +/** + * slave_has_request() - inquiry spi slave has request + * @priv: address of companion-spi private data + */ +static inline bool slave_has_request(struct companion_spi_priv *priv) +{ + return gpiod_get_value_cansleep(priv->gpiod_request) != 0; +} + +/** + * slave_is_busy() - inquiry spi slave is busy + * @priv: address of companion-spi private data + */ +static inline bool slave_is_busy(struct companion_spi_priv *priv) +{ + return gpiod_get_value_cansleep(priv->gpiod_busy) != 0; +} + +/** + * slave_is_not_busy() - inquiry spi slave is not busy + * @priv: address of companion-spi private data + */ +static inline bool slave_is_not_busy(struct companion_spi_priv *priv) +{ + return gpiod_get_value_cansleep(priv->gpiod_busy) == 0; +} + +/** + * slave_select() - select spi slave + * @priv: address of companion-spi private data + */ +static inline void slave_select(struct companion_spi_priv *priv) +{ + gpiod_set_value_cansleep(priv->gpiod_cs, 1); +} + +/** + * slave_deselect() - deselect spi slave + * @priv: address of companion-spi private data + */ +static inline void slave_deselect(struct companion_spi_priv *priv) +{ + gpiod_set_value_cansleep(priv->gpiod_cs, 0); +} + +/** + * companion_spi_wait_while_busy() - wait while spi slave is busy + * @priv: address of companion-spi private data + */ +static int companion_spi_wait_while_busy(struct companion_spi_priv *priv) +{ + unsigned int count; + + /* + * as short as possible wait while busy polling which shall + * succeed most of the times + */ + count = READY_POLL_US / READY_POLL_US_GRAN; + do { + if (slave_is_not_busy(priv)) + return 0; + + udelay(READY_POLL_US_GRAN); + } while (--count); + + /* + * wait while busy polling with sleeping, in case companion + * is busy with other things, this shall happen rarely + */ + count = READY_POLL_MS / READY_POLL_MS_GRAN; + do { + if (slave_is_not_busy(priv)) { + priv->stats.while_busy_ext++; + dev_info(&priv->spi->dev, + "waited long while busy (%u)\n", + priv->stats.while_busy_ext); + return 0; + } + + msleep(READY_POLL_MS_GRAN); + } while (--count); + + priv->stats.while_busy_fail++; + dev_err(&priv->spi->dev, + "time out waiting for busy deassertion (%u)\n", + priv->stats.while_busy_fail); + return -EBUSY; +} + +/** + * companion_spi_wait_until_busy() - wait until spi slave is busy + * @priv: address of companion-spi private data + */ +static int companion_spi_wait_until_busy(struct companion_spi_priv *priv) +{ + unsigned int count; + + /* + * as short as possible wait until busy polling which shall + * succeed most of the times + */ + count = READY_POLL_US / READY_POLL_US_GRAN; + do { + if (slave_is_busy(priv)) + return 0; + + udelay(READY_POLL_US_GRAN); + } while (--count); + + /* + * wait until busy polling with sleeping, in case companion + * is busy with other things, this shall happen rarely + */ + count = READY_POLL_MS / READY_POLL_MS_GRAN; + do { + if (slave_is_busy(priv)) { + priv->stats.until_busy_ext++; + dev_info(&priv->spi->dev, + "waited long until busy (%u)\n", + priv->stats.until_busy_ext); + return 0; + } + + msleep(READY_POLL_MS_GRAN); + } while (--count); + + priv->stats.until_busy_fail++; + dev_err(&priv->spi->dev, + "time out waiting for busy assertion (%u)\n", + priv->stats.until_busy_fail); + return -EBUSY; +} + +/** + * companion_spi_cpu_to_be32() - convert companion packet to big endian 32 bit + * @buf: address of the packet to convert + */ +static void companion_spi_cpu_to_be32(char *buf) +{ + u32 *buf32 = (u32 *)buf; + int i; + + for (i = 0; i < (BCP_PACKET_SIZE / sizeof(u32)); i++, buf32++) + *buf32 = cpu_to_be32(*buf32); +} + +/** + * companion_spi_be32_to_cpu() - convert companion packet from big endian 32 bit + * @buf: address of the packet to convert + */ +static void companion_spi_be32_to_cpu(char *buf) +{ + u32 *buf32 = (u32 *)buf; + int i; + + for (i = 0; i < (BCP_PACKET_SIZE / sizeof(u32)); i++, buf32++) + *buf32 = be32_to_cpu(*buf32); +} + +/** + * companion_spi_transceive() - transceive spi message + * @priv: address of companion-spi private data + * @message: address of the spi message to transceive + * @transfer: address of the spi transfer + */ +static void companion_spi_transceive(struct companion_spi_priv *priv, + struct spi_message *message, + struct spi_transfer *transfer) +{ + const struct companion_packet *p; + int err; + + if (priv->dump_packets) { + p = transfer->tx_buf; + dump_packet(p, KERN_INFO, DRIVER_NAME" Tx: "); + } + + companion_spi_cpu_to_be32((char *)transfer->tx_buf); + + spi_message_init_with_transfers(message, transfer, 1); + + err = companion_spi_wait_while_busy(priv); + + slave_select(priv); + + if (err) { + priv->stats.force_started++; + dev_err(&priv->spi->dev, + "force started transfer (%u)\n", + priv->stats.force_started); + + /* wait slave to pull up busy line in case force started */ + err = companion_spi_wait_while_busy(priv); + if (err) { + priv->stats.force_started_failure++; + dev_err(&priv->spi->dev, + "force started failed, continuing (%u)\n", + priv->stats.force_started_failure); + } + } + + err = companion_spi_wait_until_busy(priv); + if (err) { + priv->stats.ready_failure++; + dev_err(&priv->spi->dev, + "started transfer in case not ready (%u)\n", + priv->stats.ready_failure); + } + + err = spi_sync(priv->spi, message); + if (err) + dev_err(&priv->spi->dev, + "sending spi message failed: %d\n", + message->status); + + slave_deselect(priv); + + companion_spi_be32_to_cpu(transfer->rx_buf); + + if (priv->dump_packets) { + p = transfer->rx_buf; + dump_packet(p, KERN_INFO, DRIVER_NAME" Rx: "); + } +} + +/** + * companion_spi_request_irq() - irq handler of request signal + * @irq: irq number of request signal + * @data: address of user supplied data for irq handler + */ +static irqreturn_t companion_spi_request_irq(int irq, void *data) +{ + struct companion_spi_priv *priv = data; + + wake_up_interruptible(&priv->wait); + return IRQ_HANDLED; +} + +/** + * companion_spi_thread() - main thread to drive spi communication + * @data: address of user supplied data for thread + */ +static int companion_spi_thread(void *data) +{ + struct companion_spi_priv *priv = data; + struct companion_packet tx_packet; + struct companion_packet rx_packet; + struct spi_message message; + struct spi_transfer transfer; + + memset(&transfer, 0, sizeof(transfer)); + transfer.tx_buf = tx_packet.data; + transfer.rx_buf = rx_packet.data; + transfer.len = sizeof(struct companion_packet); + transfer.cs_change = 0; + transfer.bits_per_word = 32; + + do { + if (wait_event_interruptible(priv->wait, + kthread_should_stop() || + slave_has_request(priv) || + qm_has_tx_data(&priv->pm.qm))) + continue; + + if (kthread_should_stop()) + break; + + pm_prepare_tx(&priv->pm, &tx_packet); + companion_spi_transceive(priv, &message, &transfer); + pm_on_tx_done(&priv->pm); + pm_on_rx_done(&priv->pm, &rx_packet); + } while (!kthread_should_stop()); + + return 0; +} + +static const struct of_device_id companion_spi_of_match[] = { + { .compatible = "bosch,companion-spi", .data = NULL, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, companion_spi_of_match); + +/** + * companion_spi_parse_dt() - parse device tree + * @priv: address of companion-spi private data + */ +static int companion_spi_parse_dt(struct companion_spi_priv *priv) +{ + struct device *dev = &priv->spi->dev; + struct device_node *np = dev->of_node; + int err; + + if (!np) { + dev_err(dev, "no device tree data\n"); + return -EINVAL; + } + + priv->gpiod_request = devm_gpiod_get(dev, "request", GPIOD_IN); + if (IS_ERR(priv->gpiod_request)) { + err = PTR_ERR(priv->gpiod_request); + if (err != -EPROBE_DEFER) + dev_err(dev, "invalid 'request-gpios':%d\n", err); + return err; + } + + priv->gpiod_busy = devm_gpiod_get(dev, "busy", GPIOD_IN); + if (IS_ERR(priv->gpiod_busy)) { + err = PTR_ERR(priv->gpiod_busy); + if (err != -EPROBE_DEFER) + dev_err(dev, "invalid 'busy-gpios':%d\n", err); + return err; + } + + priv->gpiod_cs = devm_gpiod_get(dev, "cs", GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpiod_cs)) { + err = PTR_ERR(priv->gpiod_cs); + if (err != -EPROBE_DEFER) + dev_err(dev, "invalid 'cs-gpios':%d\n", err); + return err; + } + + return 0; +} + +/** + * companion_spi_probe() - probe callback + * @spi: address of the spi device + */ +static int companion_spi_probe(struct spi_device *spi) +{ + struct companion_spi_priv *priv; + u8 null_packet[BCP_PACKET_SIZE] = {0}; + int err; + + priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->spi = spi; + init_waitqueue_head(&priv->wait); + pm_init(&priv->pm); + + err = companion_spi_parse_dt(priv); + if (err) + return err; + + spi->mode = SPI_MODE_1; + + err = spi_setup(spi); + if (err) { + dev_err(&spi->dev, "spi_setup() returns: %d\n", err); + return err; + } + + err = spi_write(spi, null_packet, sizeof(null_packet)); + if (err) { + dev_err(&spi->dev, "dummy transfer failed: %d\n", err); + return err; + } + + spi_set_drvdata(spi, priv); + + err = sysfs_create_group(&spi->dev.kobj, + &companion_spi_attribute_group); + if (err) { + dev_err(&spi->dev, "sysfs_create_group() returns: %d\n", err); + return err; + } + + priv->task = kthread_run(companion_spi_thread, priv, DRIVER_NAME); + if (!priv->task) + return -EIO; + + err = devm_request_irq(&spi->dev, + gpiod_to_irq(priv->gpiod_request), + companion_spi_request_irq, + IRQF_TRIGGER_FALLING, + "companion-spi-request", + priv); + if (err) + return -ENODEV; + + return of_platform_populate(spi->dev.of_node, NULL, NULL, &spi->dev); +} + +/** + * companion_spi_remove() - remove callback + * @spi: address of the spi device + */ +static int companion_spi_remove(struct spi_device *spi) +{ + struct companion_spi_priv *priv = spi_get_drvdata(spi); + + qm_reset(&priv->pm.qm); + kthread_stop(priv->task); + sysfs_remove_group(&spi->dev.kobj, &companion_spi_attribute_group); + of_platform_depopulate(&spi->dev); + return 0; +} + +static struct spi_driver companion_spi_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(companion_spi_of_match), + }, + .probe = companion_spi_probe, + .remove = companion_spi_remove, +}; +module_spi_driver(companion_spi_driver); + +MODULE_AUTHOR("Zhu Yi "); +MODULE_DESCRIPTION("Companion low level init/core code"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/companion/protocol-manager.c b/drivers/spi/companion/protocol-manager.c new file mode 100644 index 0000000..1b09c27 --- /dev/null +++ b/drivers/spi/companion/protocol-manager.c @@ -0,0 +1,1032 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Companion protocol manager code + * + * Copyright (C) 2015-2018 Bosch Sicherheitssysteme GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "protocol-manager.h" + +#define PM_RESPONSE_TIMEOUT HZ +#define PM_CAN_TX_TIMEOUT msecs_to_jiffies(30000) + +/** + * struct companion_filter - companion packet filter + * @node: filter list node + * @match: address of match callback + * @process: address of process callback + */ +struct companion_filter { + struct list_head node; + bool (*match)(const struct companion_packet *p); + void (*process)(struct companion_protocol_manager *pm, + const struct companion_packet *p); +}; + +/** + * null_match() - match null packet + * @p: address of the packet to handle + */ +static bool null_match(const struct companion_packet *p) +{ + return is_null_type(p); +} + +/** + * io_match() - match IO packet + * @p: address of the packet to handle + */ +static bool io_match(const struct companion_packet *p) +{ + return is_io_type(p); +} + +/** + * io_process() - process IO packet + * @pm: address of the protocol manager + * @p: address of the packet to handle + */ +static void io_process(struct companion_protocol_manager *pm, + const struct companion_packet *p) +{ + if (qm_io_rxq_in(&pm->qm, p)) { + down_read(&pm->io_lock); + if (pm->io_ops && pm->io_ops->on_rx_done) + pm->io_ops->on_rx_done(pm->io_data); + up_read(&pm->io_lock); + + pm->stats.io_rx++; + } else { + pm->stats.io_rx_overflows++; + } +} + +/** + * can_data_match() - match CAN data packet + * @p: address of the packet to handle + */ +static bool can_data_match(const struct companion_packet *p) +{ + return ((const struct can_data_frame *)p)->type == BCP_CAN_DATA; +} + +/** + * can_data_process() - process CAN data packet + * @pm: address of the protocol manager + * @p: address of the packet to handle + */ +static void can_data_process(struct companion_protocol_manager *pm, + const struct companion_packet *p) +{ + u8 port = ((const struct can_data_frame *)p)->port - 1; + + if (port >= BCP_CAN_PORTS) + return; + + if (qm_can_rxq_in(&pm->qm, p, port)) { + down_read(&pm->can_lock[port]); + if (pm->can_ops[port] && pm->can_ops[port]->on_rx_done) + pm->can_ops[port]->on_rx_done(pm->can_data[port]); + up_read(&pm->can_lock[port]); + + pm->stats.can_rx[port]++; + } else { + pm->stats.can_rx_overflows[port]++; + } +} + +/** + * can_bittiming_match() - match CAN bittiming packet + * @p: address of the packet to handle + */ +static bool can_bittiming_match(const struct companion_packet *p) +{ + return ((const struct can_bittiming_response *)p)->type == + BCP_CAN_BITTIMING; +} + +/** + * can_bittiming_process() - process CAN bittiming packet + * @pm: address of the protocol manager + * @p: address of the packet to handle + */ +static void can_bittiming_process(struct companion_protocol_manager *pm, + const struct companion_packet *p) +{ + u8 port = ((const struct can_bittiming_response *)p)->port - 1; + u8 status = ((const struct can_bittiming_response *)p)->status; + + if (port >= BCP_CAN_PORTS) + return; + + if (status == BCP_STATUS_SUCCESS) { + pm->response[port][bcp_can_bittiming] = 0; + pm->stats.can_ack_success[port][0]++; + } else { + pm->response[port][bcp_can_bittiming] = -EINVAL; + pm->stats.can_ack_failure[port][0]++; + } + + if (test_and_clear_bit(bcp_can_bittiming, &pm->flags[port])) + wake_up_interruptible_all(&pm->wait[port]); +} + +/** + * can_mode_match() - match CAN mode packet + * @p: address of the packet to handle + */ +static bool can_mode_match(const struct companion_packet *p) +{ + return ((const struct can_mode_response *)p)->type == + BCP_CAN_MODE; +} + +/** + * can_mode_process() - process CAN mode packet + * @pm: address of the protocol manager + * @p: address of the packet to handle + */ +static void can_mode_process(struct companion_protocol_manager *pm, + const struct companion_packet *p) +{ + u8 port = ((const struct can_mode_response *)p)->port - 1; + u8 status = ((const struct can_mode_response *)p)->status; + + if (port >= BCP_CAN_PORTS) + return; + + if (status == BCP_STATUS_SUCCESS) { + pm->response[port][bcp_can_mode] = 0; + pm->stats.can_ack_success[port][0]++; + } else { + pm->response[port][bcp_can_mode] = -EINVAL; + pm->stats.can_ack_failure[port][0]++; + } + + if (test_and_clear_bit(bcp_can_mode, &pm->flags[port])) + wake_up_interruptible_all(&pm->wait[port]); +} + +/** + * can_status_match() - match CAN status packet + * @p: address of the packet to handle + */ +static bool can_status_match(const struct companion_packet *p) +{ + return ((const struct can_status_response *)p)->type == + BCP_CAN_STATUS; +} + +/** + * can_status_process() - process CAN status packet + * @pm: address of the protocol manager + * @p: address of the packet to handle + */ +static void can_status_process(struct companion_protocol_manager *pm, + const struct companion_packet *p) +{ + u8 port = ((const struct can_status_response *)p)->port - 1; + u8 rx_err = ((const struct can_status_response *)p)->rx_err; + u8 tx_err = ((const struct can_status_response *)p)->tx_err; + u8 status = ((const struct can_status_response *)p)->status; + + if (port >= BCP_CAN_PORTS) + return; + + if (status == BCP_STATUS_SUCCESS) { + pm->response[port][bcp_can_status] = 0; + pm->rx_err[port] = rx_err; + pm->tx_err[port] = tx_err; + + if (test_bit(bcp_can_status, &pm->flags[port])) { + pm->stats.can_ack_success[port][0]++; + goto polling_out; + } + + if (qm_can_err_in(&pm->qm, p, port)) { + down_read(&pm->can_lock[port]); + if (pm->can_ops[port] && pm->can_ops[port]->on_error) + pm->can_ops[port]->on_error(pm->can_data[port]); + up_read(&pm->can_lock[port]); + + pm->stats.can_err[port]++; + } else { + pm->stats.can_err_overflows[port]++; + } + } else { + pm->response[port][bcp_can_status] = -EINVAL; + if (test_bit(bcp_can_status, &pm->flags[port])) + pm->stats.can_ack_failure[port][0]++; + } + +polling_out: + if (test_and_clear_bit(bcp_can_status, &pm->flags[port])) + wake_up_interruptible_all(&pm->wait[port]); +} + +/** + * can_tx_ack_match() - match CAN tx acknowledge packet + * @p: address of the packet to handle + */ +static bool can_tx_ack_match(const struct companion_packet *p) +{ + return ((const struct can_tx_acknowledge *)p)->type == + BCP_CAN_TX_ACK; +} + +/** + * can_tx_ack_process() - process CAN tx acknowledge packet + * @pm: address of the protocol manager + * @p: address of the packet to handle + */ +static void can_tx_ack_process(struct companion_protocol_manager *pm, + const struct companion_packet *p) +{ + u8 port = ((const struct can_tx_acknowledge *)p)->port - 1; + u8 prio = ((const struct can_tx_acknowledge *)p)->prio; + u8 sequence = ((const struct can_tx_acknowledge *)p)->sequence; + u8 status = ((const struct can_tx_acknowledge *)p)->status; + u8 space = ((const struct can_tx_acknowledge *)p)->space; + bool lost_seq = false; + + if (port >= BCP_CAN_PORTS || prio >= BCP_CAN_PRIOS) + return; + + /* local_txq will be decreased after kernel sent data to companion, + * remote_txq will be increased after companion send ack to kernel, + * so local_txq < remote_txq should be guaranteed. Otherwise, kernel + * received unexpected ack, hence ignore it. + */ + pm->remote_txq[port][prio] = space; + if (pm->local_txq[port][prio] >= space) { + pm->stats.can_ack_unexpect[port][prio]++; + return; + } + + pm->local_txq[port][prio]++; + pm->sequence[port][prio]++; + if (pm->sequence[port][prio] != sequence) { + lost_seq = true; + pm->sequence[port][prio] = sequence; + pm->stats.can_lost_seq_sync[port][prio]++; + } + + down_read(&pm->can_lock[port]); + if (pm->can_ops[port] && pm->can_ops[port]->on_tx_done) + pm->can_ops[port]->on_tx_done(pm->can_data[port], + prio, + lost_seq, + status == BCP_STATUS_SUCCESS); + up_read(&pm->can_lock[port]); + + if (status == BCP_STATUS_SUCCESS) + pm->stats.can_ack_success[port][prio]++; + else + pm->stats.can_ack_failure[port][prio]++; +} + +/** + * can_txq_status_match() - match CAN tx queue status packet + * @p: address of the packet to handle + */ +static bool can_txq_status_match(const struct companion_packet *p) +{ + return ((const struct can_txq_status_response *)p)->type == + BCP_CAN_TX_QUEUE_STATUS; +} + +/** + * can_txq_status_process() - process CAN tx queue status packet + * @pm: address of the protocol manager + * @p: address of the packet to handle + */ +static void can_txq_status_process(struct companion_protocol_manager *pm, + const struct companion_packet *p) +{ + u8 port = ((const struct can_txq_status_response *)p)->port - 1; + const u8 *space = ((const struct can_txq_status_response *)p)->space; + u8 status = ((const struct can_txq_status_response *)p)->status; + + if (port >= BCP_CAN_PORTS) + return; + + if (status == BCP_STATUS_SUCCESS) { + memcpy(pm->remote_txq[port], space, BCP_CAN_PRIOS); + pm->response[port][bcp_can_txq_status] = 0; + pm->stats.can_ack_success[port][0]++; + } else { + pm->response[port][bcp_can_txq_status] = -EINVAL; + pm->stats.can_ack_failure[port][0]++; + } + + if (test_and_clear_bit(bcp_can_txq_status, &pm->flags[port])) + wake_up_interruptible_all(&pm->wait[port]); +} + +/** + * unknown_match() - match unknown packet + * @p: address of the packet to handle + */ +static bool unknown_match(const struct companion_packet *p) +{ + return true; +} + +/** + * unknown_process() - process unknown packet + * @pm: address of the protocol manager + * @p: address of the packet to handle + */ +static void unknown_process(struct companion_protocol_manager *pm, + const struct companion_packet *p) +{ + dump_packet(p, KERN_ERR, "Unknown packet: "); +} + +static struct companion_filter null_filter = { + .node = LIST_HEAD_INIT(null_filter.node), + .match = null_match, + .process = NULL, +}; + +static struct companion_filter io_filter = { + .node = LIST_HEAD_INIT(io_filter.node), + .match = io_match, + .process = io_process, +}; + +static struct companion_filter can_data_filter = { + .node = LIST_HEAD_INIT(can_data_filter.node), + .match = can_data_match, + .process = can_data_process, +}; + +static struct companion_filter can_bittiming_filter = { + .node = LIST_HEAD_INIT(can_bittiming_filter.node), + .match = can_bittiming_match, + .process = can_bittiming_process, +}; + +static struct companion_filter can_mode_filter = { + .node = LIST_HEAD_INIT(can_mode_filter.node), + .match = can_mode_match, + .process = can_mode_process, +}; + +static struct companion_filter can_status_filter = { + .node = LIST_HEAD_INIT(can_status_filter.node), + .match = can_status_match, + .process = can_status_process, +}; + +static struct companion_filter can_tx_ack_filter = { + .node = LIST_HEAD_INIT(can_tx_ack_filter.node), + .match = can_tx_ack_match, + .process = can_tx_ack_process, +}; + +static struct companion_filter can_txq_status_filter = { + .node = LIST_HEAD_INIT(can_txq_status_filter.node), + .match = can_txq_status_match, + .process = can_txq_status_process, +}; + +static struct companion_filter unknown_filter = { + .node = LIST_HEAD_INIT(unknown_filter.node), + .match = unknown_match, + .process = unknown_process, +}; + +/** + * to_timer_data() - helper to access companion_timer_data in work_struct + * @ws: address of the work_struct object + */ +static struct companion_timer_data *to_timer_data(struct work_struct *ws) +{ + return container_of(ws, struct companion_timer_data, work); +} + +/** + * pm_can_tx_timeout_callback() - CAN tx timeout callback + * @ws: address of the work_struct object + */ +static void pm_can_tx_timeout_callback(struct work_struct *ws) +{ + struct companion_timer_data *td = to_timer_data(ws); + struct companion_protocol_manager *pm = td->pm; + u8 port = td->port; + u8 prio = td->prio; + + down_read(&pm->can_lock[port]); + if (pm->can_ops[port] && pm->can_ops[port]->on_tx_timeout) + pm->can_ops[port]->on_tx_timeout(pm->can_data[port], prio); + up_read(&pm->can_lock[port]); +} + +/** + * pm_can_on_tx_timeout() - CAN tx timeout handler + * @tl: address of the timer_list object + */ +static void pm_can_on_tx_timeout(struct timer_list *tl) +{ + struct companion_timer *ct = from_timer(ct, tl, timer); + + schedule_work(&ct->data.work); +} + +#define CHECK_SIZE(x) \ + BUILD_BUG_ON(sizeof(struct companion_packet) != sizeof(x)) + +/** + * pm_init() - initialize the protocol manager + * @pm: address of the protocol manager to be initialized + */ +void pm_init(struct companion_protocol_manager *pm) +{ + int i; + + /* sanity check for correct packet size at compile time */ + CHECK_SIZE(struct can_data_frame); + CHECK_SIZE(struct can_bittiming_request); + CHECK_SIZE(struct can_bittiming_response); + CHECK_SIZE(struct can_mode_request); + CHECK_SIZE(struct can_mode_response); + CHECK_SIZE(struct can_status_request); + CHECK_SIZE(struct can_status_response); + CHECK_SIZE(struct can_tx_acknowledge); + CHECK_SIZE(struct can_txq_status_request); + CHECK_SIZE(struct can_txq_status_response); + + + init_rwsem(&pm->io_lock); + for (i = 0; i < BCP_CAN_PORTS; ++i) { + init_rwsem(&pm->can_lock[i]); + init_waitqueue_head(&pm->wait[i]); + } + + qm_init(&pm->qm); + + INIT_LIST_HEAD(&pm->filters); + list_add_tail(&null_filter.node, &pm->filters); + list_add_tail(&io_filter.node, &pm->filters); + list_add_tail(&can_data_filter.node, &pm->filters); + list_add_tail(&can_tx_ack_filter.node, &pm->filters); + list_add_tail(&can_bittiming_filter.node, &pm->filters); + list_add_tail(&can_mode_filter.node, &pm->filters); + list_add_tail(&can_status_filter.node, &pm->filters); + list_add_tail(&can_txq_status_filter.node, &pm->filters); + list_add_tail(&unknown_filter.node, &pm->filters); +} + +/** + * pm_io_ops_register() - register companion IO packets handler + * @pm: address of the protocol manager to be registered + * @ops: address of the IO packets callback + * @data: address of the IO packets callback argument + */ +int pm_io_ops_register(struct companion_protocol_manager *pm, + struct companion_io_ops *ops, + void *data) +{ + int result = 0; + + down_write(&pm->io_lock); + if (pm->io_ops) { + result = -EEXIST; + goto out; + } + + qm_reset_io(&pm->qm); + pm->io_ops = ops; + pm->io_data = data; + +out: + up_write(&pm->io_lock); + return result; +} + +/** + * pm_io_ops_unregister() - unregister companion IO packets handler + * @pm: address of the protocol manager to be unregistered + */ +int pm_io_ops_unregister(struct companion_protocol_manager *pm) +{ + int result = 0; + + down_write(&pm->io_lock); + if (!pm->io_ops) { + result = -ENODEV; + goto out; + } + + pm->io_ops = NULL; + pm->io_data = NULL; + qm_reset_io(&pm->qm); + +out: + up_write(&pm->io_lock); + return result; +} + +/** + * pm_can_ops_register() - register companion CAN packets handler + * @pm: address of the protocol manager to be registered + * @port: port number of which CAN to be registered + * @ops: address of the CAN packets callback + * @data: address of the CAN packets callback argument + */ +int pm_can_ops_register(struct companion_protocol_manager *pm, + u8 port, + struct companion_can_ops *ops, + void *data) +{ + int i, result = 0; + + if (port >= BCP_CAN_PORTS) + return -EINVAL; + + down_write(&pm->can_lock[port]); + if (pm->can_ops[port]) { + result = -EEXIST; + goto out; + } + + qm_reset_can(&pm->qm, port); + pm->can_ops[port] = ops; + pm->can_data[port] = data; + + for (i = 0; i < BCP_CAN_PRIOS; ++i) { + pm->timer[port][i].data.pm = pm; + pm->timer[port][i].data.port = port; + pm->timer[port][i].data.prio = i; + INIT_WORK(&pm->timer[port][i].data.work, + pm_can_tx_timeout_callback); + timer_setup(&pm->timer[port][i].timer, pm_can_on_tx_timeout, 0); + } + +out: + up_write(&pm->can_lock[port]); + return result; +} + +/** + * pm_can_ops_unregister() - unregister companion CAN packets handler + * @pm: address of the protocol manager to be unregistered + * @port: port number of which CAN to be unregistered + */ +int pm_can_ops_unregister(struct companion_protocol_manager *pm, u8 port) +{ + int i, result = 0; + + if (port >= BCP_CAN_PORTS) + return -EINVAL; + + down_write(&pm->can_lock[port]); + if (!pm->can_ops[port]) { + result = -ENODEV; + goto out; + } + + pm->can_ops[port] = NULL; + pm->can_data[port] = NULL; + qm_reset_can(&pm->qm, port); + + for (i = 0; i < BCP_CAN_PRIOS; ++i) { + del_timer_sync(&pm->timer[port][i].timer); + cancel_work_sync(&pm->timer[port][i].data.work); + pm->timer[port][i].data.pm = NULL; + pm->timer[port][i].data.port = 0; + pm->timer[port][i].data.prio = 0; + } + +out: + up_write(&pm->can_lock[port]); + return result; +} + +/** + * pm_prepare_tx() - prepare tx data + * @pm: address of the protocol manager to be used + * @p: address of the data to be sent + */ +void pm_prepare_tx(struct companion_protocol_manager *pm, + struct companion_packet *p) +{ + pm->is_io_type = false; + + if (qm_get_tx_data(&pm->qm, p)) { + if (is_io_type(p)) + pm->is_io_type = true; + } else { + memset(p, BCP_NOOP, sizeof(*p)); + } +} + +/** + * pm_on_tx_done() - handle tx done + * @pm: address of the protocol manager to be used + */ +void pm_on_tx_done(struct companion_protocol_manager *pm) +{ + if (!pm->is_io_type) + return; + + down_read(&pm->io_lock); + if (pm->io_ops && pm->io_ops->on_tx_done) + pm->io_ops->on_tx_done(pm->io_data); + up_read(&pm->io_lock); +} + +/** + * pm_on_rx_done() - handle rx done + * @pm: address of the protocol manager to be used + * @p: address of the recevied data + */ +void pm_on_rx_done(struct companion_protocol_manager *pm, + const struct companion_packet *p) +{ + struct companion_filter *filter; + + list_for_each_entry(filter, &pm->filters, node) { + if (filter->match && filter->match(p)) { + if (filter->process) + filter->process(pm, p); + + break; + } + } +} + +/** + * pm_can_data_tx() - send CAN data according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be sent + * @prio: priority of the data to be sent + * @cf: the raw CAN frame to be send + */ +int pm_can_data_tx(struct companion_protocol_manager *pm, + u8 port, + u8 prio, + const struct can_frame *cf) +{ + struct can_data_frame p; + + if (port >= BCP_CAN_PORTS || prio >= BCP_CAN_PRIOS) + return -EINVAL; + + if (pm->local_txq[port][prio] == 0) + return -ENOSPC; + + p.type = BCP_CAN_DATA; + p.port = port + 1; + p.prio = prio; + p.dlc = cf->can_dlc; + p.id = cf->can_id; + memcpy(p.data, cf->data, sizeof(cf->data)); + + if (!qm_can_txq_in(&pm->qm, + (struct companion_packet *)&p, + port, + prio)) { + pm->stats.can_tx_overflows[port][prio]++; + return -ENOSPC; + } + + pm->local_txq[port][prio]--; + + pm->stats.can_tx[port][prio]++; + return 0; +} + +/** + * pm_can_data_rx() - receive CAN data according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be received + * @cf: address of the raw CAN frame to be copied + */ +int pm_can_data_rx(struct companion_protocol_manager *pm, + u8 port, + struct can_frame *cf) +{ + struct can_data_frame p; + + if (port >= BCP_CAN_PORTS) + return -EINVAL; + + if (!qm_can_rxq_out(&pm->qm, (struct companion_packet *)&p, port)) + return -EIO; + + cf->can_id = p.id; + cf->can_dlc = p.dlc; + memcpy(cf->data, p.data, sizeof(cf->data)); + return 0; +} + +/** + * pm_can_err() - receive CAN error according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be received + * @bec: address of the error counter to be copied + * @state: address of the error state to be copied + * @code: address of the error code to be copied + */ +int pm_can_err(struct companion_protocol_manager *pm, + u8 port, + struct can_berr_counter *berr, + u8 *state, + u8 *code) +{ + struct can_status_response p; + + if (port >= BCP_CAN_PORTS) + return -EINVAL; + + if (!qm_can_err_out(&pm->qm, (struct companion_packet *)&p, port)) + return -EIO; + + berr->rxerr = p.rx_err; + berr->txerr = p.tx_err; + *state = p.state; + *code = p.code; + return 0; +} + +/** + * pm_wait_for_response() - wait for CAN packets response + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be wait + * @flag: flag to be wait + */ +int pm_wait_for_response(struct companion_protocol_manager *pm, + u8 port, + enum bcp_can_flag flag) +{ + unsigned long *flags = &pm->flags[port]; + long ret; + + if (test_bit(flag, flags)) { + ret = wait_event_interruptible_timeout(pm->wait[port], + !test_bit(flag, flags), + PM_RESPONSE_TIMEOUT); + + if (ret < 0) + return ret; + + if (ret == 0) + return -ETIMEDOUT; + } + + return pm->response[port][flag]; +} + +/** + * pm_can_set_bittiming() - set CAN bittiming according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be set + * @bittiming: the bittiming to be set + */ +int pm_can_set_bittiming(struct companion_protocol_manager *pm, + u8 port, + const struct can_bittiming *bittiming) +{ + struct can_bittiming_request p; + + if (port >= BCP_CAN_PORTS) + return -EINVAL; + + memset(&p, 0, sizeof(p)); + p.type = BCP_CAN_BITTIMING; + p.port = port + 1; + p.prescaler = bittiming->brp; + p.prop_seg = bittiming->prop_seg; + p.phase_seg1 = bittiming->phase_seg1; + p.phase_seg2 = bittiming->phase_seg2; + p.sjw = bittiming->sjw; + + if (!qm_can_txq_in(&pm->qm, + (struct companion_packet *)&p, + port, + 0)) { + pm->stats.can_tx_overflows[port][0]++; + return -ENOSPC; + } + + set_bit(bcp_can_bittiming, &pm->flags[port]); + pm->stats.can_tx[port][0]++; + return 0; +} + +/** + * pm_can_set_mode() - set CAN mode according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be set + * @mode: the mode to be set + */ +int pm_can_set_mode(struct companion_protocol_manager *pm, + u8 port, + enum can_mode mode) +{ + struct can_mode_request p; + + if (port >= BCP_CAN_PORTS) + return -EINVAL; + + memset(&p, 0, sizeof(p)); + p.type = BCP_CAN_MODE; + p.port = port + 1; + + switch (mode) { + case CAN_MODE_START: + p.mode = BCP_CAN_MODE_NORMAL; + break; + + case CAN_MODE_STOP: + p.mode = BCP_CAN_MODE_OFF; + break; + + default: + return -EOPNOTSUPP; + } + + if (!qm_can_txq_in(&pm->qm, + (struct companion_packet *)&p, + port, + 0)) { + pm->stats.can_tx_overflows[port][0]++; + return -ENOSPC; + } + + set_bit(bcp_can_mode, &pm->flags[port]); + pm->stats.can_tx[port][0]++; + return 0; +} + +/** + * pm_can_set_ctrlmode() - set CAN control mode according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be set + * @ctrl: the control mode to be set + */ +int pm_can_set_ctrlmode(struct companion_protocol_manager *pm, + u8 port, + u32 ctrl) +{ + struct can_mode_request p; + + if (port >= BCP_CAN_PORTS) + return -EINVAL; + + if (!(ctrl & CAN_CTRLMODE_LISTENONLY)) + return -EOPNOTSUPP; + + memset(&p, 0, sizeof(p)); + p.type = BCP_CAN_MODE; + p.port = port + 1; + p.mode = BCP_CAN_MODE_LISTEN; + + if (!qm_can_txq_in(&pm->qm, + (struct companion_packet *)&p, + port, + 0)) { + pm->stats.can_tx_overflows[port][0]++; + return -ENOSPC; + } + + set_bit(bcp_can_mode, &pm->flags[port]); + pm->stats.can_tx[port][0]++; + return 0; +} + +/** + * pm_can_get_status() - get CAN status according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be inquiry + */ +int pm_can_get_status(struct companion_protocol_manager *pm, u8 port) +{ + struct can_status_request p; + + if (port >= BCP_CAN_PORTS) + return -EINVAL; + + memset(&p, 0, sizeof(p)); + p.type = BCP_CAN_STATUS; + p.port = port + 1; + + if (!qm_can_txq_in(&pm->qm, + (struct companion_packet *)&p, + port, + 0)) { + pm->stats.can_tx_overflows[port][0]++; + return -ENOSPC; + } + + set_bit(bcp_can_status, &pm->flags[port]); + pm->stats.can_tx[port][0]++; + return 0; +} + +/** + * pm_can_get_txq_status() - get CAN tx queue status according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be inquiry + */ +int pm_can_get_txq_status(struct companion_protocol_manager *pm, u8 port) +{ + struct can_txq_status_request p; + + if (port >= BCP_CAN_PORTS) + return -EINVAL; + + memset(&p, 0, sizeof(p)); + p.type = BCP_CAN_TX_QUEUE_STATUS; + p.port = port + 1; + + if (!qm_can_txq_in(&pm->qm, + (struct companion_packet *)&p, + port, + 0)) { + pm->stats.can_tx_overflows[port][0]++; + return -ENOSPC; + } + + set_bit(bcp_can_txq_status, &pm->flags[port]); + pm->stats.can_tx[port][0]++; + return 0; +} + +/** + * pm_can_txq_is_full() - inquiry CAN tx queue is full + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be inquiry + * @prio: queue number of which tx queue to be inquiry + * @is_full: address of the is full result to be copied + */ +int pm_can_txq_is_full(struct companion_protocol_manager *pm, + u8 port, + u8 prio, + bool *is_full) +{ + if (port >= BCP_CAN_PORTS || prio >= BCP_CAN_PRIOS) + return -EINVAL; + + *is_full = (pm->local_txq[port][prio] == 0); + return 0; +} + +/** + * pm_can_txq_has_space() - inquiry CAN tx queue has space + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be inquiry + * @prio: queue number of which tx queue to be inquiry + * @has_space: address of the has space result to be copied + */ +int pm_can_txq_has_space(struct companion_protocol_manager *pm, + u8 port, + u8 prio, + bool *has_space) +{ + if (port >= BCP_CAN_PORTS || prio >= BCP_CAN_PRIOS) + return -EINVAL; + + *has_space = (pm->local_txq[port][prio] > 0); + return 0; +} + +/** + * pm_can_start_tx_timer() - start CAN tx timeout detection + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be used + * @prio: queue number of which tx queue's timer to be used + */ +int pm_can_start_tx_timer(struct companion_protocol_manager *pm, + u8 port, + u8 prio) +{ + if (port >= BCP_CAN_PORTS || prio >= BCP_CAN_PRIOS) + return -EINVAL; + + mod_timer(&pm->timer[port][prio].timer, jiffies + PM_CAN_TX_TIMEOUT); + return 0; +} + +/** + * pm_can_stop_tx_timer() - stop CAN tx timeout detection + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be used + * @prio: queue number of which tx queue's timer to be used + */ +int pm_can_stop_tx_timer(struct companion_protocol_manager *pm, + u8 port, + u8 prio) +{ + if (port >= BCP_CAN_PORTS || prio >= BCP_CAN_PRIOS) + return -EINVAL; + + del_timer_sync(&pm->timer[port][prio].timer); + return 0; +} diff --git a/drivers/spi/companion/protocol-manager.h b/drivers/spi/companion/protocol-manager.h new file mode 100644 index 0000000..2cf10fa --- /dev/null +++ b/drivers/spi/companion/protocol-manager.h @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Companion protocol manager code + * + * Copyright (C) 2015-2018 Bosch Sicherheitssysteme GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _BOSCH_COMPANION_PROTOCOL_MANAGER_H +#define _BOSCH_COMPANION_PROTOCOL_MANAGER_H + +#include +#include +#include +#include +#include +#include +#include "queue-manager.h" + +/** + * enum bcp_can_flag - companion CAN packet event flag + */ +enum bcp_can_flag { + bcp_can_bittiming, + bcp_can_mode, + bcp_can_status, + bcp_can_txq_status, + + bcp_can_max +}; + +/** + * struct companion_statistics - statistics information of companion + * @io_tx_overflows: counter of IO packets transmit overflow + * @io_rx_overflows: counter of IO packets receive overflow + * @can_tx_overflows: counter of CAN packets transmit overflow + * @can_rx_overflows: counter of CAN packets receive overflow + * @can_err_overflows: counter of CAN error packets receive overflow + * @io_tx: counter of IO packets transmitted + * @io_rx: counter of IO packets received + * @can_tx: counter of CAN packets transmitted + * @can_ack_success: counter of CAN response success packets received + * @can_ack_failure: counter of CAN response failure packets received + * @can_rx: counter of CAN packets received + * @can_err: counter of CAN error packets received + * @can_lost_seq_sync: counter of CAN packets sequence lost sync + * @can_lost_txq_sync: counter of CAN tx queue status lost sync + * @can_ack_timeout: counter of CAN tx ack timeout + * @can_ack_unexpect: counter of CAN unexpected tx ack + */ +struct companion_statistics { + u32 io_tx_overflows; + u32 io_rx_overflows; + u32 can_tx_overflows[BCP_CAN_PORTS][BCP_CAN_PRIOS]; + u32 can_rx_overflows[BCP_CAN_PORTS]; + u32 can_err_overflows[BCP_CAN_PORTS]; + + u32 io_tx; + u32 io_rx; + u32 can_tx[BCP_CAN_PORTS][BCP_CAN_PRIOS]; + u32 can_ack_success[BCP_CAN_PORTS][BCP_CAN_PRIOS]; + u32 can_ack_failure[BCP_CAN_PORTS][BCP_CAN_PRIOS]; + u32 can_rx[BCP_CAN_PORTS]; + u32 can_err[BCP_CAN_PORTS]; + u32 can_lost_seq_sync[BCP_CAN_PORTS][BCP_CAN_PRIOS]; + u32 can_lost_txq_sync[BCP_CAN_PORTS][BCP_CAN_PRIOS]; + u32 can_ack_timeout[BCP_CAN_PORTS][BCP_CAN_PRIOS]; + u32 can_ack_unexpect[BCP_CAN_PORTS][BCP_CAN_PRIOS]; +}; + +struct companion_protocol_manager; +/** + * struct companion_timer_data - encapsulate data passed to timer + * @pm: companion protocol manager + * @port: port number of which CAN associated with the timer + * @prio: priority of which CAN tx queue associated with the timer + * @work: work for execute timeout callback in thread context + */ +struct companion_timer_data { + struct companion_protocol_manager *pm; + u8 port; + u8 prio; + struct work_struct work; +}; + +/** + * struct companion_timer - encapsulate timer and data + * @timer: timer_list object for timeout + * @data: data associated with the timer + */ +struct companion_timer { + struct timer_list timer; + struct companion_timer_data data; +}; + +/** + * struct companion_protocol_manager - encapsulate companion protocol handling + * @io_ops: callback of IO packet handling + * @io_data: callback argument of io_ops + * @io_lock: lock to protect IO callback + * @can_ops: callback of CAN packet handling + * @can_data: callback argument of can_ops + * @can_lock: lock to protect CAN callback + * @wait: wait queue head for CAN packet response + * @flags: event flags for CAN packet response + * @response: response result of each CAN packet event flag + * @rx_err: receive error counter of CAN port + * @tx_err: transmit error counter of CAN port + * @sequence: sequence counter of each CAN tx queue + * @remote_txq: CAN tx queue space status at remote + * @local_txq: CAN tx queue space status at local + * @timer: timer for detect CAN tx ack timeout for each CAN tx queue + * @stats: statistics information of companion + * @is_io_type: flag to record the sent packet is IO type or not + * @qm: queue manager + * @filters: filter list to handle receveid companion packets + */ +struct companion_protocol_manager { + struct companion_io_ops *io_ops; + void *io_data; + struct rw_semaphore io_lock; + struct companion_can_ops *can_ops[BCP_CAN_PORTS]; + void *can_data[BCP_CAN_PORTS]; + struct rw_semaphore can_lock[BCP_CAN_PORTS]; + wait_queue_head_t wait[BCP_CAN_PORTS]; + unsigned long flags[BCP_CAN_PORTS]; + int response[BCP_CAN_PORTS][bcp_can_max]; + u8 rx_err[BCP_CAN_PORTS]; + u8 tx_err[BCP_CAN_PORTS]; + u8 sequence[BCP_CAN_PORTS][BCP_CAN_PRIOS]; + u8 remote_txq[BCP_CAN_PORTS][BCP_CAN_PRIOS]; + u8 local_txq[BCP_CAN_PORTS][BCP_CAN_PRIOS]; + struct companion_timer timer[BCP_CAN_PORTS][BCP_CAN_PRIOS]; + + struct companion_statistics stats; + bool is_io_type; + + struct companion_queue_manager qm; + struct list_head filters; +}; + +/** + * pm_init() - initialize the protocol manager + * @pm: address of the protocol manager to be initialized + */ +void pm_init(struct companion_protocol_manager *pm); + +/** + * pm_io_ops_register() - register companion IO packets handler + * @pm: address of the protocol manager to be registered + * @ops: address of the IO packets callback + * @data: address of the IO packets callback argument + */ +int pm_io_ops_register(struct companion_protocol_manager *pm, + struct companion_io_ops *ops, + void *data); + +/** + * pm_io_ops_unregister() - unregister companion IO packets handler + * @pm: address of the protocol manager to be unregistered + */ +int pm_io_ops_unregister(struct companion_protocol_manager *pm); + +/** + * pm_can_ops_register() - register companion CAN packets hanler + * @pm: address of the protocol manager to be registered + * @port: port number of which CAN to be registered + * @ops: address of the CAN packets callback + * @data: address of the CAN packets callback argument + */ +int pm_can_ops_register(struct companion_protocol_manager *pm, + u8 port, + struct companion_can_ops *ops, + void *data); + +/** + * pm_can_ops_unregister() - unregister companion CAN packets handler + * @pm: address of the protocol manager to be unregistered + * @port: port number of which CAN to be unregistered + */ +int pm_can_ops_unregister(struct companion_protocol_manager *pm, u8 port); + +/** + * pm_prepare_tx() - prepare tx data + * @pm: address of the protocol manager to be used + * @p: address of the data to be sent + */ +void pm_prepare_tx(struct companion_protocol_manager *pm, + struct companion_packet *p); + +/** + * pm_on_tx_done() - handle tx done + * @pm: address of the protocol manager to be used + */ +void pm_on_tx_done(struct companion_protocol_manager *pm); + +/** + * pm_on_rx_done() - handle rx done + * @pm: address of the protocol manager to be used + * @p: address of the recevied data + */ +void pm_on_rx_done(struct companion_protocol_manager *pm, + const struct companion_packet *p); + +/** + * pm_can_data_tx() - send CAN data according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be sent + * @prio: priority of the data to be sent + * @cf: the raw CAN frame to be send + */ +int pm_can_data_tx(struct companion_protocol_manager *pm, + u8 port, + u8 prio, + const struct can_frame *cf); + +/** + * pm_can_data_rx() - receive CAN data according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be received + * @cf: address of the raw CAN frame to be copied + */ +int pm_can_data_rx(struct companion_protocol_manager *pm, + u8 port, + struct can_frame *cf); + +/** + * pm_can_err() - receive CAN error according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be received + * @bec: address of the error counter to be copied + * @state: address of the error state to be copied + * @code: address of the error code to be copied + */ +int pm_can_err(struct companion_protocol_manager *pm, + u8 port, + struct can_berr_counter *bec, + u8 *state, + u8 *code); + +/** + * pm_wait_for_response() - wait for CAN packets response + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be wait + * @flag: flag to be wait + */ +int pm_wait_for_response(struct companion_protocol_manager *pm, + u8 port, + enum bcp_can_flag flag); + +/** + * pm_can_set_bittiming() - set CAN bittiming according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be set + * @bittiming: the bittiming to be set + */ +int pm_can_set_bittiming(struct companion_protocol_manager *pm, + u8 port, + const struct can_bittiming *bittiming); + +/** + * pm_can_set_mode() - set CAN mode according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be set + * @mode: the mode to be set + */ +int pm_can_set_mode(struct companion_protocol_manager *pm, + u8 port, + enum can_mode mode); + +/** + * pm_can_set_ctrlmode() - set CAN control mode according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be set + * @ctrl: the control mode to be set + */ +int pm_can_set_ctrlmode(struct companion_protocol_manager *pm, + u8 port, + u32 ctrl); + +/** + * pm_can_get_status() - get CAN status according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be inquiry + */ +int pm_can_get_status(struct companion_protocol_manager *pm, u8 port); + +/** + * pm_can_get_txq_status() - get CAN tx queue status according to protocol + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be inquiry + */ +int pm_can_get_txq_status(struct companion_protocol_manager *pm, u8 port); + +/** + * pm_can_txq_is_full() - inquiry CAN tx queue is full + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be inquiry + * @prio: queue number of which tx queue to be inquiry + * @is_full: address of the is full result to be copied + */ +int pm_can_txq_is_full(struct companion_protocol_manager *pm, + u8 port, + u8 prio, + bool *is_full); + +/** + * pm_can_txq_has_space() - inquiry CAN tx queue has space + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be inquiry + * @prio: queue number of which tx queue to be inquiry + * @has_space: address of the has space result to be copied + */ +int pm_can_txq_has_space(struct companion_protocol_manager *pm, + u8 port, + u8 prio, + bool *has_space); + +/** + * pm_can_start_tx_timer() - start CAN tx timeout detection + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be used + * @prio: queue number of which tx queue's timer to be used + */ +int pm_can_start_tx_timer(struct companion_protocol_manager *pm, + u8 port, + u8 prio); + +/** + * pm_can_stop_tx_timer() - stop CAN tx timeout detection + * @pm: address of the protocol manager to be used + * @port: port number of which CAN to be used + * @prio: queue number of which tx queue's timer to be used + */ +int pm_can_stop_tx_timer(struct companion_protocol_manager *pm, + u8 port, + u8 prio); +#endif diff --git a/drivers/spi/companion/protocol.h b/drivers/spi/companion/protocol.h new file mode 100644 index 0000000..7f70ed6 --- /dev/null +++ b/drivers/spi/companion/protocol.h @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Companion protocol kernel space API + * + * Copyright (C) 2015-2018 Bosch Sicherheitssysteme GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _BOSCH_COMPANION_PROTOCOL_KERNEL_H +#define _BOSCH_COMPANION_PROTOCOL_KERNEL_H + +#include + +#define BCP_CAN_PORTS 2u +#define BCP_CAN_PRIOS 8u + +/** + * BCP type field definitions (CAN) + */ +#define BCP_NOOP 0x00u +#define BCP_CAN_DATA 0x01u +#define BCP_CAN_BITTIMING 0x03u +#define BCP_CAN_MODE 0x04u +#define BCP_CAN_STATUS 0x06u +#define BCP_CAN_TX_ACK 0x07u +#define BCP_CAN_TX_QUEUE_STATUS 0x0Fu + +/** + * BCP status field definitions + */ +#define BCP_STATUS_SUCCESS 0x00u +#define BCP_STATUS_UNKNOWN 0x01u +#define BCP_STATUS_OTHER 0x02u + +/** + * BCP packet size definition + */ +#define BCP_PACKET_SIZE 16u + +/** + * struct companion_packet - companion packet general format + * @data: contents of the packet + */ +struct companion_packet { + u8 data[BCP_PACKET_SIZE]; +}; + +/** + * struct can_data_frame - companion can data frame packet + * @type: packet type + * @port: can port + * @prio: priority of the can frame + * @dlc: can frame payload in bytes + * @id: can frame id + * @data: can frame payload + */ +struct can_data_frame { + u8 type; + u8 port; + u8 prio; + u8 dlc; + u32 id; + u8 data[8]; +}; + +/** + * struct can_bittiming_request - companion can bittiming request packet + * @type: packet type + * @port: can port + * @prescaler: bitrate prescaler + * @prop_seg: propagation segment in TQs + * @phase_seg1: phase buffer segment 1 in TQs + * @phase_seg2: phase buffer segment 2 in TQs + * @sjw: synchronisation jump width in TQs + * @reserved: reserved + */ +struct can_bittiming_request { + u8 type; + u8 port; + u16 prescaler; + u8 prop_seg; + u8 phase_seg1; + u8 phase_seg2; + u8 sjw; + u8 reserved[8]; +}; + +/** + * struct can_bittiming_response - companion can bittiming response packet + * @type: packet type + * @port: can port + * @prescaler: bitrate prescaler + * @prop_seg: propagation segment in TQs + * @phase_seg1: phase buffer segment 1 in TQs + * @phase_seg2: phase buffer segment 2 in TQs + * @sjw: synchronisation jump width in TQs + * @reserved: reserved + * @status: process status + */ +struct can_bittiming_response { + u8 type; + u8 port; + u16 prescaler; + u8 prop_seg; + u8 phase_seg1; + u8 phase_seg2; + u8 sjw; + u8 reserved[7]; + u8 status; +}; + +/** + * struct can_mode_request - companion can mode request packet + * @type: packet type + * @port: can port + * @mode: can mode + * @reserved: reserved + */ +struct can_mode_request { + u8 type; + u8 port; + u8 mode; + u8 reserved[13]; +}; +#define BCP_CAN_MODE_OFF 0x00u +#define BCP_CAN_MODE_NORMAL 0x01u +#define BCP_CAN_MODE_LISTEN 0x02u + +/** + * struct can_mode_response - companion can mode response packet + * @type: packet type + * @port: can port + * @mode: can mode + * @reserved: reserved + * @status: process status + */ +struct can_mode_response { + u8 type; + u8 port; + u8 mode; + u8 reserved[12]; + u8 status; +}; + +/** + * struct can_status_request - companion can status request packet + * @type: packet type + * @port: can port + * @reserved: reserved + */ +struct can_status_request { + u8 type; + u8 port; + u8 reserved[14]; +}; + +/** + * struct can_status_response - companion can status response packet + * @type: packet type + * @port: can port + * @rx_err: rx error counter + * @tx_err: tx error counter + * @err1: can controller error status 1 + * @err2: can controller error status 2 + * @reserved: reserved + * @status: process status + */ +struct can_status_response { + u8 type; + u8 port; + u8 rx_err; + u8 tx_err; + u8 state; + u8 code; + u8 reserved[9]; + u8 status; +}; + +/** + * struct can_tx_acknowledge - companion can tx acknowledge packet + * @type: packet type + * @port: can port + * @prio: priority of the can frame + * @dlc: payload length of the can frame + * @sequence: monotonic increasing sequence counter of sent can frames + * @space: queue space left of this priority + * @reserved: reserved + * @status: process status + */ +struct can_tx_acknowledge { + u8 type; + u8 port; + u8 prio; + u8 dlc; + u8 sequence; + u8 space; + u8 reserved[9]; + u8 status; +}; + +/** + * struct can_txq_status_request - companion can txq status request packet + * @type: packet type + * @port: can port + * @reserved: reserved + */ +struct can_txq_status_request { + u8 type; + u8 port; + u8 reserved[14]; +}; + +/** + * struct can_txq_status_response - companion can txq status response packet + * @type: packet type + * @port: can port + * @space: queue space left of each priority + * @reserved: reserved + * @status: process status + */ +struct can_txq_status_response { + u8 type; + u8 port; + u8 space[8]; + u8 reserved[5]; + u8 status; +}; + +/** + * is_null_type() - return true if the packet is null type + * @p: the packet to test + */ +static inline bool is_null_type(const struct companion_packet *p) +{ + return p->data[0] == BCP_NOOP; +} + +/** + * is_io_type() - return true if the packet is io type + * @p: the packet to test + */ +static inline bool is_io_type(const struct companion_packet *p) +{ + return (p->data[0] & 0x80); +} + +/** + * is_can_type() - return true if the packet is can type + * @p: the packet to test + */ +static inline bool is_can_type(const struct companion_packet *p) +{ + return !is_io_type(p) && !is_null_type(p); +} + +/** + * dump_packet() - dump raw packet data in hexadecimal format + * @p: the packet to dump + * @level: the log level of the dump + * @prefix: the prefix string of the dump + */ +static inline void dump_packet(const struct companion_packet *p, + const char *level, + const char *prefix) +{ + print_hex_dump(level, prefix, DUMP_PREFIX_NONE, 16, 1, + p->data, sizeof(p->data), false); +} + +#endif diff --git a/drivers/spi/companion/queue-manager.c b/drivers/spi/companion/queue-manager.c new file mode 100644 index 0000000..f72696f --- /dev/null +++ b/drivers/spi/companion/queue-manager.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Companion queue management code + * + * Copyright (C) 2015-2018 Bosch Sicherheitssysteme GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "queue-manager.h" + +/** + * qm_init() - initialize all managed queues + * @qm: address of the queue manager to be initialized + */ +void qm_init(struct companion_queue_manager *qm) +{ + int i, j; + + INIT_KFIFO(qm->io_txq.fifo); + INIT_KFIFO(qm->io_rxq.fifo); + + for (i = 0; i < BCP_CAN_PORTS; ++i) { + for (j = 0; j < BCP_CAN_PRIOS; ++j) + INIT_KFIFO(qm->can_txq[i][j].fifo); + + INIT_KFIFO(qm->can_rxq[i].fifo); + INIT_KFIFO(qm->can_err[i].fifo); + mutex_init(&qm->can_txq_lock[i]); + } +} + +/** + * qm_reset() - reset all managed queues + * @qm: address of the queue manager to be reset + */ +void qm_reset(struct companion_queue_manager *qm) +{ + int i; + + qm_reset_io(qm); + + for (i = 0; i < BCP_CAN_PORTS; ++i) + qm_reset_can(qm, i); +} + +/** + * qm_reset_io() - reset managed IO queues + * @qm: address of the queue manager to be reset + */ +void qm_reset_io(struct companion_queue_manager *qm) +{ + kfifo_reset(&qm->io_txq.fifo); + kfifo_reset(&qm->io_rxq.fifo); +} + +/** + * qm_reset_can() - reset managed CAN queues + * @qm: address of the queue manager to be reset + * @port: port number of which CAN queue should be reset + */ +void qm_reset_can(struct companion_queue_manager *qm, u8 port) +{ + int i; + + for (i = 0; i < BCP_CAN_PRIOS; ++i) + kfifo_reset(&qm->can_txq[port][i].fifo); + + kfifo_reset(&qm->can_rxq[port].fifo); + kfifo_reset(&qm->can_err[port].fifo); +} + +/** + * qm_has_tx_data() - return true if has tx data + * @qm: address of the queue manager to be used + */ +bool qm_has_tx_data(struct companion_queue_manager *qm) +{ + int i, j; + + for (i = 0; i < BCP_CAN_PORTS; ++i) + for (j = 0; j < BCP_CAN_PRIOS; ++j) + if (!kfifo_is_empty(&qm->can_txq[i][j].fifo)) + return true; + + return !kfifo_is_empty(&qm->io_txq.fifo); +} + +/* + * Define maximum CAN packets can be sent in a row in case there is IO packet + * pending or coming, which specifies the minimal bandwidth for IO packets. + */ +#define CAN_MAX_IN_A_ROW 8 + +/** + * qm_get_tx_data() - return true if got the tx data + * @qm: address of the queue manager to be used + * @p: where the data to be copied + */ +bool qm_get_tx_data(struct companion_queue_manager *qm, + struct companion_packet *p) +{ + int i, j; + + /* + * Implement the companion packet scheduling algorithm which guarantees + * IO packets share minimal 1 / (CAN_MAX_IN_A_ROW + 1) bandwidth, and + * the rest bandwidth is shared equally for all CAN ports. + * + * The purpose is to ensure fairness between all CAN ports and also keep + * CAN packets have higher priority than IO packets in general, but + * avoid IO packets starvation in case CAN is very busy. + * + * The bandwidth is not statically allocated, so the active user (IO or + * CAN) can use up to 100% bandwidth if there are no other active users. + */ + + if (qm->io_promoted && qm_io_txq_out(qm, p)) { + qm->io_promoted = false; + return true; + } + + for (i = 0; i < BCP_CAN_PORTS; ++i) { + /* ensure fairness for all can ports */ + qm->can_current++; + if (qm->can_current >= BCP_CAN_PORTS) + qm->can_current = 0; + + for (j = 0; j < BCP_CAN_PRIOS; ++j) { + if (qm_can_txq_out(qm, p, qm->can_current, j)) { + qm->can_sched++; + if (qm->can_sched >= CAN_MAX_IN_A_ROW) { + qm->io_promoted = true; + qm->can_sched = 0; + } + return true; + } + } + } + + return qm_io_txq_out(qm, p); +} diff --git a/drivers/spi/companion/queue-manager.h b/drivers/spi/companion/queue-manager.h new file mode 100644 index 0000000..aa5d31f --- /dev/null +++ b/drivers/spi/companion/queue-manager.h @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Companion queue management code + * + * Copyright (C) 2015-2018 Bosch Sicherheitssysteme GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _BOSCH_COMPANION_QUEUE_MANAGEMENT_H +#define _BOSCH_COMPANION_QUEUE_MANAGEMENT_H + +#include +#include +#include "protocol.h" + +#define QUEUE_SIZE 16u + +/** + * struct companion_queue - encapsulate kfifo as companion queue + * @fifo: the kfifo object for companion packets + */ +struct companion_queue { + DECLARE_KFIFO(fifo, struct companion_packet, QUEUE_SIZE); +}; + +/** + * struct companion_queue_manager - manage all queues for companion + * @io_txq: the tx queue for IO messages + * @io_rxq: the rx queue for IO messages + * @can_txq: the tx queues for CAN messages + * @can_rxq: the rx queues for CAN messages + * @can_err: the queues for CAN error messages + * @can_txq_lock: lock for protect CAN tx queue 0 + * @io_promoted: flag to indicate promoted IO messages priority + * @can_current: the currently scheduled CAN port + * @can_sched: counter of how many times CAN messages be scheduled + */ +struct companion_queue_manager { + struct companion_queue io_txq; + struct companion_queue io_rxq; + struct companion_queue can_txq[BCP_CAN_PORTS][BCP_CAN_PRIOS]; + struct companion_queue can_rxq[BCP_CAN_PORTS]; + struct companion_queue can_err[BCP_CAN_PORTS]; + struct mutex can_txq_lock[BCP_CAN_PORTS]; + + bool io_promoted; + u8 can_current; + u32 can_sched; +}; + +/** + * qm_init() - initialize all managed queues + * @qm: address of the queue manager to be initialized + */ +void qm_init(struct companion_queue_manager *qm); + +/** + * qm_reset() - reset all managed queues + * @qm: address of the queue manager to be reset + */ +void qm_reset(struct companion_queue_manager *qm); + +/** + * qm_reset_io() - reset managed IO queues + * @qm: address of the queue manager to be reset + */ +void qm_reset_io(struct companion_queue_manager *qm); + +/** + * qm_reset_can() - reset managed CAN queues + * @qm: address of the queue manager to be reset + * @port: port number of which CAN queue should be reset + */ +void qm_reset_can(struct companion_queue_manager *qm, u8 port); + +/** + * qm_has_tx_data() - return true if has tx data + * @qm: address of the queue manager to be used + */ +bool qm_has_tx_data(struct companion_queue_manager *qm); + +/** + * qm_get_tx_data() - return true if got the tx data + * @qm: address of the queue manager to be used + * @p: where the data to be copied + */ +bool qm_get_tx_data(struct companion_queue_manager *qm, + struct companion_packet *p); + +/** + * qm_io_txq_is_full() - return true if IO tx queue is full + * @qm: address of the queue manager to be used + */ +static inline bool qm_io_txq_is_full(struct companion_queue_manager *qm) +{ + return kfifo_is_full(&qm->io_txq.fifo); +} + +/** + * qm_io_rxq_is_empty() - return true if IO rx queue is empty + * @qm: address of the queue manager to be used + */ +static inline bool qm_io_rxq_is_empty(struct companion_queue_manager *qm) +{ + return kfifo_is_empty(&qm->io_rxq.fifo); +} + +/** + * qm_io_txq_in() - put data from user sapce into IO tx queue + * @qm: address of the queue manager to be used + * @buf: address of the data to be put + * @count: number of bytes to be put + * @copied: address to store the number of copied bytes + */ +static inline int qm_io_txq_in(struct companion_queue_manager *qm, + const char __user *buf, + size_t count, + unsigned int *copied) +{ + return kfifo_from_user(&qm->io_txq.fifo, buf, count, copied); +} + +/** + * qm_io_txq_out() - get data from the IO tx queue + * @qm: address of the queue manager to be used + * @p: address of the data to be copied + */ +static inline bool qm_io_txq_out(struct companion_queue_manager *qm, + struct companion_packet *p) +{ + return kfifo_out(&qm->io_txq.fifo, p, 1) == 1; +} + +/** + * qm_io_rxq_in() - put data into IO rx queue + * @qm: address of the queue manager to be used + * @p: address of the data to be put + */ +static inline bool qm_io_rxq_in(struct companion_queue_manager *qm, + const struct companion_packet *p) +{ + return kfifo_in(&qm->io_rxq.fifo, p, 1) == 1; +} + +/** + * qm_io_rxq_out() - copy data from the IO rx queue into user space + * @qm: address of the queue manager to be used + * @buf: address of the data to be copied + * @count: number of the bytes to be copied + * @copied: address to store the number of copied bytes + */ +static inline int qm_io_rxq_out(struct companion_queue_manager *qm, + char __user *buf, + size_t count, + unsigned int *copied) +{ + return kfifo_to_user(&qm->io_rxq.fifo, buf, count, copied); +} + +/** + * qm_can_txq_in() - put data into CAN tx queue + * @qm: address of the queue manager to be used + * @p: address of the data to be put + * @port: port number of which CAN queue array to be put + * @prio: priority of which CAN queue to be put + */ +static inline bool qm_can_txq_in(struct companion_queue_manager *qm, + const struct companion_packet *p, + u8 port, + u8 prio) +{ + bool result = false; + + if (prio > 0) + return kfifo_in(&qm->can_txq[port][prio].fifo, p, 1) == 1; + + /* queue 0 has multiple writers due to it sends both data and + * administrative frames, while queue 1-7 only send data frame + * (single writer), hence only queue 0 needs lock. + */ + mutex_lock(&qm->can_txq_lock[port]); + result = (kfifo_in(&qm->can_txq[port][prio].fifo, p, 1) == 1); + mutex_unlock(&qm->can_txq_lock[port]); + return result; +} + +/** + * qm_can_txq_out() - get data from the CAN tx queue + * @qm: address of the queue manager to be used + * @p: address of the data to be copied + * @port: port number of which CAN queue array to be copied + * @prio: priority of which CAN queue to be copied + */ +static inline bool qm_can_txq_out(struct companion_queue_manager *qm, + struct companion_packet *p, + u8 port, + u8 prio) +{ + return kfifo_out(&qm->can_txq[port][prio].fifo, p, 1) == 1; +} + +/** + * qm_can_rxq_in() - put data into CAN rx queue + * @qm: address of the queue manager to be used + * @p: address of the data to be put + * @port: port number of which CAN queue to be put + */ +static inline bool qm_can_rxq_in(struct companion_queue_manager *qm, + const struct companion_packet *p, + u8 port) +{ + return kfifo_in(&qm->can_rxq[port].fifo, p, 1) == 1; +} + +/** + * qm_can_rxq_out() - get data from the CAN rx queue + * @qm: address of the queue manager to be used + * @p: address of the data to be copied + * @port: port number of which CAN queue to be copied + */ +static inline bool qm_can_rxq_out(struct companion_queue_manager *qm, + struct companion_packet *p, + u8 port) +{ + return kfifo_out(&qm->can_rxq[port].fifo, p, 1) == 1; +} + +static inline bool qm_can_err_in(struct companion_queue_manager *qm, + const struct companion_packet *p, + u8 port) +{ + return kfifo_in(&qm->can_err[port].fifo, p, 1) == 1; +} + +static inline bool qm_can_err_out(struct companion_queue_manager *qm, + struct companion_packet *p, + u8 port) +{ + return kfifo_out(&qm->can_err[port].fifo, p, 1) == 1; +} + +#endif diff --git a/include/linux/companion.h b/include/linux/companion.h new file mode 100644 index 0000000..fdc6258 --- /dev/null +++ b/include/linux/companion.h @@ -0,0 +1,259 @@ +/* + * Companion low level driver interface + * + * Copyright (C) 2017 Bosch Sicherheitssysteme GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _BOSCH_COMPANION_H +#define _BOSCH_COMPANION_H + +#include +#include + +#define COMPANION_PACKET_SIZE 16 + +/** + * struct companion_io_ops - callbacks of companion IO packets handling + * @on_tx_done: called when IO packets tx is done + * @on_rx_done: called when IO packets rx is done + */ +struct companion_io_ops { + void (*on_tx_done)(void *data); + void (*on_rx_done)(void *data); +}; + +/** + * struct companion_can_ops - callbacks of companion CAN packets handling + * @on_tx_done: called when CAN packets tx is done + * @on_rx_done: called when CAN packets rx is done + * @on_error: called when CAN error detected + * @on_tx_timeout: called when CAN packets tx timeout + */ +struct companion_can_ops { + void (*on_tx_done)(void *data, u8 prio, bool lost_seq, bool success); + void (*on_rx_done)(void *data); + void (*on_error)(void *data); + void (*on_tx_timeout)(void *data, u8 prio); +}; + +/** + * companion_io_ops_register() - register companion IO packets handler + * @parent: address of the caller parent device to be registered + * @ops: address of the IO packets callback + * @data: address of the IO packets callback argument + */ +int companion_io_ops_register(struct device *parent, + struct companion_io_ops *ops, + void *data); + +/** + * companion_io_ops_unregister() - unregister companion IO packets handler + * @parent: address of the caller parent device to be unregistered + */ +int companion_io_ops_unregister(struct device *parent); + +/** + * companion_can_ops_register() - register companion CAN packets handler + * @parent: address of the caller parent device to be registered + * @ops: address of the CAN packets callback + * @data: address of the CAN packets callback argument + */ +int companion_can_ops_register(struct device *parent, + u8 port, + struct companion_can_ops *ops, + void *data); + +/** + * companion_can_ops_unregister() - unregister comapnion CAN packets handler + * @parent: address of the caller parent device to be unregistered + * @port: port number of which CAN to be unregistered + */ +int companion_can_ops_unregister(struct device *parent, u8 port); + +/** + * companion_io_txq_is_full() - return true if IO tx queue is full + * @parent: address of the caller parent device to be used + */ +bool companion_io_txq_is_full(struct device *parent); + +/** + * companion_io_rxq_is_empty() - return true if IO rx queue is empty + * @parent: address of the caller parent device to be used + */ +bool companion_io_rxq_is_empty(struct device *parent); + +/** + * companion_do_io_tx() - send IO packets from user space to companion + * @parent: address of the caller parent device to be used + * @buf: address of the user space data to be sent + * @count: number of bytes to be sent + */ +int companion_do_io_tx(struct device *parent, + const char __user *buf, + size_t count); + +/** + * companion_do_io_rx() - receive IO packets from companion to user space + * @parent: address of the caller parent device to be used + * @buf: address of the data to be copied + * @count: number of bytes to be copied + */ +int companion_do_io_rx(struct device *parent, + char __user *buf, + size_t count); + +/** + * companion_do_can_tx() - send CAN data to companion + * @parent: address of the caller parent device to be used + * @port: port number of which CAN to be sent + * @prio: priority of the raw CAN frame to be sent + * @cf: address of the raw CAN frame to be sent + */ +int companion_do_can_tx(struct device *parent, + u8 port, + u8 prio, + const struct can_frame *cf); + +/** + * companion_do_can_rx() - receive CAN data from companion + * @parent: address of the caller parent device to be used + * @port: port number of which CAN to be received + * @cf: address of the raw CAN frame to be copied + */ +int companion_do_can_rx(struct device *parent, + u8 port, + struct can_frame *cf); + +/** + * companion_do_can_err() - receive CAN error from companion + * @parent: address of the caller parent device to be used + * @port: port number of which CAN to be received + * @bec: address of the error counter to be copied + * @state: address of the error state to be copied + * @code: address of the error code to be copied + */ +int companion_do_can_err(struct device *parent, + u8 port, + struct can_berr_counter *bec, + u8 *state, + u8 *code); + +#define COMPANION_CAN_STATE_WARNING BIT(0) +#define COMPANION_CAN_STATE_PASSIVE BIT(1) +#define COMPANION_CAN_STATE_BUS_OFF BIT(2) +#define COMPANION_CAN_ERROR_STUFF BIT(0) +#define COMPANION_CAN_ERROR_FORM BIT(1) +#define COMPANION_CAN_ERROR_ACK BIT(2) +#define COMPANION_CAN_ERROR_BIT1 BIT(3) +#define COMPANION_CAN_ERROR_BIT0 BIT(4) +#define COMPANION_CAN_ERROR_CRC BIT(5) +#define COMPANION_CAN_ERROR_RXOV BIT(7) + +/** + * companion_do_set_can_bittiming() - set companion CAN bittiming + * @parent: address of the caller parent device to be used + * @port: port number of which CAN to be set + * @bittiming: the bittiming to be set + */ +int companion_do_set_can_bittiming(struct device *parent, + u8 port, + const struct can_bittiming *bittiming); + +/** + * companion_do_set_can_mode() - set companion CAN mode + * @parent: address of the caller parent device to be used + * @port: port number of which CAN to be set + * @mode: the mode to be set + */ +int companion_do_set_can_mode(struct device *parent, + u8 port, + enum can_mode mode); + +/** + * companion_do_set_can_ctrlmode() - set companion CAN control mode + * @parent: address of the caller parent device to be used + * @port: port number of which CAN to be set + * @ctrl: the control mode to be set + */ +int companion_do_set_can_ctrlmode(struct device *parent, + u8 port, + u32 ctrl); + +/** + * companion_do_get_can_status() - get companion CAN status + * @parent: address of the caller parent device to be used + * @port: port number of which CAN to be inquiry + * @bec: address of the error counter to be copied + */ +int companion_do_get_can_status(struct device *parent, + u8 port, + struct can_berr_counter *bec); + +/** + * companion_do_get_can_txq_status() - get companion CAN tx queue status + * @parent: address of the caller parent device to be used + * @port: port number of which CAN to be inquiry + * @prio: queue number of which tx queue to be inquiry + * @lost_txq_sync: flag of the given CAN tx queue lost sync or not + */ +int companion_do_get_can_txq_status(struct device *parent, + u8 port, + u8 prio, + bool *lost_txq_sync); + +/** + * companion_do_get_can_txq_status_all() - get all companion CAN tx queue status + * @parent: address of the caller parent device to be used + * @port: port number of which CAN to be inquiry + */ +int companion_do_get_can_txq_status_all(struct device *parent, + u8 port); + +/** + * companion_do_can_txq_is_full() - inquiry companion CAN tx queue is full + * @parent: address of the caller parent device to be used + * @port: port number of which CAN to be inquiry + * @prio: queue number of which tx queue to be inquiry + * @is_full: address of the is full result to be copied + */ +int companion_do_can_txq_is_full(struct device *parent, + u8 port, + u8 prio, + bool *is_full); + +/** + * companion_do_can_txq_has_space() - inquiry companion CAN tx queue has space + * @parent: address of the caller parent device to be used + * @port: port number of which CAN to be inquiry + * @prio: queue number of which tx queue to be inquiry + * @has_space: address of the has_space result to be copied + */ +int companion_do_can_txq_has_space(struct device *parent, + u8 port, + u8 prio, + bool *has_space); + +/** + * companion_do_can_start_tx_timer() - start companioin CAN tx timeout detection + * @parent: address of the caller parent device to be used + * @port: port number of which CAN to be used + * @prio: queue number of which tx queue's timer to be used + */ +int companion_do_can_start_tx_timer(struct device *parent, + u8 port, + u8 prio); + +/** + * companion_do_can_stop_tx_timer() - stop companion CAN tx timeout detection + * @parent: address of the caller parent device to be used + * @port: port number of which CAN to be used + * @prio: queue number of which tx queue's timer to be used + */ +int companion_do_can_stop_tx_timer(struct device *parent, + u8 port, + u8 prio); +#endif From patchwork Wed Jun 13 14:37:19 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Jonas Mark (BT-FIR/ENG1-Grb)" X-Patchwork-Id: 928906 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=de.bosch.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=de.bosch.com header.i=@de.bosch.com header.b="L9Eexou2"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 415TtS3CY6z9s19 for ; Thu, 14 Jun 2018 00:40:00 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S935929AbeFMOjn (ORCPT ); Wed, 13 Jun 2018 10:39:43 -0400 Received: from de-out1.bosch-org.com ([139.15.230.186]:33662 "EHLO de-out1.bosch-org.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S935815AbeFMOiR (ORCPT ); Wed, 13 Jun 2018 10:38:17 -0400 Received: from fe0vm1650.rbesz01.com (unknown [139.15.230.188]) by fe0vms0187.rbdmz01.com (Postfix) with ESMTPS id 415TrR612Jz1XLFYq; Wed, 13 Jun 2018 16:38:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=de.bosch.com; s=2015-01-21; t=1528900695; bh=xKWvSJAExCFYwQPkF9cpeARYs1KgGM4+MQ0/Lt2hetU=; l=10; h=From:From:Reply-To:Sender; b=L9Eexou2SEqSzfQnYDeyVUSs/i2QSsgFVskCSKzJs0NJnsO/dHJXkVYYKXGJA0rMh 1Ct/cvyXHYNXI3UgR13fagg83L4oNbDslVVftKM77RBTfXZbIgA/pwLDERXdrQiBNS 3g0v+geWHxgNA4MAp0xGtZGOM4tUWyqMenyAFZGY= Received: from si0vm2083.rbesz01.com (unknown [10.58.172.176]) by fe0vm1650.rbesz01.com (Postfix) with ESMTPS id 415TrR5LPlz1Cd; Wed, 13 Jun 2018 16:38:15 +0200 (CEST) X-AuditID: 0a3aad17-513ff70000000e32-da-5b212c585488 Received: from fe0vm1652.rbesz01.com ( [10.58.173.29]) (using TLS with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by si0vm2083.rbesz01.com (SMG Outbound) with SMTP id 8C.4A.03634.85C212B5; Wed, 13 Jun 2018 16:38:16 +0200 (CEST) Received: from SI-HUB1000.de.bosch.com (si-hub1000.de.bosch.com [10.4.103.106]) by fe0vm1652.rbesz01.com (Postfix) with ESMTPS id 415TrR2wDBzBpBV; Wed, 13 Jun 2018 16:38:15 +0200 (CEST) Received: from luchador.grb-fir.grb.de.bosch.com (10.19.187.97) by SI-HUB1000.de.bosch.com (10.4.103.106) with Microsoft SMTP Server id 14.3.319.2; Wed, 13 Jun 2018 16:38:15 +0200 From: Mark Jonas To: Wolfgang Grandegger , Marc Kleine-Budde CC: , , , , , , , , , , Mark Jonas Subject: [PATCH v2 3/5] char: implement companion-char driver Date: Wed, 13 Jun 2018 16:37:19 +0200 Message-ID: <1528900641-18677-4-git-send-email-mark.jonas@de.bosch.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1528900641-18677-1-git-send-email-mark.jonas@de.bosch.com> References: <1528224240-30786-1-git-send-email-mark.jonas@de.bosch.com> <1528900641-18677-1-git-send-email-mark.jonas@de.bosch.com> MIME-Version: 1.0 X-Brightmail-Tracker: H4sIAAAAAAAAA22Sf0xbVRTHuX2v7WvludcW1rMi071ki2G/OubMU4dBTbSby2Zctsw1uj3G k1b7g/QVhMXMGqmkuDGWgbaFETZdRpYtZCCxZUJipYBiMKLMdrgSHUsKzE1ks4gBfY/C2j/8 79zzPZ/zPffcS2DqYbmOMNucnMPGWmiZElc+fTl344ENa4z6rv7tzETd14gJ/DKGMRcTDRjz Y1eTjGk7V48zfS0rmfEYYiKTC3LmfCwoLVQYmv0DuCHovyE3TA+PIsMH//bKDSfn9YaZ9tWv yA4qtxdzFnM559j87GGl6WTiJ7zU9UZF981OzIU8u2qQggDqCZgfiuBirKa8Erg2v6MGKYW4 G8H0WBQlD18gCPfGpGKVjMqD6EAAE+Msah803voQF4swqkcCo90fIVHQUAXgfv/KIoBTayF2 /cKiBUm9BGeq/5EkrVdDdMiz2EhBGaCzqxklx3AjGAnvT9ar4Bvf+CKLUQBfxeNYHVrhT5P8 aVILklxE2bxZX27N1zNbNzmKOP6ofsumI3ZrO0ouWhtA966wIUQRiM4k33t0jVEtZcv5SmsI bSMkdDa5u+4xo/rhIntxpYnlTYccZRaOp3UkysjIUGsepPmyIquZ5812WwgBgdFZ5O64wJHF bOVRzmFPYiGUQ+C0ljxe/O5BNVXCOrm3Oa6UcyyrzxAEDSS/XphB5eBKuIo3zRbnskznJj1X pivpthJCEUJbiUzBu0dsQfKlrJU3lyzhq5K4ejmbQr9FLxALd+uPY2rcZrdxOi3ZlyfwlFhp KrM9mED3CGn8XLhUdpqQ6jKJRpCwQw05KMKZwn9OeQMZ0DS9plYtJVNQ/lmBoXpy4HqEhNjd HPBPm+FOyzFoP1WF4LNoI4LGsWEEzYH7COKzCeEPTrVJ4Hb0qgQmbjThMFPVikNkMohDbagP B2+3WwoR12kpDNyul0HViTkZTPimCGg/MaoQMSWMNEwpYfa7v5QQHPjzIbjk6yXBe22GhKDn 3AoY+uR7FSx4z2igtalZA77aTs2ksFSJsNRwWHxQ3sk6/2epS9nUzXQu9OlM/825tj92UPEN dQWPF+xd/3vteOXLO6/+vfP1F+9VF6oSLe6w/Ni6rOnB57TbVrFa1nCnAw3Sty7593nyS/DE 832+8RpFza8FT1Zbyc2XLd7IkUHjqdMbC7PP57564cuuip+f8g43VGs/nr3vXtijdb1FHD7k eae17beOH87u7+ifo3HexG7Jwxw8+x9mGJFdZQQAAA== Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Zhu Yi The upper level companion-char driver provides character device interface to userspace for communicate IO messages with the companion processor. Signed-off-by: Zhu Yi Signed-off-by: Mark Jonas --- drivers/char/Kconfig | 7 + drivers/char/Makefile | 3 + drivers/char/companion.c | 360 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 370 insertions(+) create mode 100644 drivers/char/companion.c diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index c28dca0..e878d56 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -588,5 +588,12 @@ config TILE_SROM source "drivers/char/xillybus/Kconfig" +config COMPANION_CHAR + tristate "Character device for companion communication (Bosch)" + depends on COMPANION_SPI + help + The character device allows the userspace to exchange IO messages + with the Bosch companion processor via the companion SPI driver. + endmenu diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 7dc3abe..0dae572 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -60,3 +60,6 @@ js-rtc-y = rtc.o obj-$(CONFIG_TILE_SROM) += tile-srom.o obj-$(CONFIG_XILLYBUS) += xillybus/ obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o + +obj-$(CONFIG_COMPANION_CHAR) += companion-char.o +companion-char-objs := companion.o diff --git a/drivers/char/companion.c b/drivers/char/companion.c new file mode 100644 index 0000000..2e26f01 --- /dev/null +++ b/drivers/char/companion.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Companion upper level character device + * + * Copyright (C) 2015-2018 Bosch Sicherheitssysteme GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "companion-char" + +static struct class *companion_char_class; +static dev_t devt; + +/** + * struct companion_char_minor - companion-char minor structure + * @dev: address of the associated device + * @writelock: mutex to protect write + * @readlock: mutex to protect read + * @writewait: wait queue head of write + * @readwait: wait queue head of read + */ +struct companion_char_minor { + struct device *dev; + struct mutex writelock; + struct mutex readlock; + wait_queue_head_t writewait; + wait_queue_head_t readwait; +}; + +/** + * struct companion_char_priv - companion-char private data structure + * @cdev: char device + * @parent: address of the associated parent device + * @minors: address of the companion-char minor + */ +struct companion_char_priv { + struct cdev cdev; + struct device *parent; + struct companion_char_minor *minors; +}; + +/** + * companion_char_read() - read callback + * @filp: address of the associated virtual file + * @buf: address of the user space buffer to receive + * @count: number of bytes to read + * @offset: address of the read offset + */ +static ssize_t companion_char_read(struct file *filp, + char __user *buf, + size_t count, + loff_t *offset) +{ + unsigned int number = MINOR(file_inode(filp)->i_rdev); + struct companion_char_priv *priv = filp->private_data; + struct companion_char_minor *minor = &priv->minors[number]; + int err; + + if (count != COMPANION_PACKET_SIZE) + return -EMSGSIZE; + + if (mutex_lock_interruptible(&minor->readlock)) + return -ERESTARTSYS; + + while (companion_io_rxq_is_empty(priv->parent)) { + mutex_unlock(&minor->readlock); + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + if (wait_event_interruptible(minor->readwait, + !companion_io_rxq_is_empty(priv->parent))) + return -ERESTARTSYS; + if (mutex_lock_interruptible(&minor->readlock)) + return -ERESTARTSYS; + } + + err = companion_do_io_rx(priv->parent, buf, count); + mutex_unlock(&minor->readlock); + return err; +} + +/** + * companion_char_write() - write callback + * @filp: address of the associated virtual file + * @buf: address of the user space buffer to transfer + * @count: number of bytes to write + * @offset: address of the write offset + */ +static ssize_t companion_char_write(struct file *filp, + const char __user *buf, + size_t count, + loff_t *offset) +{ + unsigned int number = MINOR(file_inode(filp)->i_rdev); + struct companion_char_priv *priv = filp->private_data; + struct companion_char_minor *minor = &priv->minors[number]; + int err; + + if (count != COMPANION_PACKET_SIZE) + return -EMSGSIZE; + + if (mutex_lock_interruptible(&minor->writelock)) + return -ERESTARTSYS; + + while (companion_io_txq_is_full(priv->parent)) { + mutex_unlock(&minor->writelock); + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + if (wait_event_interruptible(minor->writewait, + !companion_io_txq_is_full(priv->parent))) + return -ERESTARTSYS; + if (mutex_lock_interruptible(&minor->writelock)) + return -ERESTARTSYS; + } + + err = companion_do_io_tx(priv->parent, buf, count); + mutex_unlock(&minor->writelock); + return err; +} + +/** + * companion_char_poll() - poll callback + * @filp: address of the associated virtual file + * @wait: address of the associated poll table + */ +static unsigned int companion_char_poll(struct file *filp, poll_table *wait) +{ + unsigned int number = MINOR(file_inode(filp)->i_rdev); + struct companion_char_priv *priv = filp->private_data; + struct companion_char_minor *minor = &priv->minors[number]; + unsigned int mask = 0; + + poll_wait(filp, &minor->writewait, wait); + poll_wait(filp, &minor->readwait, wait); + + mutex_lock(&minor->writelock); + if (!companion_io_txq_is_full(priv->parent)) + mask |= POLLOUT | POLLWRNORM; + mutex_unlock(&minor->writelock); + + mutex_lock(&minor->readlock); + if (!companion_io_rxq_is_empty(priv->parent)) + mask |= POLLIN | POLLRDNORM; + mutex_unlock(&minor->readlock); + + return mask; +} + +/** + * companion_char_open() - open callback + * @inode: address of the associated inode + * @filp: address of the associated virtual file + */ +static int companion_char_open(struct inode *inode, struct file *filp) +{ + struct companion_char_priv *priv = container_of( + inode->i_cdev, + struct companion_char_priv, + cdev); + + filp->private_data = priv; + nonseekable_open(inode, filp); + return 0; +} + +/** + * companion_char_release() - release callback + * @inode: address of the associated inode + * @filp: address of the associated virtual file + */ +static int companion_char_release(struct inode *inode, struct file *filp) +{ + filp->private_data = NULL; + return 0; +} + +static const struct file_operations companion_char_ops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = companion_char_read, + .write = companion_char_write, + .poll = companion_char_poll, + .open = companion_char_open, + .release = companion_char_release, +}; + +/** + * companion_char_on_tx_done() - tx done callback + * @data: address of user supplied callback data + */ +static void companion_char_on_tx_done(void *data) +{ + struct companion_char_priv *priv = data; + struct companion_char_minor *minor = &priv->minors[0]; + + wake_up_interruptible(&minor->writewait); +} + +/** + * companion_char_on_rx_done() - rx done callback + * @data: address of user supplied callback data + */ +static void companion_char_on_rx_done(void *data) +{ + struct companion_char_priv *priv = data; + struct companion_char_minor *minor = &priv->minors[0]; + + wake_up_interruptible(&minor->readwait); +} + +static struct companion_io_ops companion_char_io_ops = { + .on_tx_done = companion_char_on_tx_done, + .on_rx_done = companion_char_on_rx_done, +}; + +/** + * companion_char_probe() - probe callback + * @pdev: address of the platform device + */ +static int companion_char_probe(struct platform_device *pdev) +{ + struct companion_char_priv *priv; + struct companion_char_minor *minors; + int err; + + if (!pdev->dev.parent) { + dev_err(&pdev->dev, "no parent device found\n"); + return -ENODEV; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->parent = pdev->dev.parent; + + minors = devm_kzalloc(&pdev->dev, sizeof(*minors), GFP_KERNEL); + if (!minors) + return -ENOMEM; + + minors->dev = device_create(companion_char_class, + &pdev->dev, + MKDEV(MAJOR(devt), 0), + priv, + "companion%d", + 0); + if (IS_ERR_OR_NULL(minors->dev)) + return PTR_ERR_OR_ZERO(minors->dev); + priv->minors = minors; + + mutex_init(&minors->writelock); + mutex_init(&minors->readlock); + init_waitqueue_head(&minors->writewait); + init_waitqueue_head(&minors->readwait); + + cdev_init(&priv->cdev, &companion_char_ops); + err = cdev_add(&priv->cdev, MKDEV(MAJOR(devt), 0), 1); + if (err) { + dev_err(&pdev->dev, "cdev_add() failed: %d\n", err); + goto on_error; + } + + dev_set_drvdata(&pdev->dev, priv); + + err = companion_io_ops_register(priv->parent, + &companion_char_io_ops, + priv); + if (err) { + dev_err(&pdev->dev, "companion_io_ops_register() failed: %d\n", + err); + goto on_error; + } + return 0; + +on_error: + device_destroy(companion_char_class, MKDEV(MAJOR(devt), 0)); + cdev_del(&priv->cdev); + return err; +} + +/** + * companion_char_remove() - remove callback + * @pdev: address of the platform device + */ +static int companion_char_remove(struct platform_device *pdev) +{ + struct companion_char_priv *priv = dev_get_drvdata(&pdev->dev); + + companion_io_ops_unregister(priv->parent); + device_destroy(companion_char_class, MKDEV(MAJOR(devt), 0)); + cdev_del(&priv->cdev); + return 0; +} + +static const struct of_device_id companion_char_of_match[] = { + { .compatible = "bosch,companion-char", .data = NULL, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, companion_char_of_match); + +static struct platform_driver companion_char_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(companion_char_of_match), + }, + .probe = companion_char_probe, + .remove = companion_char_remove, +}; + +/** + * companion_char_init() - module init + */ +static int __init companion_char_init(void) +{ + int err; + + companion_char_class = class_create(THIS_MODULE, DRIVER_NAME); + if (IS_ERR_OR_NULL(companion_char_class)) + return PTR_ERR_OR_ZERO(companion_char_class); + + err = alloc_chrdev_region(&devt, 0, 1, DRIVER_NAME); + if (err) { + class_destroy(companion_char_class); + return err; + } + + err = platform_driver_register(&companion_char_driver); + if (err) { + class_destroy(companion_char_class); + unregister_chrdev_region(devt, 1); + } + + return err; +} + +/** + * companion_char_exit() - module exit + */ +static void __exit companion_char_exit(void) +{ + platform_driver_unregister(&companion_char_driver); + class_destroy(companion_char_class); + unregister_chrdev_region(devt, 1); +} + +module_init(companion_char_init); +module_exit(companion_char_exit); + +MODULE_AUTHOR("Zhu Yi "); +MODULE_DESCRIPTION("Companion upper level character device"); +MODULE_LICENSE("GPL v2"); From patchwork Wed Jun 13 14:37:20 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Jonas Mark (BT-FIR/ENG1-Grb)" X-Patchwork-Id: 928904 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=de.bosch.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=de.bosch.com header.i=@de.bosch.com header.b="hkMzT4/J"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 415Tsj0nTMz9s19 for ; Thu, 14 Jun 2018 00:39:21 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S935893AbeFMOir (ORCPT ); Wed, 13 Jun 2018 10:38:47 -0400 Received: from de-out1.bosch-org.com ([139.15.230.186]:33890 "EHLO de-out1.bosch-org.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S935818AbeFMOiS (ORCPT ); Wed, 13 Jun 2018 10:38:18 -0400 Received: from si0vm1948.rbesz01.com (unknown [139.15.230.188]) by si0vms0217.rbdmz01.com (Postfix) with ESMTPS id 415TrT05RMz4f3lwq; Wed, 13 Jun 2018 16:38:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=de.bosch.com; s=2015-01-21; t=1528900697; bh=xKWvSJAExCFYwQPkF9cpeARYs1KgGM4+MQ0/Lt2hetU=; l=10; h=From:From:Reply-To:Sender; b=hkMzT4/Jcl+9EBh0ZdB3+S+M23K+QHsBdXbGWY8WrjYnMM4P/zEyUs5Ck3ceeVXq8 5iOOYphx8uG89S8sSJvTWjvEJdsTMk6jFd3B8a4L0dQIFAad8l7TUKcd90BYac9R2I 9Jo5pUvYP3lYWiTJWSqxKWmhhib7X78DXMWPRXAM= Received: from si0vm2083.rbesz01.com (unknown [10.58.172.176]) by si0vm1948.rbesz01.com (Postfix) with ESMTPS id 415TrS6nF0z4fj; Wed, 13 Jun 2018 16:38:16 +0200 (CEST) X-AuditID: 0a3aad17-513ff70000000e32-e2-5b212c5ac912 Received: from si0vm1949.rbesz01.com ( [10.58.173.29]) (using TLS with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by si0vm2083.rbesz01.com (SMG Outbound) with SMTP id 7E.4A.03634.A5C212B5; Wed, 13 Jun 2018 16:38:18 +0200 (CEST) Received: from SI-HUB1000.de.bosch.com (si-hub1000.de.bosch.com [10.4.103.106]) by si0vm1949.rbesz01.com (Postfix) with ESMTPS id 415TrS47pfz6D44M7; Wed, 13 Jun 2018 16:38:16 +0200 (CEST) Received: from luchador.grb-fir.grb.de.bosch.com (10.19.187.97) by SI-HUB1000.de.bosch.com (10.4.103.106) with Microsoft SMTP Server id 14.3.319.2; Wed, 13 Jun 2018 16:38:15 +0200 From: Mark Jonas To: Wolfgang Grandegger , Marc Kleine-Budde CC: , , , , , , , , , , Mark Jonas Subject: [PATCH v2 4/5] can: implement companion-can driver Date: Wed, 13 Jun 2018 16:37:20 +0200 Message-ID: <1528900641-18677-5-git-send-email-mark.jonas@de.bosch.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1528900641-18677-1-git-send-email-mark.jonas@de.bosch.com> References: <1528224240-30786-1-git-send-email-mark.jonas@de.bosch.com> <1528900641-18677-1-git-send-email-mark.jonas@de.bosch.com> MIME-Version: 1.0 X-Brightmail-Tracker: H4sIAAAAAAAAA22Sf0xbVRTHue+9vr42vO3xGOPYuSk1+0NQVjbNnmMxS1xi1QRxypJBVB7y pI39gX2FDGKUOqYbdsKEKetIR7ao2YawdZstm+2wo5U5I4KS2TCKJmMWNiUdYwMn6CuFtX/4 z82553s+53vvuZfC2StyFaU3WQWLiTeoSSWh3PTV6seLH8sq0bh9Cm686SLiPFdHce743QM4 9/O5NpLrOtJCcMH2ldy1MOJ+nZiXc5+Hu2VbFFqno4/QdjtG5Nro4DDS7vq3V65tnNNop1xr Csli5eZywaCvFizrni5V6npnbfLK4Am0s/nYX/I6FKhHDUhBAfME3BzqIRqQkmKZVgx6Aj+Q 8c0FBKO2qcWNG0GgNyyLISSTDaE+Dx6LVzBFcGjsgwUcZ3wYDHs/WuibzuRD/WRIHosJZi1c j7SRsZhmngV7YwsZ914DoR/3LjRSMFo4e865wLLMbgRDge3x+jS4dPAaEYtxBuDbSARvQssd SZIjSWpH2HGUIeo11cb1Gm5DrqVMEGs1eblvmI0uFJ91pgfdPsX7EUMhdSr93kNZJayMrxZr jH70JIWpM+iCpodL2GVl5vIaHS/qXrdUGQRRraJRSkoKm34/LVaVGfWiqDeb/AgoXL2CLohI HF3O19QKFnMc86NVFKHOpO3l7xSzTAVvFd4ShErBsqTmU5QaaDFHOkOaRagQdr6pN1iXZPXq uOfKZCXZFqMUfrSBSpW8fbEWtFjJG0V9xSL+QBxnl7IJ9Hv0DDU/2WLHWcJkNgmqTDqYLfFM rFJXZbp/AtWDdMkZ6VIZSUKiywS6gqQZptOXY3Cq9KUT3kB70tt2sGmLyQS0/qjEMP15EJ5c Bf/s3wTO+Q4Eod9OIzjaP4jg0Ki0uIYjCJyeaQTRG10YTO1xYXAzdB6DbyIBDGZuTWPQOn0P h2NXbQQcvmQnYKaumQDX/pME9P19ioDf//AR0Dk4QkCrd7dMKmmXQeO0Wwa3/7wnA+dPzSSM ngmSMDXQJQenb5CC8YM3KHBE71Bg3zdLTUhjxaSxBgKxJxWtvPV/xrqYTdxNVYdevjtzonbH 9bH6odpo97bv+jc/dbGoI80517kn71FbwQXt8ugjLzYosvd1vBoq8gYqOl9QGZblfrnRBoVl PXdem1g3N4DGtux6qXVv5fPb8sksr3d87elXSs+7bp3Ef8npdo98MvCu/9Oc0vDG7cGzqHTk ua8/dhd+eHnrF29v7frMM5v7vpoQdXxeNm4R+f8Ai/+7WWoEAAA= Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Zhu Yi The upper level companion-can driver provides SocketCAN interface to userspace for communicate CAN messages with the companion processor. Signed-off-by: Zhu Yi Signed-off-by: Mark Jonas --- drivers/net/can/Kconfig | 8 + drivers/net/can/Makefile | 2 + drivers/net/can/companion.c | 693 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 703 insertions(+) create mode 100644 drivers/net/can/companion.c diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index ac4ff39..e403a7e 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -155,6 +155,14 @@ config PCH_CAN is an IOH for x86 embedded processor (Intel Atom E6xx series). This driver can access CAN bus. +config COMPANION_CAN + tristate "Network device for companion communication (Bosch)" + depends on COMPANION_SPI + ---help--- + The network device allows the userspace to use SocketCAN interface + to communicate with the Bosch companion processor via the companion + SPI driver. + source "drivers/net/can/c_can/Kconfig" source "drivers/net/can/cc770/Kconfig" source "drivers/net/can/ifi_canfd/Kconfig" diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile index 02b8ed7..575ea63 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -34,5 +34,7 @@ obj-$(CONFIG_CAN_SUN4I) += sun4i_can.o obj-$(CONFIG_CAN_TI_HECC) += ti_hecc.o obj-$(CONFIG_CAN_XILINXCAN) += xilinx_can.o obj-$(CONFIG_PCH_CAN) += pch_can.o +obj-$(CONFIG_COMPANION_CAN) += companion-can.o +companion-can-objs := companion.o subdir-ccflags-$(CONFIG_CAN_DEBUG_DEVICES) += -DDEBUG diff --git a/drivers/net/can/companion.c b/drivers/net/can/companion.c new file mode 100644 index 0000000..0702d24 --- /dev/null +++ b/drivers/net/can/companion.c @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Companion upper level can network device + * + * Copyright (C) 2015-2018 Bosch Sicherheitssysteme GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define TX_QUEUE_DEPTH 16 +#define NUM_TX_QUEUES 8 +#define NUM_RX_QUEUES 1 +#define TX_ECHO_SKB_MAX (NUM_TX_QUEUES * TX_QUEUE_DEPTH) +#define DRIVER_NAME "companion-can" + +/** + * struct companion_can_priv - companion-can private data structure + * @can: standard common CAN private data, must be first member + * @parent: address of the associated parent device + * @dev: address of the associated network device + * @port: the companion CAN port number + * @tx_head: array of all tx queue head + * @tx_tail: arrat of all tx queue tail + */ +struct companion_can_priv { + struct can_priv can; + struct device *parent; + struct net_device *dev; + u8 port; + u8 tx_head[NUM_TX_QUEUES]; + u8 tx_tail[NUM_TX_QUEUES]; +}; + +/** + * companion_can_put_echo_skb() - put echo skb into ring buffer + * @priv: address of companion-can private data + * @prio: which CAN queue to put + * @skb: address of the packet to put + */ +static void companion_can_put_echo_skb(struct companion_can_priv *priv, + u8 prio, + struct sk_buff *skb) +{ + u8 offset = prio * TX_QUEUE_DEPTH; + u8 index = priv->tx_head[prio] % TX_QUEUE_DEPTH; + + can_put_echo_skb(skb, priv->dev, offset + index); + priv->tx_head[prio]++; +} + +/** + * companion_can_get_echo_skb() - get echo skb from ring buffer + * @priv: address of companion-can private data + * @prio: which CAN queue to get + */ +static u8 companion_can_get_echo_skb(struct companion_can_priv *priv, u8 prio) +{ + u8 offset, index, result = 0; + + if (priv->tx_head[prio] != priv->tx_tail[prio]) { + offset = prio * TX_QUEUE_DEPTH; + index = priv->tx_tail[prio] % TX_QUEUE_DEPTH; + result = can_get_echo_skb(priv->dev, offset + index); + priv->tx_tail[prio]++; + } + return result; +} + +/** + * companion_can_free_echo_skb() - free echo skb from ring buffer + * @priv: address of companion-can private data + * @prio: which CAN queue to free + */ +static void companion_can_free_echo_skb(struct companion_can_priv *priv, + u8 prio) +{ + u8 offset, index; + + if (priv->tx_head[prio] != priv->tx_tail[prio]) { + offset = prio * TX_QUEUE_DEPTH; + index = priv->tx_tail[prio] % TX_QUEUE_DEPTH; + can_free_echo_skb(priv->dev, offset + index); + priv->tx_tail[prio]++; + } +} + +/** + * companion_can_set_bittiming() - set CAN bittiming + * @dev: address of the associated network device + */ +static int companion_can_set_bittiming(struct net_device *dev) +{ + struct companion_can_priv *priv = netdev_priv(dev); + const struct can_bittiming *bt = &priv->can.bittiming; + u32 ctrl = priv->can.ctrlmode; + int err; + + err = companion_do_set_can_bittiming(priv->parent, priv->port, bt); + if (err) + return err; + + if (ctrl & CAN_CTRLMODE_LISTENONLY) { + err = companion_do_set_can_ctrlmode(priv->parent, + priv->port, + ctrl); + if (err) + return err; + } + return 0; +} + +/** + * companion_can_set_mode() - set CAN mode + * @dev: address of the associated network device + * @mode: the CAN mode to set + */ +static int companion_can_set_mode(struct net_device *dev, enum can_mode mode) +{ + struct companion_can_priv *priv = netdev_priv(dev); + int err; + + switch (mode) { + case CAN_MODE_START: + err = companion_can_set_bittiming(dev); + if (err) + return err; + /* fall through */ + + case CAN_MODE_STOP: + err = companion_do_set_can_mode(priv->parent, + priv->port, + mode); + if (err) + return err; + break; + + default: + return -EOPNOTSUPP; + } + return 0; +} + +/** + * companion_can_get_berr_counter() - get CAN error counter + * @dev: address of the associated network device + * @bec: address of the CAN error counter to store + */ +static int companion_can_get_berr_counter(const struct net_device *dev, + struct can_berr_counter *bec) +{ + struct companion_can_priv *priv = netdev_priv(dev); + + return companion_do_get_can_status(priv->parent, priv->port, bec); +} + +/** + * companion_can_handle_state() - handle CAN state transition + * @dev: address of the associated network device + * @cf: address of the CAN frame to store CAN state + * @bec: address of the CAN error counter + * @state: the companion CAN state + */ +static void companion_can_handle_state(struct net_device *dev, + struct can_frame *cf, + struct can_berr_counter *bec, + u8 state) +{ + struct companion_can_priv *priv = netdev_priv(dev); + enum can_state new_state = CAN_STATE_ERROR_ACTIVE; + enum can_state rx_state = CAN_STATE_ERROR_ACTIVE; + enum can_state tx_state = CAN_STATE_ERROR_ACTIVE; + + if (state & COMPANION_CAN_STATE_BUS_OFF) { + new_state = CAN_STATE_BUS_OFF; + rx_state = bec->rxerr >= bec->txerr ? new_state : rx_state; + tx_state = bec->txerr >= bec->rxerr ? new_state : tx_state; + } else if (state & COMPANION_CAN_STATE_PASSIVE) { + new_state = CAN_STATE_ERROR_PASSIVE; + rx_state = bec->rxerr > 127 ? new_state : rx_state; + tx_state = bec->txerr > 127 ? new_state : tx_state; + } else if (state & COMPANION_CAN_STATE_WARNING) { + new_state = CAN_STATE_ERROR_WARNING; + rx_state = bec->rxerr >= bec->txerr ? new_state : rx_state; + tx_state = bec->txerr >= bec->rxerr ? new_state : tx_state; + } + + if (new_state != priv->can.state) { + can_change_state(dev, cf, tx_state, rx_state); + + if (new_state == CAN_STATE_BUS_OFF) + can_bus_off(dev); + } +} + +/** + * companion_can_handle_error() - handle CAN error + * @dev: address of the associated network device + * @cf: address of the CAN frame to store CAN error + * @code: the companion CAN error code + */ +static void companion_can_handle_error(struct net_device *dev, + struct can_frame *cf, + u8 code) +{ + struct companion_can_priv *priv = netdev_priv(dev); + + if (code & COMPANION_CAN_ERROR_RXOV) { + cf->can_id |= CAN_ERR_CRTL; + cf->data[1] |= CAN_ERR_CRTL_RX_OVERFLOW; + dev->stats.rx_over_errors++; + dev->stats.rx_errors++; + } + + if (code & (COMPANION_CAN_ERROR_STUFF | + COMPANION_CAN_ERROR_FORM | + COMPANION_CAN_ERROR_ACK | + COMPANION_CAN_ERROR_BIT1 | + COMPANION_CAN_ERROR_BIT0 | + COMPANION_CAN_ERROR_CRC)) { + cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR; + + if (code & COMPANION_CAN_ERROR_STUFF) { + cf->data[2] |= CAN_ERR_PROT_STUFF; + dev->stats.rx_errors++; + } + + if (code & COMPANION_CAN_ERROR_FORM) { + cf->data[2] |= CAN_ERR_PROT_FORM; + dev->stats.rx_errors++; + } + + if (code & COMPANION_CAN_ERROR_ACK) { + cf->can_id |= CAN_ERR_ACK; + cf->data[3] = CAN_ERR_PROT_LOC_ACK; + dev->stats.tx_errors++; + } + + if (code & COMPANION_CAN_ERROR_BIT1) { + cf->data[2] |= CAN_ERR_PROT_BIT1; + dev->stats.tx_errors++; + } + + if (code & COMPANION_CAN_ERROR_BIT0) { + cf->data[2] |= CAN_ERR_PROT_BIT0; + dev->stats.tx_errors++; + } + + if (code & COMPANION_CAN_ERROR_CRC) { + cf->data[2] |= CAN_ERR_PROT_BIT; + cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ; + dev->stats.rx_errors++; + } + + priv->can.can_stats.bus_error++; + } +} + +/** + * companion_can_poll_err() - poll CAN error packet from companion + * @dev: address of the associated network device + */ +static bool companion_can_poll_err(struct net_device *dev) +{ + struct companion_can_priv *priv = netdev_priv(dev); + struct can_berr_counter bec; + u8 state; + u8 code; + struct sk_buff *skb; + struct can_frame *cf; + + if (companion_do_can_err(priv->parent, + priv->port, + &bec, + &state, + &code) != 0) + return false; + + skb = alloc_can_err_skb(dev, &cf); + if (!skb) { + dev_err(&dev->dev, "cannot alloc err skb\n"); + return false; + } + + companion_can_handle_state(dev, cf, &bec, state); + companion_can_handle_error(dev, cf, code); + + dev->stats.rx_bytes += cf->can_dlc; + dev->stats.rx_packets++; + netif_rx(skb); + return true; +} + +/** + * companion_can_poll_data() - poll CAN data packet from companion + * @dev: address of the associated network device + */ +static bool companion_can_poll_data(struct net_device *dev) +{ + struct companion_can_priv *priv = netdev_priv(dev); + struct sk_buff *skb; + struct can_frame *cf; + + skb = alloc_can_skb(dev, &cf); + if (!skb) { + dev_err(&dev->dev, "cannot alloc rx skb\n"); + dev->stats.rx_dropped++; + return false; + } + + if (companion_do_can_rx(priv->parent, priv->port, cf) != 0) { + dev_kfree_skb_any(skb); + return false; + } + + dev->stats.rx_bytes += cf->can_dlc; + dev->stats.rx_packets++; + netif_rx(skb); + can_led_event(dev, CAN_LED_EVENT_RX); + return true; +} + +/** + * companion_can_on_tx_done() - CAN tx done callback + * @data: address of user supplied callback data + * @prio: which CAN queue is done + * @lost_seq_sync: flag indicate lost sequence happened + * @success: flag indicate last send is succeed or not + */ +static void companion_can_on_tx_done(void *data, + u8 prio, + bool lost_seq_sync, + bool success) +{ + struct companion_can_priv *priv = data; + struct net_device *dev = priv->dev; + struct net_device_stats *stats = &dev->stats; + int err; + + if (success) { + stats->tx_bytes += companion_can_get_echo_skb(priv, prio); + stats->tx_packets++; + can_led_event(dev, CAN_LED_EVENT_TX); + } else { + companion_can_free_echo_skb(priv, prio); + dev_err(&dev->dev, "on_tx_done(%d) failed\n", prio); + } + + if (lost_seq_sync) + dev_err(&dev->dev, "txq[%d] lost sequence sync\n", prio); + + err = companion_do_can_stop_tx_timer(priv->parent, priv->port, prio); + if (err) + dev_err(&dev->dev, + "stop txq[%d] tx timer failed: %d\n", + prio, err); + + netif_wake_subqueue(dev, prio); +} + +/** + * companion_can_on_rx_done() - CAN rx done callback + * @data: address of user supplied callback data + */ +static void companion_can_on_rx_done(void *data) +{ + struct companion_can_priv *priv = data; + + while (companion_can_poll_data(priv->dev)) + ; +} + +/** + * companion_can_on_error() - CAN error callback + * @data: address of user supplied callback data + */ +static void companion_can_on_error(void *data) +{ + struct companion_can_priv *priv = data; + + while (companion_can_poll_err(priv->dev)) + ; +} + +/** + * companion_can_on_tx_timeout() - CAN tx timeout callback + * @data: address of user supplied callback data + * @prio: which CAN queue tx timed out + */ +static void companion_can_on_tx_timeout(void *data, u8 prio) +{ + struct companion_can_priv *priv = data; + bool lost_txq_sync = false; + int err; + + err = companion_do_get_can_txq_status(priv->parent, + priv->port, + prio, + &lost_txq_sync); + if (err) { + dev_err(&priv->dev->dev, + "get can txq[%d] status failed: %d\n", prio, err); + + if (err != -EINVAL) + companion_do_can_start_tx_timer(priv->parent, + priv->port, + prio); + return; + } + + if (lost_txq_sync) { + dev_err(&priv->dev->dev, + "txq[%d] out of sync, restart data flow\n", prio); + companion_can_free_echo_skb(priv, prio); + netif_wake_subqueue(priv->dev, prio); + } else { + dev_err(&priv->dev->dev, + "txq[%d] is sync'd, but no ack, wait again\n", prio); + companion_do_can_start_tx_timer(priv->parent, priv->port, prio); + } +} + +static struct companion_can_ops companion_can_can_ops = { + .on_tx_done = companion_can_on_tx_done, + .on_rx_done = companion_can_on_rx_done, + .on_error = companion_can_on_error, + .on_tx_timeout = companion_can_on_tx_timeout, +}; + +/** + * companion_can_open() - ndo_open callback + * @dev: address of the associated network device + */ +static int companion_can_open(struct net_device *dev) +{ + struct companion_can_priv *priv = netdev_priv(dev); + bool has_space = false; + int err, i; + + err = companion_can_ops_register(priv->parent, + priv->port, + &companion_can_can_ops, + priv); + if (err) { + dev_err(&dev->dev, + "companion_can_ops_register() failed: %d\n", err); + goto out; + } + + err = companion_can_set_mode(dev, CAN_MODE_START); + if (err) { + dev_err(&dev->dev, + "companion_can_set_mode() failed: %d\n", err); + goto out_register; + } + + err = companion_do_get_can_txq_status_all(priv->parent, priv->port); + if (err) { + dev_err(&dev->dev, + "companion_do_get_can_txq_status_all() failed: %d\n", + err); + goto out_mode; + } + + err = open_candev(dev); + if (err) { + dev_err(&dev->dev, "open_candev() failed: %d\n", err); + goto out_mode; + } + + priv->can.state = CAN_STATE_ERROR_ACTIVE; + can_led_event(dev, CAN_LED_EVENT_OPEN); + + for (i = 0; i < NUM_TX_QUEUES; ++i) { + err = companion_do_can_txq_has_space(priv->parent, + priv->port, + i, + &has_space); + + if (!err && has_space) { + netif_tx_start_queue(netdev_get_tx_queue(dev, i)); + } else { + netif_tx_stop_queue(netdev_get_tx_queue(dev, i)); + dev_err(&dev->dev, "txq[%d] is not started\n", i); + } + } + + return 0; + +out_mode: + companion_can_set_mode(dev, CAN_MODE_STOP); +out_register: + companion_can_ops_unregister(priv->parent, priv->port); +out: + return err; +} + +/** + * companion_can_release() - ndo_close callback + * @dev: address of the associated network device + */ +static int companion_can_release(struct net_device *dev) +{ + struct companion_can_priv *priv = netdev_priv(dev); + int err; + + netif_tx_stop_all_queues(dev); + can_led_event(dev, CAN_LED_EVENT_STOP); + priv->can.state = CAN_STATE_STOPPED; + close_candev(dev); + err = companion_can_set_mode(dev, CAN_MODE_STOP); + companion_can_ops_unregister(priv->parent, priv->port); + return err; +} + +/** + * companion_can_start_xmit() - ndo_start_xmit callback + * @skb: address of the packet to send + * @dev: address of the associated network device + */ +static int companion_can_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct companion_can_priv *priv = netdev_priv(dev); + struct can_frame *cf = (struct can_frame *)skb->data; + u16 prio = skb_get_queue_mapping(skb); + bool is_full = false; + int err; + + if (can_dropped_invalid_skb(dev, skb)) { + dev_err(&dev->dev, "dropped invalid skb on txq[%d]\n", prio); + return NETDEV_TX_OK; + } + + err = companion_do_can_tx(priv->parent, priv->port, prio, cf); + if (err) { + dev_err(&dev->dev, "dropped packet on txq[%d]\n", prio); + dev_kfree_skb_any(skb); + dev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + + err = companion_do_can_txq_is_full(priv->parent, + priv->port, + prio, + &is_full); + if (!err && is_full) { + netif_stop_subqueue(dev, prio); + err = companion_do_can_start_tx_timer(priv->parent, + priv->port, + prio); + if (err) + dev_err(&dev->dev, + "start txq[%d] tx timer failed: %d\n", + prio, err); + } + + companion_can_put_echo_skb(priv, prio, skb); + return NETDEV_TX_OK; +} + +static const struct net_device_ops companion_can_netdev_ops = { + .ndo_open = companion_can_open, + .ndo_stop = companion_can_release, + .ndo_start_xmit = companion_can_start_xmit, +}; + +static const struct of_device_id companion_can_of_match[] = { + { .compatible = "bosch,companion-can", .data = NULL, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, companion_can_of_match); + +static const struct can_bittiming_const companion_can_bittiming_const = { + .name = DRIVER_NAME, + .tseg1_min = 2, + .tseg1_max = 16, + .tseg2_min = 1, + .tseg2_max = 8, + .sjw_max = 4, + .brp_min = 1, + .brp_max = 1024, + .brp_inc = 1, +}; + +/** + * companion_can_probe() - probe callback + * @pdev: address of the platform device + */ +static int companion_can_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct net_device *dev; + struct companion_can_priv *priv; + u32 port, freq; + int err; + + if (!node) { + dev_err(&pdev->dev, "no device tree data\n"); + return -ENODEV; + } + + if (of_property_read_u32(node, "port", &port)) { + dev_err(&pdev->dev, "no port property\n"); + return -ENODEV; + } + + if (port != 0 && port != 1) { + dev_err(&pdev->dev, + "invalid port %d, valid range is [0,1]\n", port); + return -EINVAL; + } + + if (of_property_read_u32(node, "clock-frequency", &freq)) { + dev_err(&pdev->dev, "no clock-frequency property\n"); + return -ENODEV; + } + + if (!pdev->dev.parent) { + dev_err(&pdev->dev, "no parent device\n"); + return -ENODEV; + } + + dev = alloc_candev_mqs(sizeof(*priv), + TX_ECHO_SKB_MAX, + NUM_TX_QUEUES, + NUM_RX_QUEUES); + if (!dev) + return -ENOMEM; + + dev->netdev_ops = &companion_can_netdev_ops; + dev->flags |= IFF_ECHO; + dev->real_num_tx_queues = NUM_TX_QUEUES; + + priv = netdev_priv(dev); + priv->can.clock.freq = freq; + priv->can.bittiming_const = &companion_can_bittiming_const; + priv->can.do_set_mode = companion_can_set_mode; + priv->can.do_get_berr_counter = companion_can_get_berr_counter; + priv->can.ctrlmode_supported = CAN_CTRLMODE_LISTENONLY | + CAN_CTRLMODE_BERR_REPORTING; + priv->parent = pdev->dev.parent; + priv->dev = dev; + priv->port = port; + + platform_set_drvdata(pdev, dev); + SET_NETDEV_DEV(dev, &pdev->dev); + + err = register_candev(dev); + if (err) { + dev_err(&pdev->dev, "register_candev() failed: %d\n", err); + free_candev(dev); + return err; + } + + devm_can_led_init(dev); + return 0; +} + +/** + * companion_can_remove() - remove callback + * @pdev: address of the platform device + */ +static int companion_can_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + + unregister_candev(dev); + free_candev(dev); + return 0; +} + +static struct platform_driver companion_can_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(companion_can_of_match), + }, + .probe = companion_can_probe, + .remove = companion_can_remove, +}; +module_platform_driver(companion_can_driver); + +MODULE_AUTHOR("Zhu Yi "); +MODULE_DESCRIPTION("Companion upper level can network device"); +MODULE_LICENSE("GPL v2"); From patchwork Wed Jun 13 14:37:21 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Jonas Mark (BT-FIR/ENG1-Grb)" X-Patchwork-Id: 928902 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=de.bosch.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=de.bosch.com header.i=@de.bosch.com header.b="hkMzT4/J"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 415Trv0KbRz9s47 for ; Thu, 14 Jun 2018 00:38:39 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S935866AbeFMOi0 (ORCPT ); Wed, 13 Jun 2018 10:38:26 -0400 Received: from de-out1.bosch-org.com ([139.15.230.186]:40976 "EHLO de-out1.bosch-org.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S935842AbeFMOiT (ORCPT ); Wed, 13 Jun 2018 10:38:19 -0400 Received: from fe0vm1650.rbesz01.com (unknown [139.15.230.188]) by si0vms0216.rbdmz01.com (Postfix) with ESMTPS id 415TrT5blSz1XLFsv; Wed, 13 Jun 2018 16:38:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=de.bosch.com; s=2015-01-21; t=1528900697; bh=xKWvSJAExCFYwQPkF9cpeARYs1KgGM4+MQ0/Lt2hetU=; l=10; h=From:From:Reply-To:Sender; b=hkMzT4/Jcl+9EBh0ZdB3+S+M23K+QHsBdXbGWY8WrjYnMM4P/zEyUs5Ck3ceeVXq8 5iOOYphx8uG89S8sSJvTWjvEJdsTMk6jFd3B8a4L0dQIFAad8l7TUKcd90BYac9R2I 9Jo5pUvYP3lYWiTJWSqxKWmhhib7X78DXMWPRXAM= Received: from fe0vm7918.rbesz01.com (unknown [10.58.172.176]) by fe0vm1650.rbesz01.com (Postfix) with ESMTPS id 415TrT5DSQz1Cf; Wed, 13 Jun 2018 16:38:17 +0200 (CEST) X-AuditID: 0a3aad10-40fff70000000ee7-11-5b212c676bd6 Received: from fe0vm1652.rbesz01.com ( [10.58.173.29]) (using TLS with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by fe0vm7918.rbesz01.com (SMG Outbound) with SMTP id 2A.7E.03815.76C212B5; Wed, 13 Jun 2018 16:38:31 +0200 (CEST) Received: from SI-HUB1000.de.bosch.com (si-hub1000.de.bosch.com [10.4.103.106]) by fe0vm1652.rbesz01.com (Postfix) with ESMTPS id 415TrT3c3rzBpBV; Wed, 13 Jun 2018 16:38:17 +0200 (CEST) Received: from luchador.grb-fir.grb.de.bosch.com (10.19.187.97) by SI-HUB1000.de.bosch.com (10.4.103.106) with Microsoft SMTP Server id 14.3.319.2; Wed, 13 Jun 2018 16:38:17 +0200 From: Mark Jonas To: Wolfgang Grandegger , Marc Kleine-Budde CC: , , , , , , , , , , Mark Jonas Subject: [PATCH v2 5/5] spi, can, char: add companion DT binding documentation Date: Wed, 13 Jun 2018 16:37:21 +0200 Message-ID: <1528900641-18677-6-git-send-email-mark.jonas@de.bosch.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1528900641-18677-1-git-send-email-mark.jonas@de.bosch.com> References: <1528224240-30786-1-git-send-email-mark.jonas@de.bosch.com> <1528900641-18677-1-git-send-email-mark.jonas@de.bosch.com> MIME-Version: 1.0 X-Brightmail-Tracker: H4sIAAAAAAAAA22Sb1BUVRjGOXuX3bsrtw53Ad+WWOQyTs2GCBVyldL65I4mWTM2ozTR3bjs brEL3buQ2J8xlEaQGbFQ2dVZCGgq/LOuhq0UUSsIVGMOxahhYkS2UM6E2Lg5unQvC+5+6MuZ c97n/Z3nnOcckqCH1HrS5nDygoMrY1RapXbV0bRllqyMopyvpzE72XgGsf7LYwTbeWsfwf7Y fUjFetualOzZ1hR24gpiL06F1exHV07HP6UxedyDStNp9y9q0/TwKDLtmO1Tm/bczTHNnDBs VG3RPlHCl9mqeGH56pe11iMnW4iKQOrWn2p96u3oWlI90pCAH4fg9/tRPdKSNG5WwEnXHiKy 6EHQ/KdrXvkcQdNoj1JGVNgIlwb9hDxPwpvg4O/vKeUmAn+lgNGe3UgWdPgZuNx9c65JiZfC SMOguh6RJIXXwh+7H45YG+DSubq5Fg02QVe3Zw6lcS2Ckf4X5DmFE2HINTHnS2CAb4JBohHd 746R3DFSK1J0ouRSPqfKnr8yl80WzLy4LSc3+5Vy+wkUCRr7kXvIHECYREwC5UrPKKLjuSqx 2h5AeaSCSaYKG5cU0feZy0uqrZxoLRYqy3iR0VMoLi6O1t0ri5Vmu00UbeWOAAKSYJKowqDE USVc9TZeKI9gAZRKKpnFVEPJW1tobOGc/Gs8X8ELC2oBSTJAiY9IZ0gUeAu/tdRW5lyQmbSI Z0qsEmurIDUB9BiZIHlr5C0osYKzizbLPP5ABKcXqlH0W5SvX0ydNUoMllVrpeOeq/5Bqugz 6SLJMUKUnEIXkZSbjnpHNkyQ/nDUDyi/7tBmOnG+GIUe7ZAY/MUi8NQI4PH/g2DvznECen01 8fBz6Hg8nDn1sQpcrX+rYOdAiwYG3x/QwF/tuyjYFeiioGHyGIbQSGsiNNTV0jB8444Omtt7 AcIdPj3MfPKbNHw4KQ3BrjTwhfoMEG761QB3Bo6nw9iOA0tgtubTDPBPDDMw8+91BoZvt2eC J3Q1E87XjGeCt3daWnb8sHRKylIhZdnfL7+j6OSc/5PlfDV6Of12VHwDXMdWrGWfdhttxeOh 9tWBI1PG5yxj5pbbhaTSk9XzYt+bd5/32uPWhNcV3Ex+dXlb26lFeQWvb1hxYP91xrKeT/PO 5vN09pfpF9ZsTCp99wPFQ9Pn6lKunb/q3bzPkvekUZO6bHKVwf+S40LW0bc7xsLfda5U3XpW XxFE1sNZvjcYpWjlco2EIHL/AdSBc1pcBAAA Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org From: Zhu Yi Signed-off-by: Zhu Yi Signed-off-by: Mark Jonas --- .../devicetree/bindings/spi/bosch,companion.txt | 82 ++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 Documentation/devicetree/bindings/spi/bosch,companion.txt diff --git a/Documentation/devicetree/bindings/spi/bosch,companion.txt b/Documentation/devicetree/bindings/spi/bosch,companion.txt new file mode 100644 index 0000000..5ded325 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/bosch,companion.txt @@ -0,0 +1,82 @@ +Bosch Companion SPI slave device + +The functionality bases on an external peripheral chip named Companion. +It offers two CAN interfaces, each has 8 prioritized transmit FIFOs as +well as one receive FIFO. Besides CAN, undisclosed additional functions +can be accessed through the char device. + +A standard SPI interface with two additional lines for flow control is +used. The Companion chip is the SPI slave. + +The driver suite consists of three separate drivers. The following +diagram illustrates the dependencies in layers. + + /dev/companion SocketCAN User Space +------------------------------------------------------------------- + +----------------+ +---------------+ + | companion-char | | companion-can | + +----------------+ +---------------+ + +----------------------------------+ + | companion-spi | + +----------------------------------+ + +----------------------------------+ + | standard SPI subsystem | + +----------------------------------+ Linux Kernel +------------------------------------------------------------------- + | | | | | | Hardware + CS-+ | | | | +-BUSY + CLK--+ | | +---REQUEST + MOSI---+ | + MISO-----+ + +Required properties: + +- compatible : must be "bosch,companion-spi" +- interrupt-parent : the phandle of the GPIO controller +- interrupts : (GPIO) interrupt to which 'request-gpios' is + connected to +- request-gpios : GPIO pin to request SPI master to receive data +- busy-gpios : GPIO pin to indicate SPI slave is busy +- cs-gpios : GPIO pin to select SPI slave + +Optional properties: + +The controller supports at most 2 CAN and 1 char device subnodes. When +optionally specify the subnodes, the following properties are required: + +- CAN subnode + - compatible : must be "bosch,companion-can" + - clock-frequency: CAN device clock in Hz + - port : must be 0 or 1 + +- Char device subnode + - compatible : must be "bosch,companion-char" + +Example: + +&ecspi1 { + companion-spi@0 { + compatible = "bosch,companion-spi"; + interrupt-parent = <&gpio1>; + interrupts = <26 IRQ_TYPE_EDGE_FALLING>; + request-gpios = <&gpio1 26 GPIO_ACTIVE_LOW>; + busy-gpios = <&gpio1 27 GPIO_ACTIVE_LOW>; + cs-gpios = <&gpio4 9 GPIO_ACTIVE_LOW>; + + companion-can0 { + compatible = "bosch,companion-can"; + clock-frequency = <28000000>; + port = <0>; + }; + + companion-can1 { + compatible = "bosch,companion-can"; + clock-frequency = <28000000>; + port = <1>; + }; + + companion-char { + compatible = "bosch,companion-char"; + }; + }; +};