From patchwork Fri Oct 13 05:59:28 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yasunari.Takiguchi@sony.com X-Patchwork-Id: 10003465 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 7CFBA602B3 for ; Fri, 13 Oct 2017 05:56:15 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 6CB2628FAE for ; Fri, 13 Oct 2017 05:56:15 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 6148F28FB1; Fri, 13 Oct 2017 05:56:15 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id F326728FAE for ; Fri, 13 Oct 2017 05:56:12 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752468AbdJMF4L (ORCPT ); Fri, 13 Oct 2017 01:56:11 -0400 Received: from mail-co1nam03on0102.outbound.protection.outlook.com ([104.47.40.102]:55392 "EHLO NAM03-CO1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1751542AbdJMF4J (ORCPT ); Fri, 13 Oct 2017 01:56:09 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=Sony.onmicrosoft.com; s=selector1-Sony-onmicrosoft-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version; bh=zd2VyyAQz3Wiy/GDiEi71We7fxqPK/6xB96gTIJlfvU=; b=WwJJUh7oxKT3fIrCOYwkqC7/v3FwT9Lj5kyPtUtyhmKwv7J3Jg1y4DQE+kezfVRw+EkS+Ejr7iko1wrHVZmMt2l8lozOk08g2Xot6f+SloXbfUN/TiTQ868yWZknAq+dT5uGdBmXrKh8d0VGRCCMxrwQWPBsHpyMBNZgJh5pXHQ= Received: from CY4PR13CA0003.namprd13.prod.outlook.com (10.168.161.141) by SN1PR13MB0462.namprd13.prod.outlook.com (10.163.201.156) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P256) id 15.20.77.5; Fri, 13 Oct 2017 05:56:06 +0000 Received: from SN1NAM02FT059.eop-nam02.prod.protection.outlook.com (2a01:111:f400:7e44::202) by CY4PR13CA0003.outlook.office365.com (2603:10b6:903:32::13) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) id 15.20.156.2 via Frontend Transport; Fri, 13 Oct 2017 05:56:06 +0000 Received-SPF: Pass (protection.outlook.com: domain of sony.com designates 117.103.190.43 as permitted sender) receiver=protection.outlook.com; client-ip=117.103.190.43; helo=jp.sony.com; Received: from jp.sony.com (117.103.190.43) by SN1NAM02FT059.mail.protection.outlook.com (10.152.72.177) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P384) id 15.20.77.10 via Frontend Transport; Fri, 13 Oct 2017 05:56:06 +0000 Received: from JPYOKXHT101.jp.sony.com (117.103.191.48) by JPYOKXEG103.jp.sony.com (117.103.190.43) with Microsoft SMTP Server (TLS) id 14.3.361.1; Fri, 13 Oct 2017 05:55:54 +0000 Received: from localhost.localdomain (43.25.41.74) by JPYOKXHT101.jp.sony.com (117.103.191.48) with Microsoft SMTP Server (TLS) id 14.3.361.1; Fri, 13 Oct 2017 05:55:54 +0000 From: To: , , CC: , , Yasunari Takiguchi , Masayuki Yamamoto , Hideki Nozawa , "Kota Yonezawa" , Toshihiko Matsumoto , Satoshi Watanabe Subject: [PATCH v4 02/12] [media] cxd2880-spi: Add support for CXD2880 SPI interface Date: Fri, 13 Oct 2017 14:59:28 +0900 Message-ID: <20171013055928.21132-1-Yasunari.Takiguchi@sony.com> X-Mailer: git-send-email 2.13.0 In-Reply-To: <20171013054635.20946-1-Yasunari.Takiguchi@sony.com> References: <20171013054635.20946-1-Yasunari.Takiguchi@sony.com> MIME-Version: 1.0 X-Originating-IP: [43.25.41.74] X-EOPAttributedMessage: 0 X-MS-Office365-Filtering-HT: Tenant X-Forefront-Antispam-Report: CIP:117.103.190.43; IPV:NLI; CTRY:JP; EFV:NLI; SFV:NSPM; SFS:(10019020)(6009001)(39860400002)(376002)(346002)(2980300002)(438002)(199003)(189002)(2876002)(50466002)(86362001)(110136005)(5660300001)(5890100001)(106002)(189998001)(575784001)(2950100002)(3846002)(8676002)(16526018)(5003940100001)(50226002)(4326008)(36756003)(6116002)(1076002)(106466001)(54906003)(356003)(305945005)(47776003)(6306002)(2906002)(8936002)(49486002)(86152003)(48376002)(6666003)(66066001)(107886003)(7736002)(478600001)(7636002)(76176999)(39060400002)(72206003)(16586007)(2201001)(50986999)(246002)(316002)(2004002); DIR:OUT; SFP:1102; SCL:1; SRVR:SN1PR13MB0462; H:jp.sony.com; FPR:; SPF:Pass; PTR:jpyokxeg103.jp.sony.com; A:1; MX:1; LANG:en; X-Microsoft-Exchange-Diagnostics: 1; SN1NAM02FT059; 1:qE9sYW0qvmb38P2X6Jy6puzeISfUXts9aMz9d9JvvVBWlvUwM6mgxi/k6MCm0r37SmK46xPiHg40yZQEErefD7z7jk9w7Bs8rHATlkDSLDvBpvh4M+SigkFtuVHI4WYC X-MS-PublicTrafficType: Email X-MS-Office365-Filtering-Correlation-Id: d1325da8-670c-4b28-73d4-08d511ff1866 X-Microsoft-Antispam: UriScan:; BCL:0; PCL:0; RULEID:(22001)(2017030254152)(8251501002)(2017052603199)(201703131423075)(201703031133081)(201702281549075); SRVR:SN1PR13MB0462; X-Microsoft-Exchange-Diagnostics: 1; SN1PR13MB0462; 3:e1syNwQvwd8Vp1ytrJX8dCA7grAswHuxt+JvumknUlTPqUNaDG2DGAfNYL2TZJHVGI4kxwlull+/otLgxbbuzLdHzRe7DEG+rA/QYr+N0SxNoB33susgPckkcz7VydjSoi9XwCdp58nky5VSGXj7vYTHDRh6AgUKhwu1bCNDEVSZaqGTx+QP++I6mMpVB5fg2vKpJ7y2f6hx9AMn0Poy0x05jQSsk6OC5I3DCyfrMpb/Qo0M/268Fsnw8ECwSRfTK0hmEsYavKrVajqkjVRAOu6aZovlVXpFeNpnET0v/7zRalsEut7Yo6PA773h9I19esigPluMr1k3yEYsb793EBk0/o9g0GdRTcCx5rgQ4GA=; 25:m27IKsXZU2v2Xdtx35hk4YOOGhzQCpkRBGo4NTVdkP32uXYkPHQb3dahfSa84I3A3B9OvDhXPwtxkANgK59jE63SclADDyiCt8m3FJfiv5BHJxFvuNdTHPOvYLHoLDdaeC+OE0BBBiy8nMkPcQMpBX4laE4M2gZLvlW/wyEP3tUkHqmoLvHZsJ23pXxVKBl/+CeXjXN6P2GsAY0+DwqGqg/krptbl8XOmekbxDfNKgJMBbKw7XIMqlKqRPvcXkEZc9DJ7HyfXH9qNDrhWBYcQQwa1vhg7Kz1iEMmPheu2S+X1qSbbueoCURQhLPmvZ3XTdEU9lTwQN/GqDWAGji10g== X-MS-TrafficTypeDiagnostic: SN1PR13MB0462: X-Microsoft-Exchange-Diagnostics: 1; SN1PR13MB0462; 31:14+yJr494p1LaUbUOa1iV95aj+JpB6DNo2ve4WOzAZRHdm/KFgKH0jaNrGB4piS2xIJneG5krZ8SgArgRLCbtyIzWr8UKDFd6BEgJQtM3YhpwpcABCWxJFhnNc4+Gbf7vXew3wivcG7sTduTULVxqTC8V1tVF0Ed6nhtw1VLjv7UU08sckcr9PhgR1EWTxM4ty8AWo9+qRIAeAuepMzCzW9mOjeULCbAWibvuy5A7/M=; 20:Cyu173+0vJ5kVKGC3EXG9F5/gKM0VyTgGDO/eA6z91cXXHBNN6G/4QxgVH1krD2CSxVLXCrxd2TALRCL3PXySr1YsteyCymOAujzmbyQTg4FV185H4Br68O6+D0okXsgLh4YLGd0Z1j6yGiXvyJj2vn5BPyygT3Mf59hlwHczPP0vBdE1hQafAgStSpzEymcsTEovC90qPQ4JcbMD/HTcqorqHcOwTpjw2uJqn7SnJh1BHDq3M3sgCOEbQxlsQuFbwNmVJcWsaIVaDqO8r0nPGCjZj80yWGvf9AdZ3afsGP/ft/DZtklpXwJQHQj5WZ3MIhmUB+x3dEDh65qNLPJc7cGYmmCxERDXwxhu4znZ/8Kyu4GOBipCgioVxjDCduknBeGMRWUFdJ0FCUQzfiE4ezypcmpTANMaD+3wOXuwiHELRPG4LEih7b/2dDcosA6uRjZ7hvpdYRSmGsMPWdr43W42ybvJsSbY0Au2o4TgnDMoPSlCv1Ib33RCU0rUauJ X-Exchange-Antispam-Report-Test: UriScan:(250305191791016)(182409339516656)(22074186197030)(21532816269658); X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-CFA-Test: BCL:0; PCL:0; RULEID:(100000700101)(100105000095)(100000701101)(100105300095)(100000702101)(100105100095)(6040450)(2401047)(5005006)(8121501046)(100000703101)(100105400095)(3002001)(93006095)(93004095)(10201501046)(6055026)(6041248)(20161123558100)(20161123564025)(20161123560025)(20161123562025)(201703131423075)(201702281528075)(201703061421075)(201703061406153)(20161123555025)(6072148)(201708071742011)(100000704101)(100105200095)(100000705101)(100105500095); SRVR:SN1PR13MB0462; BCL:0; PCL:0; RULEID:(100000800101)(100110000095)(100000801101)(100110300095)(100000802101)(100110100095)(100000803101)(100110400095)(100000804101)(100110200095)(100000805101)(100110500095); SRVR:SN1PR13MB0462; X-Microsoft-Exchange-Diagnostics: 1; SN1PR13MB0462; 4:NpZg8DTmlBOADDbjGSqx5OfM94odQM/OAbNDAhUKvXuBC1uBoJUGj1qTQAYkk5F5K7Jl2v4aZllgdJqqOiBqx+aw7xLnlPlVCRQW56+iwYQr4cObXFmiw9pHe2iitKs4NPMc7cXNGrnrgrZUhYFmfKaoZLBEJ7THbDYjDDvmYNy5MWcEKqtcQASTPGsTNuUQKO2leOkoB0EYgA6aTH37V4CSrNsKy9fR2xOLxK0BBF/eJjOBMgMCIIfIR/4HKwFTqQa6HhsPuD+n8SktRt9sLu0iO8G2jAPz6ZFgjQg6LWbOtQmOMuil5w9bK7+3Ox2WqUy0uZLfKsqsP1uILRkY31/mYFuaxdPbt/LlPqiXjmwRJVVlV5CyOl7qEhfe5ESA2SK0kXaF6h6xkL8nZGtR8Q== X-Forefront-PRVS: 04599F3534 X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1; SN1PR13MB0462; 23:7uj+X7CpeeEEqO/TTXoGvUnearvQ2YgJMX+nYxG0v?= =?us-ascii?Q?Vhi9xWkXkFP5gX76SCYztdS5P5gwdnMVanvT1s/r7IjaShvXNlz6RcWr481a?= =?us-ascii?Q?pHqELjxsCeIu47taCy17dNYEPKjEa1qtGwhZeGlFsOBrXP5VF1b4amDb2mKf?= =?us-ascii?Q?2TGGYbeDnW57VN0KC8/cqfp441JrBKtBGfL7VnsmGk4HELfm7WUbMpqnQcw5?= =?us-ascii?Q?0XKqE978/+MU1JOQg8TVMjbV8bxVIxLwtwrdpN6GV7dBpTXAwvpTJnpX2W7p?= =?us-ascii?Q?WbaNk6hGZhF+yVGxrJlMJvEJIyzIlYs6Mow009eB4J7ON2eF1B09eOpm23z3?= =?us-ascii?Q?xO2ST8onIVYWbFvSvtw0kzvAfvEgl6reZEyECoxEnPt6s5Hx8o19yMKzNKwV?= =?us-ascii?Q?wyoGcORtb7HUjILkkhuM4BEboQPCkYr07xuQDzgX1MKHic40gnz/gLw4/fxe?= =?us-ascii?Q?7OOTd/EBjMLPsIaPcLgxMJnmfNHvZKg/G3T73ZGW+3RZC5oEJPGBzdXmOgqg?= =?us-ascii?Q?qoSM+6tFr+52wmn7bsv+3mt3VsVNp+t0P8aJ0JccuTNYcKf0Driu+A1M6jsi?= =?us-ascii?Q?2LcZUTUlkhuA9oT5bYxfQkOxIYu2+dDULcVjoBaVRq0X06SiI2nElhaSPv7S?= =?us-ascii?Q?NdwvCOGtNEfiuHm152SwcRfSdbaA/1/2HX9n5PGCTrTp2fg1uKm31cgMYLde?= =?us-ascii?Q?8elJ6x1hvEoNyTXhu/QAEmb8/Ul2KrMKbMaiTthO0bI4MSpEFaNz8+3411Ym?= =?us-ascii?Q?YBkPUBhLgg5MX2eYKJL0SLeHssJw0CexBrDy6PJCkTR2txTkqSHZk7mobfkJ?= =?us-ascii?Q?aiun5O23I4Grzr+gTRXacJolkz/dBkq5z6VhhBEP8yw+oFQsrTZPpe+bhDId?= =?us-ascii?Q?1uLMTIphv4S1K32UNaXWuRb0mZgEB1jbB8+4nKJV7LawaFE6CDz99brhqPQj?= =?us-ascii?Q?x1pOPZJ72mdHjFPz+vgshvJWJL6hCktx9a3uGMRs9d3wPwHECGiZmvJBZHHf?= =?us-ascii?Q?aNEy4/1G1BelNRWiK0Rk4HrAG5MdCY7qHr2Wh7rJkCjBbP4OyUCALmf1w1wP?= =?us-ascii?Q?qcn5XiMib+q2TvJJdYzytn6afnDe1rGVNPRQGbc2DBr4W+IgL2h8nmj2L6Ps?= =?us-ascii?Q?HOF5kKNwbvas7+7SmrZEdr6G8CB826QOWEO+iEcce4eUNJ9BWzuRrwyB8BsX?= =?us-ascii?Q?/chC6j7DcelrZz6pmbozELUWCG5EWmhfAq/CPMuSJGGwwGRug4cBUegbg=3D?= =?us-ascii?Q?=3D?= X-Microsoft-Exchange-Diagnostics: 1; SN1PR13MB0462; 6:kaalkq+sjAslcIwUXSMqc7VMoTB1ISPR7CG7sEK5kibPyB9+P2SazjNgfKZdo/yahg2s3aQLrOjuHCJ1lq7xZ5fxZBHqTU9YzUTzU6M8bb/QOU60oGyK31D3B2afeFUDxTZhe8ljFsfH8oo0ATa8fiT8PWFgUof4a1O3rMGj2Sv53W5iqM3whr1itrNJ97LXt1H5wbykCaA83hmBbH10zjKr3A46oYKP9c+Sf38JYVxY6nuxIKEJfPa+wlzgnRh+ideDP9a/AMFdp9+lMGMKHLFgjWC4bkBTM08I0B0v4dGAvo3N9WAJKGbjPeDyFKKgli5Q1R0OtFUF4EOr/MsJ+Q==; 5:mJZT5kNh0isj7kitlK5kVx7pFR09yzhqlxvWbJ49D7BVK/lvSStmJpx/TaEyvqBtXGzB6p2bbk4T7RTfdFfozjsPM/7M+cROJ+vQebHugpBlWtlIRjN8vf2QbpGJE9oLM3cLFtExDdtgpaLG6EvWCA==; 24:kIHjzkg0XiQzkc3wQ4NUC16XMlifA2pEG+3cVfQA4qP/ehBPRP6AvHHBHzeQNqpQzmYjAlb/2IYx5PdvUW43U2eV9wI9QNzlYwu+iofzZzU=; 7:Rz/cRatX1/IJ+bTvNxlfyRtX49QFws0jmnBC598T7B17W/fnhth48YxqNbhiDEW/uF7yinqL4Bwe7S0/XADswH8W6b6rlKP5FcDVcFrWfbaGV3r2NLwy7okMNJhk7sgtEfmCcr54SGfQ0p1Gz2lPKxdRPmsjg0dfFxgp0/ehR/oUi2ZzqQNCjjmJZi+Ak6gMXmgcmr2sWlv/AabwVXeUe/HSmpE8WekTAyVdi7noQDM= SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-OriginatorOrg: sony.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 13 Oct 2017 05:56:06.1962 (UTC) X-MS-Exchange-CrossTenant-Id: 66c65d8a-9158-4521-a2d8-664963db48e4 X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=66c65d8a-9158-4521-a2d8-664963db48e4; Ip=[117.103.190.43]; Helo=[jp.sony.com] X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: SN1PR13MB0462 Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Yasunari Takiguchi This is the SPI adapter part of the driver for the Sony CXD2880 DVB-T2/T tuner + demodulator. Signed-off-by: Yasunari Takiguchi Signed-off-by: Masayuki Yamamoto Signed-off-by: Hideki Nozawa Signed-off-by: Kota Yonezawa Signed-off-by: Toshihiko Matsumoto Signed-off-by: Satoshi Watanabe --- [Change list] Changes in V4 drivers/media/spi/cxd2880-spi.c -removed Camel case -removed unnecessary initialization at variable declaration -removed unnecessary brace {} Changes in V3 drivers/media/spi/cxd2880-spi.c -adjusted of indent spaces -removed unnecessary cast -changed debugging code -changed timeout method -modified coding style of if() -changed hexadecimal code to lower case. Changes in V2 drivers/media/spi/cxd2880-spi.c -Modified PID filter setting. drivers/media/spi/cxd2880-spi.c | 695 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 695 insertions(+) create mode 100644 drivers/media/spi/cxd2880-spi.c diff --git a/drivers/media/spi/cxd2880-spi.c b/drivers/media/spi/cxd2880-spi.c new file mode 100644 index 000000000000..387cb32f90b8 --- /dev/null +++ b/drivers/media/spi/cxd2880-spi.c @@ -0,0 +1,695 @@ +/* + * cxd2880-spi.c + * Sony CXD2880 DVB-T2/T tuner + demodulator driver + * SPI adapter + * + * Copyright (C) 2016, 2017 Sony Semiconductor Solutions Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; version 2 of the License. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see . + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__ + +#include +#include + +#include "dvb_demux.h" +#include "dmxdev.h" +#include "dvb_frontend.h" +#include "cxd2880.h" + +#define CXD2880_MAX_FILTER_SIZE 32 +#define BURST_WRITE_MAX 128 +#define MAX_TRANS_PACKET 300 + +struct cxd2880_ts_buf_info { + u8 read_ready; + u8 almost_full; + u8 almost_empty; + u8 overflow; + u8 underflow; + u16 packet_num; +}; + +struct cxd2880_pid_config { + u8 is_enable; + u16 pid; +}; + +struct cxd2880_pid_filter_config { + u8 is_negative; + struct cxd2880_pid_config pid_config[CXD2880_MAX_FILTER_SIZE]; +}; + +struct cxd2880_dvb_spi { + struct dvb_frontend dvb_fe; + struct dvb_adapter adapter; + struct dvb_demux demux; + struct dmxdev dmxdev; + struct dmx_frontend dmx_fe; + struct task_struct *cxd2880_ts_read_thread; + struct spi_device *spi; + struct mutex spi_mutex; /* For SPI access exclusive control */ + int feed_count; + int all_pid_feed_count; + u8 *ts_buf; + struct cxd2880_pid_filter_config filter_config; +}; + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static int cxd2880_write_spi(struct spi_device *spi, u8 *data, u32 size) +{ + struct spi_message msg; + struct spi_transfer tx; + int ret; + + if ((!spi) || (!data)) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + memset(&tx, 0, sizeof(tx)); + tx.tx_buf = data; + tx.len = size; + + spi_message_init(&msg); + spi_message_add_tail(&tx, &msg); + ret = spi_sync(spi, &msg); + + return ret; +} + +static int cxd2880_write_reg(struct spi_device *spi, + u8 sub_address, const u8 *data, u32 size) +{ + u8 send_data[BURST_WRITE_MAX + 4]; + const u8 *write_data_top = NULL; + int ret = 0; + + if ((!spi) || (!data)) { + pr_err("invalid arg\n"); + return -EINVAL; + } + if (size > BURST_WRITE_MAX) { + pr_err("data size > WRITE_MAX\n"); + return -EINVAL; + } + + if (sub_address + size > 0x100) { + pr_err("out of range\n"); + return -EINVAL; + } + + send_data[0] = 0x0e; + write_data_top = data; + + while (size > 0) { + send_data[1] = sub_address; + if (size > 255) + send_data[2] = 255; + else + send_data[2] = (u8)size; + + memcpy(&send_data[3], write_data_top, send_data[2]); + + ret = cxd2880_write_spi(spi, send_data, send_data[2] + 3); + if (ret) { + pr_err("write spi failed %d\n", ret); + break; + } + sub_address += send_data[2]; + write_data_top += send_data[2]; + size -= send_data[2]; + } + + return ret; +} + +static int cxd2880_spi_read_ts(struct spi_device *spi, + u8 *read_data, + u32 packet_num) +{ + int ret; + u8 data[3]; + struct spi_message message; + struct spi_transfer transfer[2]; + + if ((!spi) || (!read_data) || (!packet_num)) { + pr_err("invalid arg\n"); + return -EINVAL; + } + if (packet_num > 0xffff) { + pr_err("packet num > 0xffff\n"); + return -EINVAL; + } + + data[0] = 0x10; + data[1] = (packet_num >> 8) & 0xff; + data[2] = packet_num & 0xff; + + spi_message_init(&message); + memset(transfer, 0, sizeof(transfer)); + + transfer[0].len = 3; + transfer[0].tx_buf = data; + spi_message_add_tail(&transfer[0], &message); + transfer[1].len = packet_num * 188; + transfer[1].rx_buf = read_data; + spi_message_add_tail(&transfer[1], &message); + + ret = spi_sync(spi, &message); + if (ret) + pr_err("spi_write_then_read failed\n"); + + return ret; +} + +static int cxd2880_spi_read_ts_buffer_info(struct spi_device *spi, + struct cxd2880_ts_buf_info *info) +{ + u8 send_data = 0x20; + u8 recv_data[2]; + int ret; + + if ((!spi) || (!info)) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + ret = spi_write_then_read(spi, &send_data, 1, + recv_data, sizeof(recv_data)); + if (ret) + pr_err("spi_write_then_read failed\n"); + + info->read_ready = (recv_data[0] & 0x80) ? 1 : 0; + info->almost_full = (recv_data[0] & 0x40) ? 1 : 0; + info->almost_empty = (recv_data[0] & 0x20) ? 1 : 0; + info->overflow = (recv_data[0] & 0x10) ? 1 : 0; + info->underflow = (recv_data[0] & 0x08) ? 1 : 0; + info->packet_num = ((recv_data[0] & 0x07) << 8) | recv_data[1]; + + return ret; +} + +static int cxd2880_spi_clear_ts_buffer(struct spi_device *spi) +{ + u8 data = 0x03; + int ret; + + ret = cxd2880_write_spi(spi, &data, 1); + + if (ret) + pr_err("write spi failed\n"); + + return ret; +} + +static int cxd2880_set_pid_filter(struct spi_device *spi, + struct cxd2880_pid_filter_config *cfg) +{ + u8 data[65]; + int i; + u16 pid = 0; + int ret; + + if (!spi) { + pr_err("ivnalid arg\n"); + return -EINVAL; + } + + data[0] = 0x00; + ret = cxd2880_write_reg(spi, 0x00, &data[0], 1); + if (ret) + return ret; + if (!cfg) { + data[0] = 0x02; + ret = cxd2880_write_reg(spi, 0x50, &data[0], 1); + if (ret) + return ret; + } else { + data[0] = cfg->is_negative ? 0x01 : 0x00; + + for (i = 0; i < CXD2880_MAX_FILTER_SIZE; i++) { + pid = cfg->pid_config[i].pid; + if (cfg->pid_config[i].is_enable) { + data[1 + (i * 2)] = (pid >> 8) | 0x20; + data[2 + (i * 2)] = pid & 0xff; + } else { + data[1 + (i * 2)] = 0x00; + data[2 + (i * 2)] = 0x00; + } + } + ret = cxd2880_write_reg(spi, 0x50, data, 65); + if (ret) + return ret; + } + + return 0; +} + +static int cxd2880_update_pid_filter(struct cxd2880_dvb_spi *dvb_spi, + struct cxd2880_pid_filter_config *cfg, + bool is_all_pid_filter) +{ + int ret; + + if ((!dvb_spi) || (!cfg)) { + pr_err("invalid arg.\n"); + return -EINVAL; + } + + mutex_lock(&dvb_spi->spi_mutex); + if (is_all_pid_filter) { + struct cxd2880_pid_filter_config tmpcfg; + + memset(&tmpcfg, 0, sizeof(tmpcfg)); + tmpcfg.is_negative = 1; + tmpcfg.pid_config[0].is_enable = 1; + tmpcfg.pid_config[0].pid = 0x1fff; + + ret = cxd2880_set_pid_filter(dvb_spi->spi, &tmpcfg); + } else { + ret = cxd2880_set_pid_filter(dvb_spi->spi, cfg); + } + mutex_unlock(&dvb_spi->spi_mutex); + + if (ret) + pr_err("set_pid_filter failed\n"); + + return ret; +} + +static int cxd2880_ts_read(void *arg) +{ + struct cxd2880_dvb_spi *dvb_spi = NULL; + struct cxd2880_ts_buf_info info; + unsigned int elapsed = 0; + unsigned long start_time = 0; + u32 i; + int ret; + + dvb_spi = arg; + if (!dvb_spi) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + ret = cxd2880_spi_clear_ts_buffer(dvb_spi->spi); + if (ret) { + pr_err("set_clear_ts_buffer failed\n"); + return ret; + } + start_time = jiffies; + while (!kthread_should_stop()) { + elapsed = jiffies_to_msecs(jiffies - start_time); + ret = cxd2880_spi_read_ts_buffer_info(dvb_spi->spi, + &info); + if (ret) { + pr_err("spi_read_ts_buffer_info error\n"); + return ret; + } + + if (info.packet_num > MAX_TRANS_PACKET) { + for (i = 0; i < info.packet_num / MAX_TRANS_PACKET; + i++) { + cxd2880_spi_read_ts(dvb_spi->spi, + dvb_spi->ts_buf, + MAX_TRANS_PACKET); + dvb_dmx_swfilter(&dvb_spi->demux, + dvb_spi->ts_buf, + MAX_TRANS_PACKET * 188); + } + start_time = jiffies; + } else if ((info.packet_num > 0) && (elapsed >= 500)) { + cxd2880_spi_read_ts(dvb_spi->spi, + dvb_spi->ts_buf, + info.packet_num); + dvb_dmx_swfilter(&dvb_spi->demux, + dvb_spi->ts_buf, + info.packet_num * 188); + start_time = jiffies; + } else { + usleep_range(10000, 11000); + } + } + + return 0; +} + +static int cxd2880_start_feed(struct dvb_demux_feed *feed) +{ + int ret = 0; + int i = 0; + struct dvb_demux *demux = NULL; + struct cxd2880_dvb_spi *dvb_spi = NULL; + + if (!feed) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + demux = feed->demux; + if (!demux) { + pr_err("feed->demux is NULL\n"); + return -EINVAL; + } + dvb_spi = demux->priv; + + if (dvb_spi->feed_count == CXD2880_MAX_FILTER_SIZE) { + pr_err("Exceeded maximum PID count (32)."); + pr_err("Selected PID cannot be enabled.\n"); + return -EBUSY; + } + + if (feed->pid == 0x2000) { + if (dvb_spi->all_pid_feed_count == 0) { + ret = cxd2880_update_pid_filter(dvb_spi, + &dvb_spi->filter_config, + true); + if (ret) { + pr_err("update pid filter failed\n"); + return ret; + } + } + dvb_spi->all_pid_feed_count++; + + pr_debug("all PID feed (count = %d)\n", + dvb_spi->all_pid_feed_count); + } else { + struct cxd2880_pid_filter_config cfgtmp; + + cfgtmp = dvb_spi->filter_config; + + for (i = 0; i < CXD2880_MAX_FILTER_SIZE; i++) { + if (cfgtmp.pid_config[i].is_enable == 0) { + cfgtmp.pid_config[i].is_enable = 1; + cfgtmp.pid_config[i].pid = feed->pid; + pr_debug("store PID %d to #%d\n", + feed->pid, i); + break; + } + } + if (i == CXD2880_MAX_FILTER_SIZE) { + pr_err("PID filter is full. Assumed bug.\n"); + return -EBUSY; + } + if (!dvb_spi->all_pid_feed_count) + ret = cxd2880_update_pid_filter(dvb_spi, + &cfgtmp, + false); + if (ret) + return ret; + + dvb_spi->filter_config = cfgtmp; + } + + if (dvb_spi->feed_count == 0) { + dvb_spi->ts_buf = + kmalloc(MAX_TRANS_PACKET * 188, + GFP_KERNEL | GFP_DMA); + if (!dvb_spi->ts_buf) { + pr_err("ts buffer allocate failed\n"); + memset(&dvb_spi->filter_config, 0, + sizeof(dvb_spi->filter_config)); + dvb_spi->all_pid_feed_count = 0; + return -ENOMEM; + } + dvb_spi->cxd2880_ts_read_thread = kthread_run(cxd2880_ts_read, + dvb_spi, + "cxd2880_ts_read"); + if (IS_ERR(dvb_spi->cxd2880_ts_read_thread)) { + pr_err("kthread_run failed/\n"); + kfree(dvb_spi->ts_buf); + dvb_spi->ts_buf = NULL; + memset(&dvb_spi->filter_config, 0, + sizeof(dvb_spi->filter_config)); + dvb_spi->all_pid_feed_count = 0; + return PTR_ERR(dvb_spi->cxd2880_ts_read_thread); + } + } + + dvb_spi->feed_count++; + + pr_debug("start feed (count %d)\n", dvb_spi->feed_count); + return 0; +} + +static int cxd2880_stop_feed(struct dvb_demux_feed *feed) +{ + int i = 0; + int ret; + struct dvb_demux *demux = NULL; + struct cxd2880_dvb_spi *dvb_spi = NULL; + + if (!feed) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + demux = feed->demux; + if (!demux) { + pr_err("feed->demux is NULL\n"); + return -EINVAL; + } + dvb_spi = demux->priv; + + if (!dvb_spi->feed_count) { + pr_err("no feed is started\n"); + return -EINVAL; + } + + if (feed->pid == 0x2000) { + /* + * Special PID case. + * Number of 0x2000 feed request was stored + * in dvb_spi->all_pid_feed_count. + */ + if (dvb_spi->all_pid_feed_count <= 0) { + pr_err("PID %d not found.\n", feed->pid); + return -EINVAL; + } + dvb_spi->all_pid_feed_count--; + } else { + struct cxd2880_pid_filter_config cfgtmp; + + cfgtmp = dvb_spi->filter_config; + + for (i = 0; i < CXD2880_MAX_FILTER_SIZE; i++) { + if (feed->pid == cfgtmp.pid_config[i].pid && + cfgtmp.pid_config[i].is_enable != 0) { + cfgtmp.pid_config[i].is_enable = 0; + cfgtmp.pid_config[i].pid = 0; + pr_debug("removed PID %d from #%d\n", + feed->pid, i); + break; + } + } + dvb_spi->filter_config = cfgtmp; + + if (i == CXD2880_MAX_FILTER_SIZE) { + pr_err("PID %d not found\n", feed->pid); + return -EINVAL; + } + } + + ret = cxd2880_update_pid_filter(dvb_spi, + &dvb_spi->filter_config, + dvb_spi->all_pid_feed_count > 0); + dvb_spi->feed_count--; + + if (dvb_spi->feed_count == 0) { + int ret_stop = 0; + + ret_stop = kthread_stop(dvb_spi->cxd2880_ts_read_thread); + if (ret_stop) { + pr_err("'kthread_stop failed. (%d)\n", ret_stop); + ret = ret_stop; + } + kfree(dvb_spi->ts_buf); + dvb_spi->ts_buf = NULL; + } + + pr_debug("stop feed ok.(count %d)\n", dvb_spi->feed_count); + + return ret; +} + +static const struct of_device_id cxd2880_spi_of_match[] = { + { .compatible = "sony,cxd2880" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, cxd2880_spi_of_match); + +static int +cxd2880_spi_probe(struct spi_device *spi) +{ + int ret; + struct cxd2880_dvb_spi *dvb_spi = NULL; + struct cxd2880_config config; + + if (!spi) { + pr_err("invalid arg.\n"); + return -EINVAL; + } + + dvb_spi = kzalloc(sizeof(struct cxd2880_dvb_spi), GFP_KERNEL); + if (!dvb_spi) + return -ENOMEM; + + dvb_spi->spi = spi; + mutex_init(&dvb_spi->spi_mutex); + dev_set_drvdata(&spi->dev, dvb_spi); + config.spi = spi; + config.spi_mutex = &dvb_spi->spi_mutex; + + ret = dvb_register_adapter(&dvb_spi->adapter, + "CXD2880", + THIS_MODULE, + &spi->dev, + adapter_nr); + if (ret < 0) { + pr_err("dvb_register_adapter() failed\n"); + goto fail_adapter; + } + + if (!dvb_attach(cxd2880_attach, &dvb_spi->dvb_fe, &config)) { + pr_err("cxd2880_attach failed\n"); + goto fail_attach; + } + + ret = dvb_register_frontend(&dvb_spi->adapter, + &dvb_spi->dvb_fe); + if (ret < 0) { + pr_err("dvb_register_frontend() failed\n"); + goto fail_frontend; + } + + dvb_spi->demux.dmx.capabilities = DMX_TS_FILTERING; + dvb_spi->demux.priv = dvb_spi; + dvb_spi->demux.filternum = CXD2880_MAX_FILTER_SIZE; + dvb_spi->demux.feednum = CXD2880_MAX_FILTER_SIZE; + dvb_spi->demux.start_feed = cxd2880_start_feed; + dvb_spi->demux.stop_feed = cxd2880_stop_feed; + + ret = dvb_dmx_init(&dvb_spi->demux); + if (ret < 0) { + pr_err("dvb_dmx_init() failed\n"); + goto fail_dmx; + } + + dvb_spi->dmxdev.filternum = CXD2880_MAX_FILTER_SIZE; + dvb_spi->dmxdev.demux = &dvb_spi->demux.dmx; + dvb_spi->dmxdev.capabilities = 0; + ret = dvb_dmxdev_init(&dvb_spi->dmxdev, + &dvb_spi->adapter); + if (ret < 0) { + pr_err("dvb_dmxdev_init() failed\n"); + goto fail_dmxdev; + } + + dvb_spi->dmx_fe.source = DMX_FRONTEND_0; + ret = dvb_spi->demux.dmx.add_frontend(&dvb_spi->demux.dmx, + &dvb_spi->dmx_fe); + if (ret < 0) { + pr_err("add_frontend() failed\n"); + goto fail_dmx_fe; + } + + ret = dvb_spi->demux.dmx.connect_frontend(&dvb_spi->demux.dmx, + &dvb_spi->dmx_fe); + if (ret < 0) { + pr_err("dvb_register_frontend() failed\n"); + goto fail_fe_conn; + } + + pr_info("Sony CXD2880 has successfully attached.\n"); + + return 0; + +fail_fe_conn: + dvb_spi->demux.dmx.remove_frontend(&dvb_spi->demux.dmx, + &dvb_spi->dmx_fe); +fail_dmx_fe: + dvb_dmxdev_release(&dvb_spi->dmxdev); +fail_dmxdev: + dvb_dmx_release(&dvb_spi->demux); +fail_dmx: + dvb_unregister_frontend(&dvb_spi->dvb_fe); +fail_frontend: + dvb_frontend_detach(&dvb_spi->dvb_fe); +fail_attach: + dvb_unregister_adapter(&dvb_spi->adapter); +fail_adapter: + kfree(dvb_spi); + return ret; +} + +static int +cxd2880_spi_remove(struct spi_device *spi) +{ + struct cxd2880_dvb_spi *dvb_spi; + + if (!spi) { + pr_err("invalid arg\n"); + return -EINVAL; + } + + dvb_spi = dev_get_drvdata(&spi->dev); + + if (!dvb_spi) { + pr_err("failed\n"); + return -EINVAL; + } + dvb_spi->demux.dmx.remove_frontend(&dvb_spi->demux.dmx, + &dvb_spi->dmx_fe); + dvb_dmxdev_release(&dvb_spi->dmxdev); + dvb_dmx_release(&dvb_spi->demux); + dvb_unregister_frontend(&dvb_spi->dvb_fe); + dvb_frontend_detach(&dvb_spi->dvb_fe); + dvb_unregister_adapter(&dvb_spi->adapter); + + kfree(dvb_spi); + pr_info("cxd2880_spi remove ok.\n"); + + return 0; +} + +static const struct spi_device_id cxd2880_spi_id[] = { + { "cxd2880", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, cxd2880_spi_id); + +static struct spi_driver cxd2880_spi_driver = { + .driver = { + .name = "cxd2880", + .of_match_table = cxd2880_spi_of_match, + }, + .id_table = cxd2880_spi_id, + .probe = cxd2880_spi_probe, + .remove = cxd2880_spi_remove, +}; +module_spi_driver(cxd2880_spi_driver); + +MODULE_DESCRIPTION( +"Sony CXD2880 DVB-T2/T tuner + demodulator drvier SPI adapter"); +MODULE_AUTHOR("Sony Semiconductor Solutions Corporation"); +MODULE_LICENSE("GPL v2");