From patchwork Wed Jan 4 13:34:12 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Martin_Za=C5=A5ovi=C4=8D?= X-Patchwork-Id: 38937 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:4e01:0:0:0:0:0 with SMTP id p1csp5151803wrt; Wed, 4 Jan 2023 05:49:00 -0800 (PST) X-Google-Smtp-Source: AMrXdXvyqhS2xEn1WyQ1UjPlau2JUhpikfw4vkMWmLL4LjuMEh/bPdBnwi+bl2T/70Ho/WCugm0X X-Received: by 2002:a17:907:7da1:b0:7c1:31c:e884 with SMTP id oz33-20020a1709077da100b007c1031ce884mr55793779ejc.17.1672840140349; Wed, 04 Jan 2023 05:49:00 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1672840140; cv=none; d=google.com; s=arc-20160816; b=HNsRVgGQCTeNAoaJcrSAj13+FzSYvDuVDcYzEoYxLXo4QmmFLhqWMDgFtMuoU6BA80 RQp6nxCsy3UZqRmB0eu+erZSqWCTbSfahYSSKoTGztawAR8F+W6pdZkdBnTSt4uZed9A tJjyip9/f31XFSopomalh7ROsTJxtlkBtU1bebx9ktOHpD5kCmnFe2cH+bP1pHM+Ogmy QaYXyI3+4GElHkcpZ3hgu5ibrkWKtdxBb82v5E376TAMtNEb3QTR2Ly8rEZiKVhgr4tI cNowz2VD6DvfK+tbHSnnBrgCgKbrhKwQn1teBe0L2Yx7PtIfpw31TMpaykAXC0RWpYqi V/JA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=wsgtBjS68Ohvo1thth+3WmlxZ3oCDpfdMvKjKCDOwTM=; b=cjCKYADjlehgIkc13c2Bg3YYilzoIQbHGqYRYHWpWBrBCJaLzJzGe35fIMpU0PoSdi LI2SIUUuKGOlJRNRI0NFj32d1Ysxr+jUJFQNujGe2gdUhWran1nc0CQ2Ff94W+PhGCXn iugGnA+XgCjLxEauB1qiWjA1cufJjuAwxOUgxYPalcaB4lPWx662dEpTjSHo6Hozcm5+ Y1GQxQvZiv+lMTu50ujcbAiYKbAXwGsLn6Xt4GErzOobUCrmaVVCynAaTCdT+vxQOK2U 8RQf2/Jn1hKlwNv/5o6aSwQ61TEMKdEa5cBrfpxVXn77hVKqPgW7dmKsXgZRBRR677jZ OHsQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmail.com header.s=20210112 header.b=hXMy5MxK; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id wv1-20020a170907080100b007c0c5cb19f6si27982140ejb.684.2023.01.04.05.48.36; Wed, 04 Jan 2023 05:49:00 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@gmail.com header.s=20210112 header.b=hXMy5MxK; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S239771AbjADNhV (ORCPT + 99 others); Wed, 4 Jan 2023 08:37:21 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:41540 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S239503AbjADNgq (ORCPT ); Wed, 4 Jan 2023 08:36:46 -0500 Received: from mail-ej1-x629.google.com (mail-ej1-x629.google.com [IPv6:2a00:1450:4864:20::629]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 581E93AABC; Wed, 4 Jan 2023 05:34:43 -0800 (PST) Received: by mail-ej1-x629.google.com with SMTP id jo4so82614127ejb.7; Wed, 04 Jan 2023 05:34:43 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=wsgtBjS68Ohvo1thth+3WmlxZ3oCDpfdMvKjKCDOwTM=; b=hXMy5MxKif13gMjlj3n/5ryfal7kMspvOjKlH1P9wVPHCEwWdzydaSRZ1SeEA3dM1s SW5e2fPhjOw+O6R2dT62b4G378kF26DJjSX36lbh0HzEG3O+yaLwH8QcM5bGL2Jfn66f /zgsUc1tqxiy3z6E4g+zzsnxRkc5YDbaR+D9cLhFMoGnydkKStazzRr9eM/ZDj+efVaU 63uQBpW/dSKkm7Q6k+lruNBab8pUIdOhREpwcXjoLGI+ZMeejOLuvXFdNN3f2etW05oi 3CCxmrtjliqAZPip/27yqOCC4XFPwCoU2goMJZ3wWbeaoz0ZH7C5+BMWg8tfxLwerjWS LqhA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=wsgtBjS68Ohvo1thth+3WmlxZ3oCDpfdMvKjKCDOwTM=; b=sV9yNKvaAp2ujDVU9kfu4T1le3JoGbB5Z4TTfYrAxy996SU4YYxzzx3sYLbOi50Dc2 F2Gcmvu7aTdGd9VBlg0mQ8/kCTApd2Qb+GyUUM7nZWM2tqPRGvOGCKxG0no3d4Vrsu+r i4pSp9t5UKhpEdiAproAThjtCUtTU2VTYu7dZzoVF0inCRRsbHAouUvLEFv7CL2X8NW1 yHxtdq4iTyBDOWVnkh92EcUcWnV49V/WtmH5NNBfvNjITowirdxj4Ip63PxdIr635462 882t9mALaus+8CgzHmGHN455aa1hX+orWXjD6ATpe0jARVyxxHHv0r9ZQE7BAty/teme XhAg== X-Gm-Message-State: AFqh2krS+dDNTY1CTqvobC4+EamAmEYQpI4KjwmlFPV065otLhH5i3mu YeU9N1Nf2rwqhD7SyKHUgyznRAh5SpJgCQ== X-Received: by 2002:a17:906:354c:b0:7c0:e988:4157 with SMTP id s12-20020a170906354c00b007c0e9884157mr41343157eja.40.1672839273222; Wed, 04 Jan 2023 05:34:33 -0800 (PST) Received: from fedora.local.tbs-biometrics.cz (176-74-132-138.netdatacomm.cz. [176.74.132.138]) by smtp.gmail.com with ESMTPSA id sb20-20020a170906edd400b0073dbaeb50f6sm15211659ejb.169.2023.01.04.05.34.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 04 Jan 2023 05:34:32 -0800 (PST) From: =?utf-8?q?Martin_Za=C5=A5ovi=C4=8D?= To: linux-kernel@vger.kernel.org Cc: devicetree@vger.kernel.org, mani@kernel.org, hemantk@codeaurora.org, quic_jhugo@quicinc.com, andersson@kernel.org, Michael.Srba@seznam.cz, arnd@arndb.de, dipenp@nvidia.com, bvanassche@acm.org, iwona.winiarska@intel.com, ogabbay@kernel.org, tzimmermann@suse.de, fmdefrancesco@gmail.com, jason.m.bills@linux.intel.com, jae.hyun.yoo@linux.intel.com, gregkh@linuxfoundation.org, krzysztof.kozlowski+dt@linaro.org, robh+dt@kernel.org, =?utf-8?q?Martin_Za?= =?utf-8?q?=C5=A5ovi=C4=8D?= Subject: [PATCH 1/3] dt-bindings: add Wiegand controller dt-binding documentation Date: Wed, 4 Jan 2023 14:34:12 +0100 Message-Id: <20230104133414.39305-2-m.zatovic1@gmail.com> X-Mailer: git-send-email 2.38.1 In-Reply-To: <20230104133414.39305-1-m.zatovic1@gmail.com> References: <20230104133414.39305-1-m.zatovic1@gmail.com> MIME-Version: 1.0 X-Spam-Status: No, score=-1.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_ENVFROM_END_DIGIT, FREEMAIL_FROM,RCVD_IN_DNSWL_NONE,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1754100023375300113?= X-GMAIL-MSGID: =?utf-8?q?1754100023375300113?= A Weigand bus is defined by a Wiegand controller node. This node can contain one or more device nodes for devices attached to the controller(it is advised to only connect one device as Wiegand is a point-to-point bus). Wiegand controller needs to specify several attributes such as the pulse length in order to function properly. These attributes are documented here. Signed-off-by: Martin Zaťovič --- .../bindings/wiegand/wiegand-controller.yaml | 83 +++++++++++++++++++ MAINTAINERS | 5 ++ 2 files changed, 88 insertions(+) create mode 100644 Documentation/devicetree/bindings/wiegand/wiegand-controller.yaml diff --git a/Documentation/devicetree/bindings/wiegand/wiegand-controller.yaml b/Documentation/devicetree/bindings/wiegand/wiegand-controller.yaml new file mode 100644 index 000000000000..645306c65d43 --- /dev/null +++ b/Documentation/devicetree/bindings/wiegand/wiegand-controller.yaml @@ -0,0 +1,83 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/wiegand/wiegand-controller.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Wiegand Controller Generic Binding + +maintainers: + - Martin Zaťovič + +description: | + Wiegand busses can be described with a node for the Wiegand controller device + and a set of child nodes for each SPI slave on the bus. + +properties: + $nodename: + pattern: "^wiegand(@.*|-[0-9a-f])*$" + + compatible: + maxItems: 1 + description: Compatible of the Wiegand controller. + + data-high-gpios: + maxItems: 1 + description: GPIO used as Wiegands data-hi line. This line is initially + pulled up to high value. Wiegand write of a bit of value 1 results in + this line being pulled down for pulse length duration. + + data-lo-gpios: + maxItems: 1 + description: GPIO used as Wiegands data-lo line. This line is initially + pulled up to high value. Wiegand write of a bit of value 0 results in + this line being pulled down for pulse length duration. + + pulse-len-us: + maxItems: 1 + description: Length of the low pulse in microseconds. + + interval-len-us: + maxItems: 1 + description: Length of a whole bit (both the pulse and the high phase) + in microseconds. + + frame-gap-us: + maxItems: 1 + description: Length of the last bit of a frame (both the pulse and the + high phase) in microseconds. + + slave-device: + type: object + + properties: + compatible: + description: + Compatible of the Wiegand device. + + required: + - compatible + +required: + - compatible + - pulse-len-us + - interval-len-us + - frame-gap-us + +additionalProperties: false + +examples: + - | + wiegand@f00 { + compatible = "wiegand-gpio"; + data-hi-gpios = <&gpio2 7 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>; + data-lo-gpios = <&gpio2 6 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>; + pulse-len-us = <50>; + interval-len-us = <2000>; + frame-gap-us = <2000>; + status = "okay"; + + wiegand-foo-device { + compatible = "wiegand-foo"; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 7f86d02cb427..db9624d93af0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22428,6 +22428,11 @@ L: linux-input@vger.kernel.org S: Maintained F: drivers/hid/hid-wiimote* +WIEGAND BUS DRIVER +M: Martin Zaťovič +S: Maintained +F: Documentation/devicetree/bindings/wiegand/wiegand-controller.yaml + WILOCITY WIL6210 WIRELESS DRIVER L: linux-wireless@vger.kernel.org S: Orphan From patchwork Wed Jan 4 13:34:13 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Martin_Za=C5=A5ovi=C4=8D?= X-Patchwork-Id: 38934 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:4e01:0:0:0:0:0 with SMTP id p1csp5147312wrt; Wed, 4 Jan 2023 05:38:17 -0800 (PST) X-Google-Smtp-Source: AMrXdXsA3xNlSmtxLuFOk1RWpNOaLy1KS5t3GH7+9IidoySqBkSOa3enFJ82l7caIDQWO5oYCr19 X-Received: by 2002:a17:907:8c81:b0:7c0:7d35:e9db with SMTP id td1-20020a1709078c8100b007c07d35e9dbmr55784333ejc.15.1672839497256; Wed, 04 Jan 2023 05:38:17 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1672839497; cv=none; d=google.com; s=arc-20160816; b=LjmRRUEfGniUR0vG3l1pLm/P1Rf+c/qsoihNwbDiBpcbp1qIYcC1nyeg/bDOlCQzYN FsQ+OcEd5G+5pA2ZdYS5UOt4VoA25hbm4qiUhW3OZuFmoHci/OcsAb/lpEQRsohB1r/d aSr5oyXWXJzUcp3f2LTaVYrcB2JZbgNHCEnclJZiZ2FbNciMqUqXIZopUYjiS2xBnnoW DnMf47ZnsMWm5XLj+ZQ7ckgrrkA64SfIFjq0211pL9hbG5FsOm0OGEd6zZD5GuJiquYX GrjFkjPbh0y/+M8bl8IgsPvLZQTTELMLDi4nwgwq1G4H4zhMrD1uBW0wGtWjwTCP+Xn6 nGMQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=zwgBB8ONyjEjJPW1Sn3HtyKR+YCtKR8/Kp0A/PtHw48=; b=c2VNchA0rw5Dmdn4ggweIDixGgs2EjSdl/sg51PKymtOIuGJAx7vKdJh+Q1DW1iVSX aGIwTjdGkJBVFB4ENgScKIIz6pbQ6EXHf4e07Qv8LvWGXzDIrt1LjM4MpSgWioeqjKco eiAZ8ZoIHz9gY1ebug/KKHeozGuKbi1VPR8z/4NFNJ1xKWQu2lExyX/sXPe1Khw7cnX0 nG3qggzChIrnAuGlwzi1jrUMI9fH+5a9Vs/3uJKY5jkORyrZBTEJuWo4JHxkoaAQygbs xtUzcmVndtEHVibgXPfSm2GkQS+egSwbO6oAJMvi5ucgHmQRYsPqajbLlhgBP8IiMjGI FCMQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmail.com header.s=20210112 header.b=exuCv3xc; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id dt6-20020a170907728600b007b790c183e0si29208358ejc.290.2023.01.04.05.37.52; Wed, 04 Jan 2023 05:38:17 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@gmail.com header.s=20210112 header.b=exuCv3xc; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S234578AbjADNh1 (ORCPT + 99 others); Wed, 4 Jan 2023 08:37:27 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39984 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S239505AbjADNgq (ORCPT ); Wed, 4 Jan 2023 08:36:46 -0500 Received: from mail-ej1-x633.google.com (mail-ej1-x633.google.com [IPv6:2a00:1450:4864:20::633]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E8E853AF16; Wed, 4 Jan 2023 05:34:43 -0800 (PST) Received: by mail-ej1-x633.google.com with SMTP id qk9so82398314ejc.3; Wed, 04 Jan 2023 05:34:43 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=zwgBB8ONyjEjJPW1Sn3HtyKR+YCtKR8/Kp0A/PtHw48=; b=exuCv3xcprgME102Ifi3+WWd0TKGoXQxO9jLEPxmLnD9fA0ucUc1cGgB3vl9MvYtwl JyVRKeHRe8DWoXHhZQ42N39CYWbsi/IRbwC09c/gPrbvs97Hg5GQZ2mwRUTUzt40btWz J+BsjvfmRkvHe/xCGSdeyFe+Yjl+khNvm6zet2RajwBWzM7FWlBMMTin6nPYlXxOA2J3 B5j2m6A5PrlcDD1KGbhlS0XBZ11UyiR+5DcipA+opsfa67KdeGknemAd/IGfQy5xOOG9 6SegrrLemd3955E/0ZhbgfVMNKOP7rH8DeCjlswen9hG8fLYmPSqGUpsoemBr55Ms81l uj+Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=zwgBB8ONyjEjJPW1Sn3HtyKR+YCtKR8/Kp0A/PtHw48=; b=o+GjLTqo7a7lLNJGDazO7s/Wxgw9UeLDOJGbncR8UwyQXzczm9ProrSpnWPdW/g/nj lC0y+65/JCzMOFtbFZSPer7fnMR8O4qGzqObDxkM8WroH41AjU+kwgxVxWg8bC/bd8Dc //k2bPvw3C2YGVrMsKUq0vKn6reo9Yva6+iM2JYsFFaRIQRULLhLpVSjEboIKbjsxvMl kzw8Ygs2Q4dsjeOwo19aUfpb/PfkSNtHc/giVBVqKZ+aZGxt9l956CeSHRxYv8bKQINl R4/TXyavq3XCGoe/sqUr4eN3Stj7VXuGuB95XJ4DE/butLr4S9r0HBbf5k7yG/EnVTcC vnzQ== X-Gm-Message-State: AFqh2koj6vgTe3jO3HPvoDSxmL7dwPdk5byXXwfx4zXl+5FYAQFNdWgk GBTlDY1FpZEtspYDCdhrh/K+kto90cnCJA== X-Received: by 2002:a17:906:cb99:b0:7c0:f5d7:cac7 with SMTP id mf25-20020a170906cb9900b007c0f5d7cac7mr48917499ejb.67.1672839275216; Wed, 04 Jan 2023 05:34:35 -0800 (PST) Received: from fedora.local.tbs-biometrics.cz (176-74-132-138.netdatacomm.cz. [176.74.132.138]) by smtp.gmail.com with ESMTPSA id sb20-20020a170906edd400b0073dbaeb50f6sm15211659ejb.169.2023.01.04.05.34.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 04 Jan 2023 05:34:34 -0800 (PST) From: =?utf-8?q?Martin_Za=C5=A5ovi=C4=8D?= To: linux-kernel@vger.kernel.org Cc: devicetree@vger.kernel.org, mani@kernel.org, hemantk@codeaurora.org, quic_jhugo@quicinc.com, andersson@kernel.org, Michael.Srba@seznam.cz, arnd@arndb.de, dipenp@nvidia.com, bvanassche@acm.org, iwona.winiarska@intel.com, ogabbay@kernel.org, tzimmermann@suse.de, fmdefrancesco@gmail.com, jason.m.bills@linux.intel.com, jae.hyun.yoo@linux.intel.com, gregkh@linuxfoundation.org, krzysztof.kozlowski+dt@linaro.org, robh+dt@kernel.org, =?utf-8?q?Martin_Za?= =?utf-8?q?=C5=A5ovi=C4=8D?= Subject: [PATCH 2/3] bus: add Wiegand bus driver Date: Wed, 4 Jan 2023 14:34:13 +0100 Message-Id: <20230104133414.39305-3-m.zatovic1@gmail.com> X-Mailer: git-send-email 2.38.1 In-Reply-To: <20230104133414.39305-1-m.zatovic1@gmail.com> References: <20230104133414.39305-1-m.zatovic1@gmail.com> MIME-Version: 1.0 X-Spam-Status: No, score=-1.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_ENVFROM_END_DIGIT, FREEMAIL_FROM,RCVD_IN_DNSWL_NONE,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1754099348758159578?= X-GMAIL-MSGID: =?utf-8?q?1754099348758159578?= Add a bus driver for Wiegand protocol. The bus driver handles Wiegand controller and Wiegand device managemement and driver matching. The bus driver defines the structures for Wiegand controllers and Wiegand devices. Wiegand controller structure contains the format and payload_len fields to add support for different format messages. This should be handled by the controller driver. Each Wiegand controller should be associated with one Wiegand device, as Wiegand is typically a point-to-point bus. Signed-off-by: Martin Zaťovič --- MAINTAINERS | 2 + drivers/bus/Kconfig | 5 + drivers/bus/Makefile | 1 + drivers/bus/wiegand.c | 768 ++++++++++++++++++++++++++++++++++++++++ include/linux/wiegand.h | 228 ++++++++++++ 5 files changed, 1004 insertions(+) create mode 100644 drivers/bus/wiegand.c create mode 100644 include/linux/wiegand.h diff --git a/MAINTAINERS b/MAINTAINERS index db9624d93af0..b6e68e92f0e3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22432,6 +22432,8 @@ WIEGAND BUS DRIVER M: Martin Zaťovič S: Maintained F: Documentation/devicetree/bindings/wiegand/wiegand-controller.yaml +F: drivers/bus/wiegand.c +F: include/linux/wiegand.h WILOCITY WIL6210 WIRELESS DRIVER L: linux-wireless@vger.kernel.org diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index 7bfe998f3514..360e55abc311 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -241,6 +241,11 @@ config VEXPRESS_CONFIG Platform configuration infrastructure for the ARM Ltd. Versatile Express. +config WIEGAND + tristate "Wiegand Bus driver" + help + Driver for low-level 2-wire serial protocol communication. + config DA8XX_MSTPRI bool "TI da8xx master peripheral priority driver" depends on ARCH_DAVINCI_DA8XX diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index d90eed189a65..bde17c8ceedb 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_TI_SYSC) += ti-sysc.o obj-$(CONFIG_TS_NBUS) += ts-nbus.o obj-$(CONFIG_UNIPHIER_SYSTEM_BUS) += uniphier-system-bus.o obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o +obj-$(CONFIG_WIEGAND_GPIO) += wiegand.o obj-$(CONFIG_DA8XX_MSTPRI) += da8xx-mstpri.o diff --git a/drivers/bus/wiegand.c b/drivers/bus/wiegand.c new file mode 100644 index 000000000000..062d2f74979f --- /dev/null +++ b/drivers/bus/wiegand.c @@ -0,0 +1,768 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_IDR(wiegand_controller_idr); + +static struct bus_type wiegand_bus_type; + +struct boardinfo { + struct list_head list; + struct wiegand_board_info board_info; +}; + +static LIST_HEAD(board_list); +static LIST_HEAD(wiegand_controller_list); + +int wiegand_calc_parity8(u8 v) +{ + v = (v >> 4) ^ (v & ((1 << 4)-1)); + v = (v >> 2) ^ (v & ((1 << 2)-1)); + v = (v >> 1) ^ (v & ((1 << 1)-1)); + return v; +} +EXPORT_SYMBOL_GPL(wiegand_calc_parity8); + +/* + * This function prepends the first and appends the last bit of a message. + * It is up to the caller to allocate a buffer such that these bits fit. + */ +void wiegand_add_parity_to_data(unsigned char *tmp, u8 *data, + enum wiegand_format fmt) +{ + switch (fmt) { + case WIEGAND_V26: + data[0] = (tmp[0] >> 1) | (wiegand_calc_parity8( + tmp[0] ^ (tmp[1] & 0xf0)) << 7); + data[1] = (tmp[0] << 7) | (tmp[1] >> 1); + data[2] = (tmp[1] << 7) | (tmp[2] >> 1); + data[3] = (tmp[2] << 7) | (!wiegand_calc_parity8( + (tmp[1] & 0x0f) ^ tmp[2]) << 6); + break; + case WIEGAND_V36: + tmp[4] &= 0xc0; + data[0] = (tmp[0] >> 1) | (wiegand_calc_parity8( + tmp[0] ^ tmp[1] ^ (tmp[2] & 0x80)) << 7); + data[1] = (tmp[0] << 7) | (tmp[1] >> 1); + data[2] = (tmp[1] << 7) | (tmp[2] >> 1); + data[3] = (tmp[2] << 7) | (tmp[3] >> 1); + data[4] = (tmp[3] << 7) | (tmp[4] >> 1) | + (!wiegand_calc_parity8( + (tmp[2] & 0x7f) ^ tmp[3] ^ tmp[4]) << 4); + break; + case WIEGAND_V37: + tmp[4] &= 0xe0; + data[0] = (tmp[0] >> 1) | (wiegand_calc_parity8( + tmp[0] ^ tmp[1] ^ (tmp[2] & 0xc0)) << 7); + data[1] = (tmp[0] << 7) | (tmp[1] >> 1); + data[2] = (tmp[1] << 7) | (tmp[2] >> 1); + data[3] = (tmp[2] << 7) | (tmp[3] >> 1); + data[4] = (tmp[3] << 7) | (tmp[4] >> 1) | + (!wiegand_calc_parity8( + (tmp[2] & 0x7f) ^ tmp[3] ^ tmp[4]) << 3); + break; + default: + WARN_ON(fmt != WIEGAND_V37 && + fmt != WIEGAND_V36 && + fmt != WIEGAND_V26); + } +} +EXPORT_SYMBOL_GPL(wiegand_add_parity_to_data); + + +static void devm_wiegand_release_controller(struct device *dev, void *ctlr) +{ + wiegand_controller_put(*(struct wiegand_controller **)ctlr); +} + +static void wiegand_controller_release(struct device *dev) +{ + struct wiegand_controller *ctlr; + + ctlr = container_of(dev, struct wiegand_controller, dev); + kfree(ctlr); +} + +static struct class wiegand_controller_class = { + .name = "wiegand_master", + .owner = THIS_MODULE, + .dev_release = wiegand_controller_release, +}; + +static DEFINE_MUTEX(board_lock); + +struct wiegand_controller *__wiegand_alloc_controller(struct device *dev, + unsigned int size, bool slave) +{ + struct wiegand_controller *ctlr; + size_t ctlr_size = ALIGN(sizeof(*ctlr), dma_get_cache_alignment()); + + if (!dev) + return NULL; + + ctlr = kzalloc(size + ctlr_size, GFP_KERNEL); + if (!ctlr) + return NULL; + + device_initialize(&ctlr->dev); + ctlr->bus_num = -1; + ctlr->slave = slave; + ctlr->dev.class = &wiegand_controller_class; + ctlr->dev.parent = dev; + wiegand_controller_set_devdata(ctlr, (void *)ctlr + ctlr_size); + + return ctlr; +} +EXPORT_SYMBOL_GPL(__wiegand_alloc_controller); + +struct wiegand_controller *__devm_wiegand_alloc_controller(struct device *dev, + unsigned int size, + bool slave) +{ + struct wiegand_controller **ptr, *ctlr; + + ptr = devres_alloc(devm_wiegand_release_controller, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return NULL; + + ctlr = __wiegand_alloc_controller(dev, size, slave); + if (ctlr) { + ctlr->devm_allocated = true; + *ptr = ctlr; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return ctlr; +} +EXPORT_SYMBOL_GPL(__devm_wiegand_alloc_controller); + +static int wiegand_controller_check_ops(struct wiegand_controller *ctlr) +{ + if (!ctlr->transfer_message) + return -EINVAL; + return 0; +} + +static void wiegand_match_controller_to_boardinfo( + struct wiegand_controller *ctlr, + struct wiegand_board_info *bi) +{ + struct wiegand_device *dev; + + if (ctlr->bus_num != bi->bus_num) + return; + + dev = wiegand_new_device(ctlr, bi); + if (dev == NULL) + dev_err(ctlr->dev.parent, "can't create new device for %s\n", + bi->modalias); + bi->dev = dev; + + /* check if more devices are connected to the bus */ + if (ctlr->device_count > 1) + dev_warn(&ctlr->dev, "Wiegand is a point-to-point bus, it is advised to only connect one device per Wiegand bus. The devices may not communicate using the same pulse length, format or else, devcnt = %u.\n", + ctlr->device_count); +} + +static struct wiegand_device *of_register_wiegand_device( + struct wiegand_controller *ctlr, + struct device_node *nc) +{ + struct wiegand_device *wiegand; + int rc; + + wiegand = wiegand_alloc_device(ctlr); + if (!wiegand) { + dev_err(&ctlr->dev, "wiegad_device alloc error for %pOF\n", nc); + rc = -ENOMEM; + goto err_out; + } + + rc = of_modalias_node(nc, wiegand->modalias, sizeof(wiegand->modalias)); + if (rc < 0) { + dev_err(&ctlr->dev, "cannot find modalias for %pOF\n", nc); + goto err_out; + } + + of_node_get(nc); + wiegand->dev.of_node = nc; + wiegand->dev.fwnode = of_fwnode_handle(nc); + + rc = wiegand_add_device(wiegand); + if (rc) { + dev_err(&ctlr->dev, "wiegand_device register error %pOF\n", nc); + goto err_of_node_put; + } + + /* check if more devices are connected to the bus */ + if (ctlr->device_count > 1) + dev_warn(&ctlr->dev, "Wiegand is a point-to-point bus, it is advised to only connect one device per Wiegand bus. The devices may not communicate using the same pulse length, format or else.\n"); + + return wiegand; + +err_of_node_put: + of_node_put(nc); +err_out: + wiegand_dev_put(wiegand); + return ERR_PTR(rc); +} + +static void of_register_wiegand_devices(struct wiegand_controller *ctlr) +{ + struct wiegand_device *wiegand; + struct device_node *nc; + + if (!ctlr->dev.of_node) + return; + + for_each_available_child_of_node(ctlr->dev.of_node, nc) { + if (of_node_test_and_set_flag(nc, OF_POPULATED)) + continue; + wiegand = of_register_wiegand_device(ctlr, nc); + if (IS_ERR(wiegand)) { + dev_warn(&ctlr->dev, + "Failed to create wiegand device for %pOF\n", + nc); + of_node_clear_flag(nc, OF_POPULATED); + } + } +} + +/* + * Controllers that do not have a devicetree entry need to initialize the + * following struct wiegand_controller attributes: pulse_len, interval_len and + * frame_gap. + */ +int wiegand_register_controller(struct wiegand_controller *ctlr) +{ + struct device *dev = ctlr->dev.parent; + struct boardinfo *bi; + int status, id, first_dynamic; + + if (!dev) + return -ENODEV; + + status = wiegand_controller_check_ops(ctlr); + if (status) + return status; + + if (ctlr->dev.of_node) { + id = of_alias_get_id(ctlr->dev.of_node, "wiegand"); + if (id > 0) { + ctlr->bus_num = id; + mutex_lock(&board_lock); + id = idr_alloc(&wiegand_controller_idr, ctlr, + ctlr->bus_num, + ctlr->bus_num + 1, + GFP_KERNEL); + mutex_unlock(&board_lock); + if (WARN(id < 0, "couldn't get idr")) + return id == -ENOSPC ? -EBUSY : id; + } + device_property_read_u32(&ctlr->dev, "pulse-len-us", + &ctlr->pulse_len); + device_property_read_u32(&ctlr->dev, "interval-len-us", + &ctlr->interval_len); + device_property_read_u32(&ctlr->dev, "frame-gap-us", + &ctlr->frame_gap); + } + if (ctlr->bus_num < 0) { + first_dynamic = of_alias_get_highest_id("wiegand"); + if (first_dynamic < 0) + first_dynamic = 0; + else + first_dynamic++; + + mutex_lock(&board_lock); + id = idr_alloc(&wiegand_controller_idr, ctlr, first_dynamic, + 0, GFP_KERNEL); + mutex_unlock(&board_lock); + if (WARN(id < 0, "couldn't get idr\n")) + return id; + ctlr->bus_num = id; + } + + if (ctlr->pulse_len == 0) + dev_warn(&ctlr->dev, "pulse_len is not initialized\n"); + if (ctlr->interval_len == 0) + dev_warn(&ctlr->dev, "interval_len is not initialized\n"); + if (ctlr->frame_gap == 0) + dev_warn(&ctlr->dev, "frame_gap is not initialized\n"); + + dev_set_name(&ctlr->dev, "wiegand%u", ctlr->bus_num); + ctlr->device_count = 0; + + status = device_add(&ctlr->dev); + if (status < 0) + goto free_bus_id; + + mutex_lock(&board_lock); + list_add_tail(&ctlr->list, &wiegand_controller_list); + list_for_each_entry(bi, &board_list, list) + wiegand_match_controller_to_boardinfo(ctlr, &bi->board_info); + mutex_unlock(&board_lock); + + of_register_wiegand_devices(ctlr); + + return status; + +free_bus_id: + mutex_lock(&board_lock); + idr_remove(&wiegand_controller_idr, ctlr->bus_num); + mutex_unlock(&board_lock); + return status; +} + +static int __unregister(struct device *dev, void *null) +{ + wiegand_unregister_device(to_wiegand_device(dev)); + return 0; +} + +void wiegand_unregister_controller(struct wiegand_controller *ctlr) +{ + struct wiegand_controller *found; + int id = ctlr->bus_num; + + device_for_each_child(&ctlr->dev, NULL, __unregister); + found = idr_find(&wiegand_controller_idr, id); + device_del(&ctlr->dev); + + mutex_lock(&board_lock); + if (found == ctlr) + idr_remove(&wiegand_controller_idr, id); + mutex_unlock(&board_lock); + + if (!ctlr->devm_allocated) + put_device(&ctlr->dev); +} +EXPORT_SYMBOL_GPL(wiegand_unregister_controller); + +static void devm_wiegand_unregister(struct device *dev, void *res) +{ + wiegand_unregister_controller(*(struct wiegand_controller **)res); +} + +int devm_wiegand_register_controller(struct device *dev, + struct wiegand_controller *ctlr) +{ + struct wiegand_controller **ptr; + int ret; + + ptr = devres_alloc(devm_wiegand_unregister, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = wiegand_register_controller(ctlr); + if (!ret) { + *ptr = ctlr; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return ret; +} +EXPORT_SYMBOL_GPL(devm_wiegand_register_controller); + +/* Payload length is not regarded for standard formats. + * Setting format for a device sets the format of a controller associated + * with it. + */ +void wiegand_ctlr_set_format_and_paylen(struct wiegand_controller *ctlr, + enum wiegand_format format, + u16 payload_len) +{ + switch (format) { + case WIEGAND_V26: + ctlr->payload_len = WIEGAND_V26_PAYLEN; + break; + case WIEGAND_V36: + ctlr->payload_len = WIEGAND_V36_PAYLEN; + break; + case WIEGAND_V37: + ctlr->payload_len = WIEGAND_V37_PAYLEN; + break; + case WIEGAND_CUSTOM: + ctlr->payload_len = payload_len; + break; + default: + dev_warn(&ctlr->dev, "tried to set invalid format\n"); + return; + }; + ctlr->format = format; +} +EXPORT_SYMBOL_GPL(wiegand_ctlr_set_format_and_paylen); + +static int __wiegand_master_match(struct device *dev, const void *data) +{ + struct wiegand_master *master; + const u16 *bus_num = data; + + master = container_of(dev, struct wiegand_master, dev); + return master->bus_num == *bus_num; +} + +struct wiegand_master *wiegand_busnum_to_master(u16 bus_num) +{ + struct device *dev; + struct wiegand_master *master = NULL; + + dev = class_find_device(&wiegand_controller_class, NULL, &bus_num, + __wiegand_master_match); + if (dev) + master = container_of(dev, struct wiegand_master, dev); + + return master; +} +EXPORT_SYMBOL_GPL(wiegand_busnum_to_master); + +/* Device section */ + +static void wieganddev_release(struct device *dev) +{ + struct wiegand_device *wiegand = to_wiegand_device(dev); + + wiegand_controller_put(wiegand->controller); + kfree(wiegand); +} + +struct wiegand_device *wiegand_alloc_device(struct wiegand_controller *ctlr) +{ + struct wiegand_device *wiegand; + + if (!wiegand_controller_get(ctlr)) + return NULL; + + wiegand = kzalloc(sizeof(*wiegand), GFP_KERNEL); + if (!wiegand) { + wiegand_controller_put(ctlr); + return NULL; + } + + wiegand->controller = ctlr; + wiegand->dev.parent = &ctlr->dev; + wiegand->dev.bus = &wiegand_bus_type; + wiegand->dev.release = wieganddev_release; + + device_initialize(&wiegand->dev); + return wiegand; +} +EXPORT_SYMBOL_GPL(wiegand_alloc_device); + +struct wiegand_device *wiegand_new_device(struct wiegand_controller *ctlr, + struct wiegand_board_info *chip) +{ + struct wiegand_device *proxy; + int status; + + proxy = wiegand_alloc_device(ctlr); + if (!proxy) + return NULL; + + WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias)); + + strscpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias)); + proxy->dev.platform_data = (void *) chip->platform_data; + + /* set the format to default 26-bit */ + wiegand_ctlr_set_format_and_paylen(ctlr, WIEGAND_V26, 0); + + status = wiegand_add_device(proxy); + if (status < 0) + goto err_dev_put; + + return proxy; + +err_dev_put: + wiegand_dev_put(proxy); + return NULL; +} +EXPORT_SYMBOL_GPL(wiegand_new_device); + +static void wiegand_cleanup(struct wiegand_device *wiegand) +{ + if (wiegand->controller->cleanup) + wiegand->controller->cleanup(wiegand); +} + +static int __wiegand_add_device(struct wiegand_device *wiegand) +{ + struct wiegand_controller *ctlr = wiegand->controller; + struct device *dev = ctlr->dev.parent; + int status; + + status = wiegand_setup(wiegand); + if (status < 0) { + dev_err(dev, "can't setup %s, status %d\n", + dev_name(&wiegand->dev), status); + return status; + } + + status = device_add(&wiegand->dev); + if (status < 0) { + dev_err(dev, "can't add %s, status %d\n", + dev_name(&wiegand->dev), status); + wiegand_cleanup(wiegand); + } else { + dev_dbg(dev, "registered child %s\n", dev_name(&wiegand->dev)); + } + + return status; +} + +static void wiegand_dev_set_name(struct wiegand_device *wiegand, u8 id) +{ + dev_set_name(&wiegand->dev, "%s.%u", + dev_name(&wiegand->controller->dev), id); +} + +int wiegand_add_device(struct wiegand_device *wiegand) +{ + struct wiegand_controller *ctlr = wiegand->controller; + int status; + + wiegand_dev_set_name(wiegand, ctlr->device_count); + + status = __wiegand_add_device(wiegand); + if (!status) { + ctlr->device_count++; + wiegand->id = wiegand->controller->device_count; + } + + return status; +} + +int wiegand_setup(struct wiegand_device *wiegand) +{ + int status = 0; + + if (wiegand->controller->setup) { + status = wiegand->controller->setup(wiegand); + if (status) { + dev_err(&wiegand->controller->dev, + "Failed to setup device: %d\n", + status); + return status; + } + } + + return status; +} +EXPORT_SYMBOL_GPL(wiegand_setup); + +void wiegand_unregister_device(struct wiegand_device *wiegand) +{ + if (!wiegand) + return; + + if (wiegand->dev.of_node) { + of_node_clear_flag(wiegand->dev.of_node, OF_POPULATED); + of_node_put(wiegand->dev.of_node); + } + device_del(&wiegand->dev); + wiegand_cleanup(wiegand); + put_device(&wiegand->dev); +} +EXPORT_SYMBOL_GPL(wiegand_unregister_device); + +void wiegand_set_format_and_paylen(struct wiegand_device *wdev, + enum wiegand_format fmt, + u16 payload_len) +{ + struct wiegand_controller *ctlr = wdev->controller; + + wiegand_ctlr_set_format_and_paylen(ctlr, fmt, payload_len); +} +EXPORT_SYMBOL_GPL(wiegand_set_format_and_paylen); + +struct wiegand_device *wiegand_get_device_by_board_info( + struct wiegand_board_info *boardinfo) +{ + return boardinfo->dev; +} +EXPORT_SYMBOL_GPL(wiegand_get_device_by_board_info); + +int wiegand_register_board_info(struct wiegand_board_info *info, u8 n) +{ + struct boardinfo *bi; + int i; + + if (!n) + return 0; + + bi = kcalloc(n, sizeof(*bi), GFP_KERNEL); + if (!bi) + return -ENOMEM; + + for (i = 0; i < n; i++, bi++, info++) { + struct wiegand_controller *ctlr; + + memcpy(&bi->board_info, info, sizeof(*info)); + + mutex_lock(&board_lock); + list_add_tail(&bi->list, &board_list); + list_for_each_entry(ctlr, &wiegand_controller_list, list) + wiegand_match_controller_to_boardinfo(ctlr, + &bi->board_info); + info->dev = bi->board_info.dev; + mutex_unlock(&board_lock); + } + + return 0; +} +EXPORT_SYMBOL_GPL(wiegand_register_board_info); + +int wiegand_send_message(struct wiegand_device *wiegand, u8 *message) +{ + struct wiegand_master *master = wiegand->controller; + + if (message == NULL || message == 0) + return -EINVAL; + + if (master->transfer_message) + master->transfer_message(wiegand, message); + + return 0; +} +EXPORT_SYMBOL_GPL(wiegand_send_message); + +static const struct wiegand_device_id *wiegand_match_id( + const struct wiegand_device_id *id, + const char *name) +{ + while (id->name[0]) { + if (!strcmp(name, id->name)) + return id; + id++; + } + return NULL; +} + +static int wiegand_match_device(struct device *dev, struct device_driver *drv) +{ + const struct wiegand_device *wiegand = to_wiegand_device(dev); + const struct wiegand_driver *wdrv = to_wiegand_driver(drv); + + if (of_driver_match_device(dev, drv)) + return 1; + + if (wdrv->id_table) + return !!wiegand_match_id(wdrv->id_table, wiegand->modalias); + + return strcmp(wiegand->modalias, drv->name) == 0; +} + +static int wiegand_probe(struct device *dev) +{ + struct wiegand_device *wiegand = to_wiegand_device(dev); + const struct wiegand_driver *wdrv = to_wiegand_driver(dev->driver); + int ret = 0; + + if (wdrv->probe) + ret = wdrv->probe(wiegand); + + return ret; +} + +static int wiegand_remove(struct device *dev) +{ + const struct wiegand_driver *wdrv = to_wiegand_driver(dev->driver); + + if (wdrv->remove) + wdrv->remove(to_wiegand_device(dev)); + + return 0; +} + +static struct bus_type wiegand_bus_type = { + .name = "wiegand", + .match = wiegand_match_device, + .probe = wiegand_probe, + .remove = wiegand_remove, +}; + +int __wiegand_register_driver(struct module *owner, struct wiegand_driver *wdrv) +{ + wdrv->driver.owner = owner; + wdrv->driver.bus = &wiegand_bus_type; + + if (wdrv->driver.of_match_table) { + const struct of_device_id *of_id; + + for (of_id = wdrv->driver.of_match_table; of_id->compatible[0]; + of_id++) { + const char *of_name; + + /* remove vendor prefix */ + of_name = strnchr(of_id->compatible, + sizeof(of_id->compatible), ','); + if (of_name) + of_name++; + else + of_name = of_id->compatible; + + if (wdrv->id_table) { + const struct wiegand_device_id *wiegand_id; + + wiegand_id = wiegand_match_id(wdrv->id_table, + of_name); + if (wiegand_id) + continue; + } else { + if (strcmp(wdrv->driver.name, of_name) == 0) + continue; + } + + pr_warn("Wiegand driver %s has no wiegand_device_id for %s\n", + wdrv->driver.name, of_id->compatible); + } + } + + return driver_register(&wdrv->driver); +} +EXPORT_SYMBOL_GPL(__wiegand_register_driver); + +static int __init wiegand_init(void) +{ + int ret; + + ret = bus_register(&wiegand_bus_type); + if (ret < 0) { + pr_err("Wiegand bus registration failed: %d\n", ret); + goto err0; + } + + ret = class_register(&wiegand_controller_class); + if (ret < 0) + goto err1; + + return 0; + +err1: + bus_unregister(&wiegand_bus_type); +err0: + return ret; +} + +static void __exit wiegand_exit(void) +{ + bus_unregister(&wiegand_bus_type); + class_unregister(&wiegand_controller_class); +} +postcore_initcall_sync(wiegand_init); +module_exit(wiegand_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Wiegand bus driver"); +MODULE_AUTHOR("Martin Zaťovič "); diff --git a/include/linux/wiegand.h b/include/linux/wiegand.h new file mode 100644 index 000000000000..0b991c4a7903 --- /dev/null +++ b/include/linux/wiegand.h @@ -0,0 +1,228 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef H_LINUX_INCLUDE_LINUX_WIEGAND_H +#define H_LINUX_INCLUDE_LINUX_WIEGAND_H + +#include +#include +#include + +#define WIEGAND_NAME_SIZE 32 + +extern struct bus_type wiegand_type; + +/* The used wiegand format; when data does not end at octet boundaries, the + * lower bits of the last octet will be ignored and only the upper ones will be + * used. + */ +enum wiegand_format { + WIEGAND_CUSTOM = 0, + WIEGAND_V26 = 26, + WIEGAND_V36 = 36, + WIEGAND_V37 = 37, +}; + +enum wiegand_paylen { + WIEGAND_V26_PAYLEN = 24, + WIEGAND_V36_PAYLEN = 34, + WIEGAND_V37_PAYLEN = 35, +}; + +/** + * struct wiegand_device - Wiegand listener device + * @dev - drivers structure of the device + * @id - unique device id + * @controller - Wiegand controller associated with the device + * @modalias - Name of the driver to use with this device, or its alias. + */ +struct wiegand_device { + struct device dev; + u8 id; + struct wiegand_controller *controller; + char modalias[WIEGAND_NAME_SIZE]; +}; + +/** + * struct wiegand_board_info - description of a Wiegand device + * @modalias - Identifies the driver. + * @platform_data - Driver-specific data. + * @swnode - Software node for the device. TODO + * @bus_num - Identifies the controller parent of the device. + * @dev - Wiegand device structure. Should be initialized by board_info + * registration. + */ +struct wiegand_board_info { + char modalias[WIEGAND_NAME_SIZE]; + const void *platform_data; + const struct software_node *swnode; + u16 bus_num; + struct wiegand_device *dev; +}; + + +/** + * struct wiegand_controller - Wiegand master or slave interface + * @dev - Device interface of the controller + * @list - Link with the global wiegand_controller list + * @bus_num - Board-specific identifier for Wiegand controller + * @pulse_len: length of the low pulse in usec; defaults to 50us + * @interval_len: length of a whole bit (both the pulse and the high phase) in + * usec; defaults to 2000us + * @frame_gap: length of the last bit of a frame (both the pulse and the high + * phase) in usec; defaults to interval_len + * @format - TODO + * @payload_len - TODO + * device_count - Counter of devices connected to the same Wiegand + * bus(controller). + * devm_allocated - Whether the allocation of this struct is devres-managed + * slave - Whether the controller is a slave(receives data). + * transfer_message - Send a message on the bus. + * setup - Setup a device. + * cleanup - Cleanup after a device. + */ +struct wiegand_controller { + struct device dev; + struct list_head list; + + s16 bus_num; + + u32 pulse_len; + u32 interval_len; + u32 frame_gap; + enum wiegand_format format; + u32 payload_len; + + u8 device_count; + + bool devm_allocated; + bool slave; + + int (*transfer_message)(struct wiegand_device *dev, u8 *message); + + int (*setup)(struct wiegand_device *wiegand); + void (*cleanup)(struct wiegand_device *wiegand); +}; + +struct wiegand_driver { + const struct wiegand_device_id *id_table; + int (*probe)(struct wiegand_device *wiegand); + void (*remove)(struct wiegand_device *wiegand); + struct device_driver driver; +}; + +extern int wiegand_calc_parity8(u8 v); +extern void wiegand_add_parity_to_data(unsigned char *tmp, u8 *data, + enum wiegand_format fmt); + +/* Wiegand controller section */ + +#define wiegand_master wiegand_controller +extern struct wiegand_controller *__wiegand_alloc_controller( + struct device *host, + unsigned int size, + bool slave); + +struct wiegand_controller *__devm_wiegand_alloc_controller(struct device *dev, + unsigned int size, + bool slave); +struct wiegand_controller *__wiegand_alloc_controller(struct device *dev, + unsigned int size, + bool slave); +static inline struct wiegand_controller *devm_wiegand_alloc_master( + struct device *dev, + unsigned int size) +{ + return __devm_wiegand_alloc_controller(dev, size, false); +} +extern int wiegand_register_controller(struct wiegand_controller *ctlr); +extern int devm_wiegand_register_controller(struct device *dev, + struct wiegand_controller *ctlr); +#define wiegand_register_master(_ctlr) wiegand_register_controller(_ctlr) +#define devm_wiegand_register_master(_dev, _ctlr) \ + devm_wiegand_register_controller(_dev, _ctlr) +extern void wiegand_unregister_controller(struct wiegand_controller *ctlr); +#define wiegand_unregister_master(_ctlr) wiegand_unregister_controller(_ctlr) +extern struct wiegand_master *wiegand_busnum_to_master(u16 bus_num); +extern void wiegand_ctlr_set_format_and_paylen(struct wiegand_controller *ctlr, + enum wiegand_format format, + u16 payload_len); + +static inline void *wiegand_controller_get_devdata( + struct wiegand_controller *ctlr) +{ + return dev_get_drvdata(&ctlr->dev); +} + +static inline void wiegand_controller_set_devdata( + struct wiegand_controller *ctlr, + void *data) +{ + dev_set_drvdata(&ctlr->dev, data); +} + +#define wiegand_master_get_devdata(_ctlr) \ + wiegand_controller_get_devdata(_ctlr) +#define wiegand_master_set_devdata(_ctlr, _data) \ + wiegand_controller_set_devdata(_ctlr, _data) + +static inline struct wiegand_controller *wiegand_controller_get( + struct wiegand_controller *ctlr) +{ + if (!ctlr || !get_device(&ctlr->dev)) + return NULL; + return ctlr; +} + +static inline void wiegand_controller_put(struct wiegand_controller *ctlr) +{ + if (ctlr) + put_device(&ctlr->dev); +} + +/* Wiegand device section */ + +extern struct wiegand_device *wiegand_alloc_device( + struct wiegand_controller *ctlr); +extern struct wiegand_device *wiegand_new_device( + struct wiegand_controller *ctlr, + struct wiegand_board_info *chip); +extern int wiegand_add_device(struct wiegand_device *wiegand); +extern int wiegand_setup(struct wiegand_device *wiegand); +extern void wiegand_unregister_device(struct wiegand_device *wiegand); + +extern void wiegand_set_format_and_paylen(struct wiegand_device *wdev, + enum wiegand_format fmt, + u16 payload_len); +extern struct wiegand_device *wiegand_get_device_by_board_info( + struct wiegand_board_info *bi); +extern int wiegand_send_message(struct wiegand_device *wiegand, u8 *message); + +static inline struct wiegand_device *to_wiegand_device(struct device *dev) +{ + return dev ? container_of(dev, struct wiegand_device, dev) : NULL; +} +static inline void wiegand_dev_put(struct wiegand_device *wiegand) +{ + if (wiegand) + put_device(&wiegand->dev); +} +extern int wiegand_register_board_info(struct wiegand_board_info *info, u8 n); + +/* Wiegand driver section */ + +static inline struct wiegand_driver *to_wiegand_driver(struct device_driver *drv) +{ + return drv ? container_of(drv, struct wiegand_driver, driver) : NULL; +} +extern int __wiegand_register_driver(struct module *owner, + struct wiegand_driver *wdrv); +#define wiegand_register_driver(driver) \ + __wiegand_register_driver(THIS_MODULE, driver) + +static inline void wiegand_unregister_driver(struct wiegand_driver *wdrv) +{ + if (wdrv) + driver_unregister(&wdrv->driver); +} + +#endif /* H_LINUX_INCLUDE_LINUX_WIEGAND_H */ From patchwork Wed Jan 4 13:34:14 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Martin_Za=C5=A5ovi=C4=8D?= X-Patchwork-Id: 38933 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:4e01:0:0:0:0:0 with SMTP id p1csp5147309wrt; Wed, 4 Jan 2023 05:38:17 -0800 (PST) X-Google-Smtp-Source: AMrXdXsr3po8YqPKcdTFi6/OGykrdMQxUo5thn5sj3yUhPRnm8ewcKlQVVrzLSnnqiOJI/feh1Br X-Received: by 2002:a05:6a20:3b1d:b0:9d:efbe:a116 with SMTP id c29-20020a056a203b1d00b0009defbea116mr47776252pzh.38.1672839496950; Wed, 04 Jan 2023 05:38:16 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1672839496; cv=none; d=google.com; s=arc-20160816; b=QL8ggPCmi7rABseMpyv3YwCNmo0nnvOODJiwjmCe5XkJYz3Z4G8sILeP19Yb8EK09v WpwFEdiDsRc5V+lyL3KoIA9GDBzB43w7+apn7rm3d1I+ey7ZkkdU/MYWb+XUrv2HsHWf uz3FbUVhKmotl76CF2/++/q1iTBlScoQZngXMGL5CARQuOGfNPHVvVzfEFc7sNa/9NpI LEeS9vjiuonfVxahKEVgIUkW5MIwt9ivi0Gk83ftDOt/1jwlamclGvPLy9gf397NZS4T 8j4ycXNuMyOe3imrjetcIyLriLQ6Ej8nylWtfaHF8Yjqkq5Y2sn38PdbzmV7VSGGQTpu ESCQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=iMopO2WsrkBqiEuMwNNAMu84VnckfcWrRMZJCRK68yE=; b=GAxE2QVQMC3f74XYZqPkKdZEtawbzYb/jKBmVeZ/sF5mG7CPGzeVD3R4alRN+LJ1px SXm9LtMTYAUZ7LNc0pv3XuEpJ+il2rdEZb1AmsAqAIdDkSk9M2iGTdhjNaveUNSIFtV1 6285Bj2wVlxdNHN4VoFH2eNUGM6eTGuNdWVpNaHQBGBSUdVHpQWn1CM9tgHtbyOy/rBJ ocpw8ZrClAkrG39MQZFwuvJY3bQpe545y7aTQMmxwIQXl3k/lSrVsDTUFs+utKm8FZIR yA5sSF0fQGTChmNohxS5gHBgpgQyw+ZH2whwHhL3jw2moa7OPV40bU9BrluHCDXkj3zP O9+Q== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmail.com header.s=20210112 header.b=Z4FDjrXO; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id q202-20020a632ad3000000b00480a937f897si37458369pgq.650.2023.01.04.05.38.04; Wed, 04 Jan 2023 05:38:16 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@gmail.com header.s=20210112 header.b=Z4FDjrXO; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236203AbjADNhd (ORCPT + 99 others); Wed, 4 Jan 2023 08:37:33 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39946 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S239506AbjADNgq (ORCPT ); Wed, 4 Jan 2023 08:36:46 -0500 Received: from mail-ej1-x632.google.com (mail-ej1-x632.google.com [IPv6:2a00:1450:4864:20::632]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id EDD9F3AF19; Wed, 4 Jan 2023 05:34:43 -0800 (PST) Received: by mail-ej1-x632.google.com with SMTP id u19so82546613ejm.8; Wed, 04 Jan 2023 05:34:43 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=iMopO2WsrkBqiEuMwNNAMu84VnckfcWrRMZJCRK68yE=; b=Z4FDjrXOO7sHjfLUd77H9EjU/ZurHQXGZwD/ITrbA1HidNKHhP6QPKMIv+PdoaN3gi bqPJYvaWGJXJaQM3kH3IKkRWaaqec0eekjfprMSmgSrkAMrVht40bhqAttlnziUO6Luq HMKxz7oUHre2pOOSjM4sdkV0y8cOLtjO+7vVS9RgDRWmnQer3cTU5aNKC4mGeMJSxOef is6kKd08jMaChEkEsN69ex0eJa1miUohy7jw6iS4of9RGlCfrBiBwKgp9T6Qsi4UhW06 X2p/ySn2exldqBWdxCbXYOISHlAaBCZWM4XZkVpI1fepas2ueW2mY8vEpjwZQQKvvmq4 +okw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=iMopO2WsrkBqiEuMwNNAMu84VnckfcWrRMZJCRK68yE=; b=U/roQcpp00TQQ5/IKhrY3f9h/WR9fp+lQw0Ga9866gl6576+drgo8CC56QT4NXfsGy VBKV59HvLtMov7Qtonnla2vKdxPDb7NRsMuYXxXDyBgwWq2RDHVIq6tx1CbBECaGww+3 PQKrHhxlRcQI8SAJTIHI5iNzVJOQdjAUevYxRI2aII/ayj9KVGV97hKErQ86rtB6sXSZ COq/MD5RDoy3bvv0xSqtXIWUVDjtv/Ubk61GTVoPZiIgPz91ntfbiiTPDkrYe65VxgWf jad0iCMFAO1Q1zjf1BYuQrrEuw+mcKsWDVVEt8TWil6cZScBQoTuQhI3HOen/aL0zr4j m/Gg== X-Gm-Message-State: AFqh2kqeE7H/LClyV8wIzgLt9Xup7jozAUL4eu8Wkfzp/hO6cWcrtJMJ yw/EqygPdHPKe/w1SjPhyAWfB0DsflxvSw== X-Received: by 2002:a17:907:8b89:b0:7c1:6f86:eeb with SMTP id tb9-20020a1709078b8900b007c16f860eebmr37521210ejc.7.1672839277012; Wed, 04 Jan 2023 05:34:37 -0800 (PST) Received: from fedora.local.tbs-biometrics.cz (176-74-132-138.netdatacomm.cz. [176.74.132.138]) by smtp.gmail.com with ESMTPSA id sb20-20020a170906edd400b0073dbaeb50f6sm15211659ejb.169.2023.01.04.05.34.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 04 Jan 2023 05:34:36 -0800 (PST) From: =?utf-8?q?Martin_Za=C5=A5ovi=C4=8D?= To: linux-kernel@vger.kernel.org Cc: devicetree@vger.kernel.org, mani@kernel.org, hemantk@codeaurora.org, quic_jhugo@quicinc.com, andersson@kernel.org, Michael.Srba@seznam.cz, arnd@arndb.de, dipenp@nvidia.com, bvanassche@acm.org, iwona.winiarska@intel.com, ogabbay@kernel.org, tzimmermann@suse.de, fmdefrancesco@gmail.com, jason.m.bills@linux.intel.com, jae.hyun.yoo@linux.intel.com, gregkh@linuxfoundation.org, krzysztof.kozlowski+dt@linaro.org, robh+dt@kernel.org, =?utf-8?q?Martin_Za?= =?utf-8?q?=C5=A5ovi=C4=8D?= Subject: [PATCH 3/3] wiegand: add Wiegand GPIO bit-banged controller driver Date: Wed, 4 Jan 2023 14:34:14 +0100 Message-Id: <20230104133414.39305-4-m.zatovic1@gmail.com> X-Mailer: git-send-email 2.38.1 In-Reply-To: <20230104133414.39305-1-m.zatovic1@gmail.com> References: <20230104133414.39305-1-m.zatovic1@gmail.com> MIME-Version: 1.0 X-Spam-Status: No, score=-1.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_ENVFROM_END_DIGIT, FREEMAIL_FROM,RCVD_IN_DNSWL_NONE,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1754099348460097794?= X-GMAIL-MSGID: =?utf-8?q?1754099348460097794?= This controller formats the data to a Wiegand format - appends checksum if one of the defined formats is used - and bit-bangs the message on devicetree defined GPIO lines. Several attributes need to be defined in the devicetree in order for this driver to work, namely the data-hi-gpios, data-lo-gpios, pulse-len, frame-gap and interval-len. These attributes are documented in the devicetree binding documentation file. The driver creates a dev file for writing messages on the bus. It also creates two sysfs files to control the format and payload length of messages. Defined formats are 26, 36 and 37-bit, meaning the payloads for these formats are 24, 34 and 35 bit respectively, as two bits are allocated for checksum. A custom format is also supported and it is set by writing 0 to the format sysfs file. Custom format does not calculate and append checksum to messages - they are bit-banged as inputted. Signed-off-by: Martin Zaťovič --- .../ABI/testing/sysfs-driver-wiegand-gpio | 17 + MAINTAINERS | 2 + drivers/Kconfig | 2 + drivers/wiegand/Kconfig | 8 + drivers/wiegand/Makefile | 1 + drivers/wiegand/wiegand-gpio.c | 471 ++++++++++++++++++ 6 files changed, 501 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-wiegand-gpio create mode 100644 drivers/wiegand/Kconfig create mode 100644 drivers/wiegand/Makefile create mode 100644 drivers/wiegand/wiegand-gpio.c diff --git a/Documentation/ABI/testing/sysfs-driver-wiegand-gpio b/Documentation/ABI/testing/sysfs-driver-wiegand-gpio new file mode 100644 index 000000000000..7d651ecc0f9f --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-wiegand-gpio @@ -0,0 +1,17 @@ +What: /sys/devices/platform/.../wiegand-gpio-attributes/format +Date: January 2023 +Contact: Martin Zaťovič +Description: + Read/set Wiegand communication format. + 0 - custom format, payload length is specified by + payload_len file + 26 - 26-bit format (24 bit payload, 2 bits checksum) + 36 - 36-bit format (34 bit payload, 2 bits checksum) + 37 - 37-bit format (35 bit payload, 2 bits checksum) + +What: /sys/devices/platform/.../wiegand-gpio-attributes/payload_len +Date: January 2023 +Contact: Martin Zaťovič +Description: + Read/set Wiegand communication payload length. File is only + writable if custom format is set. diff --git a/MAINTAINERS b/MAINTAINERS index b6e68e92f0e3..c86d53d86f9c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22431,8 +22431,10 @@ F: drivers/hid/hid-wiimote* WIEGAND BUS DRIVER M: Martin Zaťovič S: Maintained +F: Documentation/ABI/testing/sysfs-driver-wiegand-gpio F: Documentation/devicetree/bindings/wiegand/wiegand-controller.yaml F: drivers/bus/wiegand.c +F: drivers/wiegand/wiegand-gpio.c F: include/linux/wiegand.h WILOCITY WIL6210 WIRELESS DRIVER diff --git a/drivers/Kconfig b/drivers/Kconfig index 968bd0a6fd78..bedc5a9fecba 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -67,6 +67,8 @@ source "drivers/spi/Kconfig" source "drivers/spmi/Kconfig" +source "drivers/wiegand/Kconfig" + source "drivers/hsi/Kconfig" source "drivers/pps/Kconfig" diff --git a/drivers/wiegand/Kconfig b/drivers/wiegand/Kconfig new file mode 100644 index 000000000000..769809bfe650 --- /dev/null +++ b/drivers/wiegand/Kconfig @@ -0,0 +1,8 @@ +if WIEGAND + +config WIEGAND_GPIO + tristate "GPIO-based wiegand master (write only)" + help + This driver uses GPIO pins to send wiegand data. + +endif # WIEGAND diff --git a/drivers/wiegand/Makefile b/drivers/wiegand/Makefile new file mode 100644 index 000000000000..da550876c408 --- /dev/null +++ b/drivers/wiegand/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_WIEGAND_GPIO) += wiegand-gpio.o diff --git a/drivers/wiegand/wiegand-gpio.c b/drivers/wiegand/wiegand-gpio.c new file mode 100644 index 000000000000..b00383ec741e --- /dev/null +++ b/drivers/wiegand/wiegand-gpio.c @@ -0,0 +1,471 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WIEGAND_MAX_PAYLEN_BYTES 256 + +struct wiegand_gpio { + struct device *dev; + struct wiegand_controller *ctlr; + struct miscdevice misc_dev; + struct mutex mutex; + struct gpio_desc *gpio_data_hi; + struct gpio_desc *gpio_data_lo; + struct file_operations fops; + u8 data[WIEGAND_MAX_PAYLEN_BYTES]; +}; + +struct wiegand_gpio_instance { + struct wiegand_gpio *dev; + unsigned long flags; +}; + +static ssize_t store_ulong(u32 *val, const char *buf, + size_t size, unsigned long max) +{ + int rc; + u32 new; + + rc = kstrtou32(buf, 0, &new); + if (rc) + return rc; + + if (max != 0 && new > max) + return -EINVAL; + + *val = new; + return size; +} + +ssize_t format_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct wiegand_gpio *wiegand_gpio = (struct wiegand_gpio *) + dev->driver_data; + struct wiegand_controller *ctlr = wiegand_gpio->ctlr; + + return sysfs_emit(buf, "%u\n", ctlr->format); +} + +/* + * To set a particular format, the number of bits the driver is supposed to + * transmit is written to the format sysfs file. For standard formats, the + * allowed inputs are 26, 36 and 37. To set a custom format, 0 is passed. + */ +ssize_t format_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int rc; + unsigned long new; + struct wiegand_gpio *wiegand_gpio = (struct wiegand_gpio *) + dev->driver_data; + struct wiegand_controller *ctlr = wiegand_gpio->ctlr; + + rc = kstrtoul(buf, 0, &new); + if (rc) + return rc; + + switch (new) { + case 0: + ctlr->format = WIEGAND_CUSTOM; + break; + case 26: + ctlr->format = WIEGAND_V26; + ctlr->payload_len = WIEGAND_V26_PAYLEN; + break; + case 36: + ctlr->format = WIEGAND_V36; + ctlr->payload_len = WIEGAND_V36_PAYLEN; + break; + case 37: + ctlr->format = WIEGAND_V37; + ctlr->payload_len = WIEGAND_V37_PAYLEN; + break; + default: + return -EINVAL; + } + + return count; +} + +/* + * Using a custom format, the payload_len sysfs file configures the size of + * messages payload in bits. When one of the standard formats is used, this + * file is read-only and contains the size of the message in bits without the + * parity bits. + */ +ssize_t payload_len_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wiegand_gpio *wiegand_gpio = (struct wiegand_gpio *) + dev->driver_data; + struct wiegand_controller *ctlr = wiegand_gpio->ctlr; + + return sysfs_emit(buf, "%u\n", ctlr->payload_len); +} + +ssize_t payload_len_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct wiegand_gpio *wiegand_gpio = (struct wiegand_gpio *) + dev->driver_data; + struct wiegand_controller *ctlr = wiegand_gpio->ctlr; + + /* standard formats use fixed payload size */ + if (ctlr->format != WIEGAND_CUSTOM) + return -EPERM; + + return store_ulong(&(ctlr->payload_len), buf, count, + WIEGAND_MAX_PAYLEN_BYTES * 8); +} + +DEVICE_ATTR_RW(format); +DEVICE_ATTR_RW(payload_len); + +static struct attribute *wiegand_attrs[] = { + &dev_attr_format.attr, + &dev_attr_payload_len.attr, + NULL, +}; + +static struct attribute_group wiegand_group = { + .name = "wiegand-gpio-attributes", + .attrs = wiegand_attrs, +}; + +/* + * To send a bit of value 1 following the wiegand protocol, one must set + * the wiegand_data_hi to low for the duration of pulse. Similarly to send + * a bit of value 0, the wiegand_data_lo is set to low for pulse duration. + * This way the two lines are never low at the same time. + */ +void wiegand_gpio_send_bit(struct wiegand_gpio *wiegand_gpio, + bool value, bool last) +{ + u32 pulse_len = wiegand_gpio->ctlr->pulse_len; + u32 interval_len = wiegand_gpio->ctlr->interval_len; + u32 frame_gap = wiegand_gpio->ctlr->frame_gap; + struct gpio_desc *gpio = value ? wiegand_gpio->gpio_data_hi + : wiegand_gpio->gpio_data_lo; + + gpiod_set_value_cansleep(gpio, 0); + udelay(pulse_len); + gpiod_set_value_cansleep(gpio, 1); + + if (last) + udelay(frame_gap - pulse_len); + else + udelay(interval_len - pulse_len); +} + +/* This function is used for writing from file in dev directory */ +static int wiegand_gpio_write_by_bits(struct wiegand_gpio *wiegand_gpio) +{ + size_t i, bitlen; + bool bit_value, is_last_bit; + enum wiegand_format format = wiegand_gpio->ctlr->format; + u32 payload_len = wiegand_gpio->ctlr->payload_len; + + bitlen = format ? payload_len + 2 : payload_len; + + for (i = 0; i < bitlen; i++) { + bit_value = ((wiegand_gpio->data[i / 8] >> (7 - (i % 8))) + & 0x01); + is_last_bit = (i + 1) == bitlen; + + wiegand_gpio_send_bit(wiegand_gpio, bit_value, + is_last_bit); + } + + return 0; +} + +static unsigned int wiegand_gpio_calc_bytes(unsigned int bits) +{ + if (bits % 8 != 0) + return (bits / 8) + 1; + else + return bits / 8; +} + +static unsigned int wiegand_gpio_get_payload_size( + unsigned long payload_len_bits, + enum wiegand_format fmt) +{ + unsigned int rc; + + if (fmt == WIEGAND_CUSTOM) + rc = wiegand_gpio_calc_bytes(payload_len_bits); + else + /* add 2 for parity bits */ + rc = wiegand_gpio_calc_bytes(payload_len_bits + 2); + + return rc; +} + +static ssize_t wiegand_gpio_get_user_data( + struct wiegand_gpio *wiegand_gpio, + char __user const *buf, size_t len) +{ + size_t rc; + size_t num_copy; + unsigned char tmp[WIEGAND_MAX_PAYLEN_BYTES]; + enum wiegand_format format = wiegand_gpio->ctlr->format; + u32 payload_len = wiegand_gpio->ctlr->payload_len; + + if (len > WIEGAND_MAX_PAYLEN_BYTES) + return -EBADMSG; + + num_copy = wiegand_gpio_get_payload_size(payload_len, format); + + if (format == WIEGAND_CUSTOM) { + rc = copy_from_user(&wiegand_gpio->data[0], buf, num_copy); + if (rc < 0) + return rc; + } else { + rc = copy_from_user(tmp, buf, num_copy); + if (rc < 0) + return rc; + wiegand_add_parity_to_data(tmp, wiegand_gpio->data, format); + } + return num_copy; +} + +static int wiegand_gpio_frelease(struct inode *ino, struct file *filp) +{ + struct wiegand_gpio_instance *info = filp->private_data; + struct wiegand_gpio *wiegand_gpio = info->dev; + + mutex_lock(&wiegand_gpio->mutex); + info->flags = 0; + mutex_unlock(&wiegand_gpio->mutex); + + kfree(info); + + return 0; +} + +static ssize_t wiegand_gpio_fwrite(struct file *filp, + char __user const *buf, size_t len, loff_t *offset) +{ + struct wiegand_gpio_instance *info = filp->private_data; + struct wiegand_gpio *wiegand_gpio = info->dev; + u32 payload_len = wiegand_gpio->ctlr->payload_len; + int rc; + + if (len * 8 < payload_len) + return -EBADMSG; + if (buf == NULL || len == 0) + return -EINVAL; + + rc = wiegand_gpio_get_user_data(wiegand_gpio, buf, len); + if (rc < 0) + return rc; + + wiegand_gpio_write_by_bits(wiegand_gpio); + + return len; +} + +static int wiegand_gpio_fopen(struct inode *ino, struct file *filp) +{ + int rc; + struct wiegand_gpio_instance *info; + struct wiegand_gpio *wiegand_gpio = container_of(filp->f_op, + struct wiegand_gpio, + fops); + mutex_lock(&wiegand_gpio->mutex); + if ((filp->f_flags & O_ACCMODE) == O_RDONLY || + (filp->f_flags & O_ACCMODE) == O_RDWR) { + dev_err(wiegand_gpio->dev, "Device is write only\n"); + rc = -EIO; + goto err; + } + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + rc = -ENOMEM; + goto err; + } + + info->dev = wiegand_gpio; + info->flags = filp->f_flags; + mutex_unlock(&wiegand_gpio->mutex); + + filp->private_data = info; + + return 0; +err: + mutex_unlock(&wiegand_gpio->mutex); + return rc; +} + +size_t get_bytelength(u8 bitlength) +{ + size_t bytelength = bitlength / 8; + + if (bitlength % 8) + bytelength++; + return bytelength; +} + +/* This function is used by device drivers */ +int wiegand_gpio_transfer_message(struct wiegand_device *dev, u8 *message) +{ + struct wiegand_controller *ctlr = dev->controller; + struct wiegand_gpio *wiegand_gpio = + wiegand_master_get_devdata(ctlr); + u8 message_bytelength; + + if (ctlr->format) { /* if format is not set to custom */ + if (ctlr->format == WIEGAND_V26) + message_bytelength = 4; /* 4 bytes for 26-bit format */ + else + message_bytelength = 5; /* 5 bytes for 36 and 37-bit */ + } else { + message_bytelength = get_bytelength(ctlr->payload_len); + } + + if (ctlr->format == WIEGAND_CUSTOM) + memcpy(wiegand_gpio->data, message, message_bytelength); + else + wiegand_add_parity_to_data(message, wiegand_gpio->data, + dev->controller->format); + + wiegand_gpio_write_by_bits(wiegand_gpio); + + return 0; +} + +static int wiegand_gpio_request(struct device *dev, + struct wiegand_gpio *wiegand_gpio) +{ + /* Get GPIO lines using device tree bindings. */ + wiegand_gpio->gpio_data_lo = devm_gpiod_get(dev, "data-lo", + GPIOD_OUT_HIGH); + if (IS_ERR(wiegand_gpio->gpio_data_lo)) + return PTR_ERR(wiegand_gpio->gpio_data_lo); + + wiegand_gpio->gpio_data_hi = devm_gpiod_get(dev, "data-hi", + GPIOD_OUT_HIGH); + return PTR_ERR_OR_ZERO(wiegand_gpio->gpio_data_hi); +} + +static int wiegand_gpio_probe(struct platform_device *device) +{ + int status; + struct wiegand_controller *master; + struct wiegand_gpio *wiegand_gpio; + struct device *dev = &device->dev; + + master = devm_wiegand_alloc_master(dev, sizeof(*wiegand_gpio)); + if (!master) + return -ENOMEM; + + if (dev->of_node) + master->dev.of_node = device->dev.of_node; + + if (status) + return status; + + master->transfer_message = &wiegand_gpio_transfer_message; + + wiegand_gpio = wiegand_master_get_devdata(master); + wiegand_gpio->ctlr = master; + wiegand_gpio->fops.owner = THIS_MODULE; + wiegand_gpio->fops.open = wiegand_gpio_fopen; + wiegand_gpio->fops.release = wiegand_gpio_frelease; + wiegand_gpio->fops.write = wiegand_gpio_fwrite; + + platform_set_drvdata(device, wiegand_gpio); + + master->bus_num = device->id; + wiegand_gpio->dev = dev; + + mutex_init(&wiegand_gpio->mutex); + + status = wiegand_gpio_request(dev, wiegand_gpio); + if (status) { + dev_err(wiegand_gpio->dev, "failed at requesting GPIOs\n"); + return status; + } + + status = gpiod_direction_output(wiegand_gpio->gpio_data_hi, 1); + status |= gpiod_direction_output(wiegand_gpio->gpio_data_lo, 1); + if (status) { + dev_err(wiegand_gpio->dev, "failed to set GPIOs direction\n"); + return status; + } + + status = sysfs_create_group(&wiegand_gpio->dev->kobj, &wiegand_group); + if (status < 0) { + dev_err(wiegand_gpio->dev, "couldn't register sysfs group\n"); + return status; + } + + status = devm_wiegand_register_master(dev, master); + if (status) { + dev_err(wiegand_gpio->dev, "failed to register master\n"); + goto free_misc_group; + } + + // set 26-bit format as default + wiegand_ctlr_set_format_and_paylen(master, WIEGAND_V26, + WIEGAND_V26_PAYLEN); + + wiegand_gpio->misc_dev.name = kasprintf(GFP_KERNEL, "wiegand-gpio%u", + master->bus_num); + wiegand_gpio->misc_dev.minor = MISC_DYNAMIC_MINOR; + wiegand_gpio->misc_dev.fops = &wiegand_gpio->fops; + + status = misc_register(&wiegand_gpio->misc_dev); + if (status) { + dev_err(wiegand_gpio->dev, "couldn't register misc device\n"); + goto free_misc_group; + } + wiegand_gpio->misc_dev.parent = wiegand_gpio->dev; + + return status; + +free_misc_group: + sysfs_remove_group(&wiegand_gpio->dev->kobj, &wiegand_group); + return status; +} + +static int wiegand_gpio_remove(struct platform_device *device) +{ + struct wiegand_gpio *wiegand_gpio = platform_get_drvdata(device); + + sysfs_remove_group(&wiegand_gpio->dev->kobj, &wiegand_group); + misc_deregister(&wiegand_gpio->misc_dev); + + return 0; +} + +static const struct of_device_id wiegand_gpio_dt_idtable[] = { + { .compatible = "wiegand-gpio", }, + {}, +}; +MODULE_DEVICE_TABLE(of, wiegand_gpio_dt_idtable); + +static struct platform_driver wiegand_gpio_driver = { + .driver = { + .name = "wiegand-gpio", + .of_match_table = wiegand_gpio_dt_idtable, + }, + .probe = wiegand_gpio_probe, + .remove = wiegand_gpio_remove, +}; + +module_platform_driver(wiegand_gpio_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Wiegand write-only driver realized by GPIOs"); +MODULE_AUTHOR("Martin Zaťovič ");