From patchwork Wed Nov 9 22:40:58 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Peter Rosin X-Patchwork-Id: 9420505 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 4DEF76048E for ; Thu, 10 Nov 2016 01:50:03 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 3C07629458 for ; Thu, 10 Nov 2016 01:50:03 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 2EB982945A; Thu, 10 Nov 2016 01:50:02 +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=-1.8 required=2.0 tests=BAD_ENC_HEADER,BAYES_00, DKIM_SIGNED, RCVD_IN_DNSWL_NONE, T_DKIM_INVALID autolearn=no version=3.3.1 Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 809CB29458 for ; Thu, 10 Nov 2016 01:50:00 +0000 (UTC) Received: by alsa0.perex.cz (Postfix, from userid 1000) id 5EB18267255; Thu, 10 Nov 2016 02:49:59 +0100 (CET) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id 428DC267267; Thu, 10 Nov 2016 02:47:38 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id DE7E3266610; Wed, 9 Nov 2016 23:42:12 +0100 (CET) Received: from EUR03-AM5-obe.outbound.protection.outlook.com (mail-eopbgr30108.outbound.protection.outlook.com [40.107.3.108]) by alsa0.perex.cz (Postfix) with ESMTP id 03585265B3E for ; Wed, 9 Nov 2016 23:42:07 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=axentiatech.onmicrosoft.com; s=selector1-axentia-se; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version; bh=tTV6e72fTmDa4yUHOMpuwABElvhzzfOp/XhEposT0N8=; b=E4odqKIe2U98JpI3dRd+vxSWz4jA0Ow6KrPY3eStwUIJZQZzU1AtXS657osSHO1GjMF9Cl8/bx1/jw6cm75mIjcGKR7U/d5jV7ECqHDjLT7XkkhZIKhsgBT6igovz6XpUOFoL5EewNuORufdEJmxHp8rV2tLh8xF7miZLFNwGNk= Authentication-Results: spf=none (sender IP is ) smtp.mailfrom=peda@axentia.se; Received: from localhost.localdomain (217.210.101.82) by VI1PR0201MB2319.eurprd02.prod.outlook.com (10.168.63.21) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P384) id 15.1.707.6; Wed, 9 Nov 2016 22:42:03 +0000 From: Peter Rosin To: Date: Wed, 9 Nov 2016 23:40:58 +0100 Message-ID: <1478731258-26084-4-git-send-email-peda@axentia.se> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1478731258-26084-1-git-send-email-peda@axentia.se> References: <1478731258-26084-1-git-send-email-peda@axentia.se> MIME-Version: 1.0 X-Originating-IP: [217.210.101.82] X-ClientProxiedBy: DB4PR01CA0034.eurprd01.prod.exchangelabs.com (10.242.152.24) To VI1PR0201MB2319.eurprd02.prod.outlook.com (10.168.63.21) X-MS-Office365-Filtering-Correlation-Id: 018a0531-6e28-4cfc-1321-08d408f1a17e X-Microsoft-Exchange-Diagnostics: 1; VI1PR0201MB2319; 2:zqK2dwdwbE/kusa2bG9B/q5NsC353tfacrxsgf7DjNCHwP1M7gd6m2Gt5rkB9Yoq+uHn0rh8U9IPjGmUVNLDcqW9OLMXyLUtGknDNlm60tTAjj4bc8wX6toileaXhkCBpathAsvdj/reccuFdMCFLQfhbzhxW7ozQVuFxe6gqb+fil0gJve2rQ/GQMTZ4kw3j4G4pyHuaFgAUtXXb8z/LQ==; 3:g5m58ENmyI6iFGhxYpSQ6heypx609uG9tmS+VD4KR2gmT4lAVOPXTCCpbX+8RDAhGx3NI+sEQJ/LPXN6v0CW5oqoKHzgFepRlqg1l5JclM4+tF4d9nhJVqjMA1tvcScf9P/m27+yFQmSsUKSp1+yEA== X-Microsoft-Antispam: UriScan:;BCL:0;PCL:0;RULEID:;SRVR:VI1PR0201MB2319; X-Microsoft-Exchange-Diagnostics: 1; VI1PR0201MB2319; 25:/cKWcl9TMJD038JCpyR+sS3atb4ryEJyrr599YyU2J29elbGq2YrfRrITaqvrfrujKfWYc9eN+kNW3IhVs5dWjhtu01qUka8n3LxNXt4PaOsSJgH6QJ8BaWcIzALQloWyokpldIXJYAPDMIsebupofw4w+CCO/sMcPzKlaLEGuej83prAH+lmnVxLol8JXbkdeA1iZPL8ZlooyMZOlzzw2gGqr6HBq6VEc5AlnLVlu8oOIroaL+70La2hC2U2HJCSv33rQNcWRVcPz+UIyrxeIjtuYbdrB5LcfExUXq8LTIsyDobFo6G439vKAGHNAVCGKp1eGZhhlOX49zOywH6ig0qtI3YhmcE2LNHe9yJBuybvNTjxg173mkb4AkcOOm91OSwMYFMdL6GqX1j4sHeTrDO7IK3+TOQ0zMeJIegB+E9kpVzdpjIo0jHyjJtBnb7fQDWdRUubJGCKq3ltsmOKisiJcAI+yEC4OilVzU+Fq8eckLnMW+6aKIer1Mn8oX8lDZv7q3Ej+KuBsI/lQ/JSx23GX3Ta9BaijNdHWDGBPG5Xtk0PrJdLJGQEt1mHdxfpMQCFvYS5lgRCTjc/FCbwqv5PaetpJQQ8Vi9GadfHgJC8P/GPG8GdhIvNI7cUt4aSDs/YxLybjkqyA5Qv2ZXk4aFkhZ2Tolcmzs7RYo/riYsToAxv2w2UKlafIeRwBtqh/Itpt2lGbBYieVkCGumwAg+8U/KDKn6g4fDsmg3y7giD/xThiN9E9DXluPSGg3A X-Microsoft-Exchange-Diagnostics: 1; VI1PR0201MB2319; 31:0xATYY16kQMFu9lt78izuQlnXYc23HyYE5n8opLN1P4OA0DK/wxvTje1IEg25MV315UrWHonEkWDsSnQUoZLByYs7jjgpNcdBSQtQ4hTRNjeIlGWq+l7bIwsTKQBCa/5hR0bfMc9pMIfjJomVuMIimUkcJtuzBwxkNLLlNN8ZcB+ZREDuS1Xhh6Uz8mYgt9wuEAPShgMf6NbIdJJTVMEMOWczZnPIo3h4DmE8asv2EMjozyGFDIiT/JkYY1asP1q; 4:ihTLqAXqYdr27yBwvaf7gP6dB0YP9OZh3FMTRzOQ7XSisBaNTDwEB7PTPu66kOqUt2Drf0SVILfrnz2GMHB7Axwa4IHOC6++7SpkfxUNHdpb3xSP0I91frj5HeFa8GKiR/h8Xczt5ju0pbeXs2xDkywgSaFBZIsCALCFGmQlVKctBGJQL+jG/ux9jkBaLqwFotGpAV75xbKWOBs2xhwHIkJaWcS3Al1rSXaeVg9quEqJxwH6YdkzAYjWf8+G4tc+iUd3DVx1dtqjTWgq+wboiK/gZfhkJonSiOMX9VO8To6ds92pbwO9sReyjkXrku3aVwsNMGCGumqXQwJj9BHHWg21joU6ArM20jnbYmYzyOjTgzTk/nMPeI+oBG8mslfsTqXkqq0UcRJfTZMA+96fKv4fmKJ4tI6qiB1SYw3OMLFbb44wB73BQcVKduxE++YD6G7FQCDlxvfPAxYPVpDiOVpNFT6TIfVIR32Ki8MHASmfgInId6zEE6Dls+v8Rc+9w13l7VUMdETiDPlTQNYKFQ== X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:(21532816269658)(17755550239193); X-Exchange-Antispam-Report-CFA-Test: BCL:0; PCL:0; RULEID:(6040176)(601004)(2401047)(8121501046)(5005006)(3002001)(10201501046)(6043046)(6042046); SRVR:VI1PR0201MB2319; BCL:0; PCL:0; RULEID:; SRVR:VI1PR0201MB2319; X-Forefront-PRVS: 0121F24F22 X-Forefront-Antispam-Report: SFV:NSPM; SFS:(10019020)(4630300001)(6069001)(6009001)(7916002)(189002)(199003)(42186005)(76176999)(50986999)(5003940100001)(2906002)(68736007)(47776003)(5660300001)(106356001)(305945005)(66066001)(77096005)(7846002)(7736002)(105586002)(2351001)(189998001)(74482002)(86362001)(101416001)(33646002)(97736004)(50466002)(36756003)(92566002)(50226002)(6666003)(8676002)(586003)(4326007)(81156014)(48376002)(2950100002)(81166006)(110136003)(6916009)(7416002)(3846002)(6116002)(2004002)(42262002)(309714004); DIR:OUT; SFP:1102; SCL:1; SRVR:VI1PR0201MB2319; H:localhost.localdomain; FPR:; SPF:None; PTR:InfoNoRecords; MX:1; A:1; LANG:en; Received-SPF: None (protection.outlook.com: axentia.se does not designate permitted sender hosts) X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1; VI1PR0201MB2319; 23:exCUKhrEf9xc2W2dxuEtGSGxemxiQTPJHN1bd5T?= =?us-ascii?Q?BF1dGIEdMG3ibajHFSUnAFBMVZeCwzDdUDgTzZcIqpiZaJmJ1mBj0krW5As1?= =?us-ascii?Q?a54tjdhh0Pi77of6UJgDn0ni06/5LXyGpEwRhN8pNGP/ig37hmarCet18QRC?= =?us-ascii?Q?ZoBvxnse7BaGYMhKPzsFWZjWuhWPmOjxJKwmj45IUqhAVRnZJ3MDk4Z7ybs7?= =?us-ascii?Q?w9ciUueiE4vrPjLw+E+Y8nZZ6kCRViySeIcHu/Sav1t+/c08mt5FY85pz3jt?= =?us-ascii?Q?evkHiKPmNCI+MuDN924fCK0ZoCZQC+GBLhdJFLnXTDpzayMMYQSub8czLkFz?= =?us-ascii?Q?ksBk4aC6srQDdVyevSR6dBt7rIBkColgr930yIdMOdAl/YfubsLj1boynxbG?= =?us-ascii?Q?RgbUR5A9CaLEhS7svdBzEp/U/JEF3vrDVhIH9qorYnCUq+LO3c1hClmVBqsp?= =?us-ascii?Q?yutSs8zCBrvlBZJCg2tAtMbZUW6LtwB9CVR29OdHu+9VOgyH5/QM6vVY/jIk?= =?us-ascii?Q?VwZw41eR6e4CpodrtoIzgSMJPfTvteJXbHlQn3DkngYX//MNN6rVzYomDnqe?= =?us-ascii?Q?nYvFTMOQAyb+plFQZaORGkDmEqONydCzxUCcA33bschv3deaNUNjRXBtm2K5?= =?us-ascii?Q?8cZCkNlKB5mH1pdiwyhYEDcce1BhO18tNM9/ZBmKf8woPsGoDqD/pUvbz8lJ?= =?us-ascii?Q?kQOn4PeCLlmRBmSKVKeMpXjWD7dmmSE499Q0T+EwSe9ztdIs6aol2WsKRT18?= =?us-ascii?Q?eeXMwM+ZwdD63ZPcCx9QPbdTHSFsqR3+lceOJaUReG6ioiiFVvS2Fw1XYY9G?= =?us-ascii?Q?yK8z/eLVdH+1nJJ0tXIsRlY8LkG26uIoREs3OW6l4pXgOa4Igd2CvmTAUZhY?= =?us-ascii?Q?zRjbc8ktGA7pYr5fSSzy0YHWcPumVwIxXnevD9c0ADeHS6KCc5VAUbUrgU7q?= =?us-ascii?Q?rqXwq9Y83IOM0A2ppaQJ02v8raf/fdRtp7Z7ybIld3+hn5G3C5lHiVND15T+?= =?us-ascii?Q?HlRd9QBa81dAb3VIfhK2YIn0+DJqG6bLENrkq4gJR3+AiLjIBMC4giBnKrx0?= =?us-ascii?Q?uIt4mea3apmdOmz9UBF8hZFofMZNkcPj/OQMvZy3MOCjG9uyfR2rhNcvwCma?= =?us-ascii?Q?R3Ed2Je1rZdhqX1IeKRyE5wrChZGQ+PzQ?= X-Microsoft-Exchange-Diagnostics: 1; VI1PR0201MB2319; 6:iDxM45aFgdOOzNYjLJgIreSj3OIeVuL8nwI7jDj848fEZSl9f3tyCzSrwRS2e/RNm4mSgSso+bf1SDaPOH83hM+oh+q132LFc15thWPnVOhB715by12pSTsAaVD/2HvBO22QHqIOuaDFpkEm2pRHiKcn+W+CpKgQ7YDVFnnOg4WP0QNET4ecvxjTEIFFQlXzApRnmvReA1zum4ENqndjuw+3SHgs4QpWJfwBbyoStk5YfKl8HyRX4q6zA1sFE9zPb6BSDXA3kcKvQumQKohjKBVmkEVssCVgfbTPtRJjLSXHl83DJO068TVn5PmY4kTgI5L5NOJwwjez4fIi5kcU/A==; 5:vxtzfeuYjs434/2BFPmGPam73u/Rf3KHCZlJGLlOpnWPOd6It0DWR/x2qmQO1aH7QFWMsZXMyt4RqGzkN4hBBifAl4rRSO3X8oTpUx67dO6SlKvVGQmlDH0tU5DBjWefhE6GL/D55nb0CYvYvvS6Kg==; 24:dlAId4WrGzqPDYIwO7ZzKhsnh5LIlwL3c6FJN4rPBLg+nuyOp4I/XRAf1Ukp1GmSTnAXvEgku0cUEKTmIOpAKroYxTv10YBb2xfXv6KXzFM= SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-Microsoft-Exchange-Diagnostics: 1; VI1PR0201MB2319; 7:awNSAkAV6Oq2J7yX2zg8zv0Wln/b1DHWfoOQ6V0CtsbD+W0oxAOHY19UO7scS1oi8hlJBeB5vBTx1HexBbi6XawFlmAi/01Bdcmq5YSMkdyD6vxUJk2h1xfKHyqgrsD1B1JepzpBYyFUlnL+TkAnMovpUhpvVBdHrpD8w19+Ocj7Y0IerTpe5D04X1oTc4gRzPe9alcw3AZ+CS4GlMxk92x3oVMZvZ+LDN7kpWD0V7SsH252MoDb8INgz1cngPrUB86hr9Era+Vgz4wKAGdz3pvwAeHJfIcIS99CZnzxsOgpMMgktqkXssyOIY31F8lxAfGh6SP7z7E0s3hqK9jF1C4Tp8X+E14vegJrVtYogJo= X-OriginatorOrg: axentia.se X-MS-Exchange-CrossTenant-OriginalArrivalTime: 09 Nov 2016 22:42:03.9338 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-Transport-CrossTenantHeadersStamped: VI1PR0201MB2319 Cc: Mark Rutland , devicetree@vger.kernel.org, alsa-devel@alsa-project.org, Takashi Iwai , Nicolas Ferre , Liam Girdwood , Rob Herring , Mark Brown , Peter Rosin Subject: [alsa-devel] [PATCH v2 3/3] ASoC: atmel: tse850: add ASoC driver for the Axentia TSE-850 X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP The TSE-850 is an FM Transmitter Station Equipment, designed to generate baseband signals for FM, mainly the DARC subcarrier, but other signals are also possible. Signed-off-by: Peter Rosin --- MAINTAINERS | 1 + sound/soc/atmel/Kconfig | 10 + sound/soc/atmel/Makefile | 2 + sound/soc/atmel/tse850-pcm5142.c | 472 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 485 insertions(+) create mode 100644 sound/soc/atmel/tse850-pcm5142.c diff --git a/MAINTAINERS b/MAINTAINERS index 4f2ebf3ab51a..4ce45f02d83f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2323,6 +2323,7 @@ M: Peter Rosin L: alsa-devel@alsa-project.org (moderated for non-subscribers) S: Maintained F: Documentation/devicetree/bindings/sound/axentia,* +F: sound/soc/atmel/tse850-pcm5142.c AZ6007 DVB DRIVER M: Mauro Carvalho Chehab diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index 22aec9a1e9a4..4a56f3dfba51 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig @@ -78,4 +78,14 @@ config SND_ATMEL_SOC_PDMIC help Say Y if you want to add support for Atmel ASoC driver for boards using PDMIC. + +config SND_ATMEL_SOC_TSE850_PCM5142 + tristate "ASoC driver for the Axentia TSE-850" + depends on ARCH_AT91 && OF + depends on ATMEL_SSC && I2C + select SND_ATMEL_SOC_SSC_DMA + select SND_SOC_PCM512x_I2C + help + Say Y if you want to add support for the ASoC driver for the + Axentia TSE-850 with a PCM5142 codec. endif diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile index a2b127bd9c87..67e10cbd4ed7 100644 --- a/sound/soc/atmel/Makefile +++ b/sound/soc/atmel/Makefile @@ -13,9 +13,11 @@ snd-atmel-soc-wm8904-objs := atmel_wm8904.o snd-soc-sam9x5-wm8731-objs := sam9x5_wm8731.o snd-atmel-soc-classd-objs := atmel-classd.o snd-atmel-soc-pdmic-objs := atmel-pdmic.o +snd-atmel-soc-tse850-pcm5142-objs := tse850-pcm5142.o obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o obj-$(CONFIG_SND_ATMEL_SOC_WM8904) += snd-atmel-soc-wm8904.o obj-$(CONFIG_SND_AT91_SOC_SAM9X5_WM8731) += snd-soc-sam9x5-wm8731.o obj-$(CONFIG_SND_ATMEL_SOC_CLASSD) += snd-atmel-soc-classd.o obj-$(CONFIG_SND_ATMEL_SOC_PDMIC) += snd-atmel-soc-pdmic.o +obj-$(CONFIG_SND_ATMEL_SOC_TSE850_PCM5142) += snd-atmel-soc-tse850-pcm5142.o diff --git a/sound/soc/atmel/tse850-pcm5142.c b/sound/soc/atmel/tse850-pcm5142.c new file mode 100644 index 000000000000..ac6a814c8ecf --- /dev/null +++ b/sound/soc/atmel/tse850-pcm5142.c @@ -0,0 +1,472 @@ +/* + * TSE-850 audio - ASoC driver for the Axentia TSE-850 with a PCM5142 codec + * + * Copyright (C) 2016 Axentia Technologies AB + * + * Author: Peter Rosin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * loop1 relays + * IN1 +---o +------------+ o---+ OUT1 + * \ / + * + + + * | / | + * +--o +--. | + * | add | | + * | V | + * | .---. | + * DAC +----------->|Sum|---+ + * | '---' | + * | | + * + + + * + * IN2 +---o--+------------+--o---+ OUT2 + * loop2 relays + * + * The 'loop1' gpio pin controlls two relays, which are either in loop + * position, meaning that input and output are directly connected, or + * they are in mixer position, meaning that the signal is passed through + * the 'Sum' mixer. Similarly for 'loop2'. + * + * In the above, the 'loop1' relays are inactive, thus feeding IN1 to the + * mixer (if 'add' is active) and feeding the mixer output to OUT1. The + * 'loop2' relays are active, short-cutting the TSE-850 from channel 2. + * IN1, IN2, OUT1 and OUT2 are TSE-850 connectors and DAC is the PCB name + * of the (filtered) output from the PCM5142 codec. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "atmel_ssc_dai.h" + +struct tse850_priv { + int ssc_id; + + struct gpio_desc *add; + struct gpio_desc *loop1; + struct gpio_desc *loop2; + + struct regulator *ana; + + int add_cache; + int loop1_cache; + int loop2_cache; +}; + +static int tse850_get_mux1(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = tse850->loop1_cache; + + return 0; +} + +static int tse850_put_mux1(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + struct soc_enum *e = (struct soc_enum *)kctrl->private_value; + unsigned int val = ucontrol->value.enumerated.item[0]; + + if (val >= e->items) + return -EINVAL; + + gpiod_set_value_cansleep(tse850->loop1, val); + tse850->loop1_cache = val; + + return snd_soc_dapm_put_enum_double(kctrl, ucontrol); +} + +static int tse850_get_mux2(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = tse850->loop2_cache; + + return 0; +} + +static int tse850_put_mux2(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + struct soc_enum *e = (struct soc_enum *)kctrl->private_value; + unsigned int val = ucontrol->value.enumerated.item[0]; + + if (val >= e->items) + return -EINVAL; + + gpiod_set_value_cansleep(tse850->loop2, val); + tse850->loop2_cache = val; + + return snd_soc_dapm_put_enum_double(kctrl, ucontrol); +} + +int tse850_get_mix(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = tse850->add_cache; + + return 0; +} + +int tse850_put_mix(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + int connect = !!ucontrol->value.integer.value[0]; + + if (tse850->add_cache == connect) + return 0; + + /* + * Hmmm, this gpiod_set_value_cansleep call should probably happen + * inside snd_soc_dapm_mixer_update_power in the loop. + */ + gpiod_set_value_cansleep(tse850->add, connect); + tse850->add_cache = connect; + + snd_soc_dapm_mixer_update_power(dapm, kctrl, connect, NULL); + return 1; +} + +int tse850_get_ana(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + int ret; + + ret = regulator_get_voltage(tse850->ana); + if (ret < 0) + return ret; + + /* + * Map regulator output values like so: + * -11.5V to "Low" (enum 0) + * 11.5V-12.5V to "12V" (enum 1) + * 12.5V-13.5V to "13V" (enum 2) + * ... + * 18.5V-19.5V to "19V" (enum 8) + * 19.5V- to "20V" (enum 9) + */ + if (ret < 11000000) + ret = 11000000; + else if (ret > 20000000) + ret = 20000000; + ret -= 11000000; + ret = (ret + 500000) / 1000000; + + ucontrol->value.enumerated.item[0] = ret; + + return 0; +} + +int tse850_put_ana(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + struct soc_enum *e = (struct soc_enum *)kctrl->private_value; + unsigned int uV = ucontrol->value.enumerated.item[0]; + int ret; + + if (uV >= e->items) + return -EINVAL; + + /* + * Map enum zero (Low) to 2 volts on the regulator, do this since + * the ana regulator is supplied by the system 12V voltage and + * requesting anything below the system voltage causes the system + * voltage to be passed through the regulator. Also, the ana + * regulator induces noise when requesting voltages near the + * system voltage. So, by mapping Low to 2V, that noise is + * eliminated when all that is needed is 12V (the system voltage). + */ + if (uV) + uV = 11000000 + (1000000 * uV); + else + uV = 2000000; + + ret = regulator_set_voltage(tse850->ana, uV, uV); + if (ret < 0) + return ret; + + return snd_soc_dapm_put_enum_double(kctrl, ucontrol); +} + +static const char * const mux_text[] = { "Mixer", "Loop" }; + +static const struct soc_enum mux_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, 2, mux_text); + +static const struct snd_kcontrol_new mux1 = + SOC_DAPM_ENUM_EXT("MUX1", mux_enum, tse850_get_mux1, tse850_put_mux1); + +static const struct snd_kcontrol_new mux2 = + SOC_DAPM_ENUM_EXT("MUX2", mux_enum, tse850_get_mux2, tse850_put_mux2); + +#define TSE850_DAPM_SINGLE_EXT(xname, reg, shift, max, invert, xget, xput) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = xget, \ + .put = xput, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) } + +static const struct snd_kcontrol_new mix[] = { + TSE850_DAPM_SINGLE_EXT("IN Switch", SND_SOC_NOPM, 0, 1, 0, + tse850_get_mix, tse850_put_mix), +}; + +static const char * const ana_text[] = { + "Low", "12V", "13V", "14V", "15V", "16V", "17V", "18V", "19V", "20V" +}; + +static const struct soc_enum ana_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, 9, ana_text); + +static const struct snd_kcontrol_new out = + SOC_DAPM_ENUM_EXT("ANA", ana_enum, tse850_get_ana, tse850_put_ana); + +static const struct snd_soc_dapm_widget tse850_dapm_widgets[] = { + SND_SOC_DAPM_LINE("OUT1", NULL), + SND_SOC_DAPM_LINE("OUT2", NULL), + SND_SOC_DAPM_LINE("IN1", NULL), + SND_SOC_DAPM_LINE("IN2", NULL), + SND_SOC_DAPM_INPUT("DAC"), + SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0), + SOC_MIXER_ARRAY("MIX", SND_SOC_NOPM, 0, 0, mix), + SND_SOC_DAPM_MUX("MUX1", SND_SOC_NOPM, 0, 0, &mux1), + SND_SOC_DAPM_MUX("MUX2", SND_SOC_NOPM, 0, 0, &mux2), + SND_SOC_DAPM_OUT_DRV("OUT", SND_SOC_NOPM, 0, 0, &out, 1), +}; + +/* + * These connections are not entirely correct, since both IN1 and IN2 + * are always fed to MIX (if the "IN switch" is set so), i.e. without + * regard to the loop1 and loop2 relays that according to this only + * control MUX1 and MUX2 but in fact also control how the input signals + * are routed. + * But, 1) I don't know how to do it right, and 2) it doesn't seem to + * matter in practice since nothing is powered in those sections anyway. + */ +static const struct snd_soc_dapm_route tse850_intercon[] = { + { "OUT1", NULL, "MUX1" }, + { "OUT2", NULL, "MUX2" }, + + { "MUX1", "Loop", "IN1" }, + { "MUX1", "Mixer", "OUT" }, + + { "MUX2", "Loop", "IN2" }, + { "MUX2", "Mixer", "OUT" }, + + { "OUT", NULL, "MIX" }, + + { "MIX", NULL, "DAC" }, + { "MIX", "IN Switch", "IN1" }, + { "MIX", "IN Switch", "IN2" }, + + /* connect board input to the codec left channel output pin */ + { "DAC", NULL, "OUTL" }, +}; + +static struct snd_soc_dai_link tse850_dailink = { + .name = "TSE-850", + .stream_name = "TSE-850-PCM", + .codec_dai_name = "pcm512x-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFS, +}; + +static struct snd_soc_card tse850_card = { + .name = "TSE-850-ASoC", + .owner = THIS_MODULE, + .dai_link = &tse850_dailink, + .num_links = 1, + .dapm_widgets = tse850_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tse850_dapm_widgets), + .dapm_routes = tse850_intercon, + .num_dapm_routes = ARRAY_SIZE(tse850_intercon), + .fully_routed = true, +}; + +static int tse850_dt_init(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *codec_np, *cpu_np; + struct snd_soc_card *card = &tse850_card; + struct snd_soc_dai_link *dailink = &tse850_dailink; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + + if (!np) { + dev_err(&pdev->dev, "only device tree supported\n"); + return -EINVAL; + } + + cpu_np = of_parse_phandle(np, "axentia,ssc-controller", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "failed to get dai and pcm info\n"); + return -EINVAL; + } + dailink->cpu_of_node = cpu_np; + dailink->platform_of_node = cpu_np; + tse850->ssc_id = of_alias_get_id(cpu_np, "ssc"); + of_node_put(cpu_np); + + codec_np = of_parse_phandle(np, "axentia,audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "failed to get codec info\n"); + return -EINVAL; + } + dailink->codec_of_node = codec_np; + of_node_put(codec_np); + + return 0; +} + +static int tse850_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &tse850_card; + struct device *dev = card->dev = &pdev->dev; + struct tse850_priv *tse850; + int ret; + + tse850 = devm_kzalloc(dev, sizeof(*tse850), GFP_KERNEL); + if (!tse850) + return -ENOMEM; + + snd_soc_card_set_drvdata(card, tse850); + + ret = tse850_dt_init(pdev); + if (ret) { + dev_err(dev, "failed to init dt info\n"); + return ret; + } + + tse850->add = devm_gpiod_get(dev, "axentia,add", GPIOD_OUT_HIGH); + if (IS_ERR(tse850->add)) { + if (PTR_ERR(tse850->add) != -EPROBE_DEFER) + dev_err(dev, "failed to get 'add' gpio\n"); + return PTR_ERR(tse850->add); + } + tse850->add_cache = 1; + + tse850->loop1 = devm_gpiod_get(dev, "axentia,loop1", GPIOD_OUT_HIGH); + if (IS_ERR(tse850->loop1)) { + if (PTR_ERR(tse850->loop1) != -EPROBE_DEFER) + dev_err(dev, "failed to get 'loop1' gpio\n"); + return PTR_ERR(tse850->loop1); + } + tse850->loop1_cache = 1; + + tse850->loop2 = devm_gpiod_get(dev, "axentia,loop2", GPIOD_OUT_HIGH); + if (IS_ERR(tse850->loop2)) { + if (PTR_ERR(tse850->loop2) != -EPROBE_DEFER) + dev_err(dev, "failed to get 'loop2' gpio\n"); + return PTR_ERR(tse850->loop2); + } + tse850->loop2_cache = 1; + + tse850->ana = devm_regulator_get(dev, "axentia,ana"); + if (IS_ERR(tse850->ana)) { + if (PTR_ERR(tse850->ana) != -EPROBE_DEFER) + dev_err(dev, "failed to get 'ana' regulator\n"); + return PTR_ERR(tse850->ana); + } + + ret = regulator_enable(tse850->ana); + if (ret < 0) { + dev_err(dev, "failed to enable the 'ana' regulator\n"); + return ret; + } + + ret = atmel_ssc_set_audio(tse850->ssc_id); + if (ret != 0) { + dev_err(dev, + "failed to set SSC %d for audio\n", tse850->ssc_id); + goto err_disable_ana; + } + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(dev, "snd_soc_register_card failed\n"); + goto err_put_audio; + } + + return 0; + +err_put_audio: + atmel_ssc_put_audio(tse850->ssc_id); +err_disable_ana: + regulator_disable(tse850->ana); + return ret; +} + +static int tse850_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + atmel_ssc_put_audio(tse850->ssc_id); + regulator_disable(tse850->ana); + + return 0; +} + +static const struct of_device_id tse850_dt_ids[] = { + { .compatible = "axentia,tse850-pcm5142", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tse850_dt_ids); + +static struct platform_driver tse850_driver = { + .driver = { + .name = "axentia-tse850-pcm5142", + .of_match_table = of_match_ptr(tse850_dt_ids), + }, + .probe = tse850_probe, + .remove = tse850_remove, +}; + +module_platform_driver(tse850_driver); + +/* Module information */ +MODULE_AUTHOR("Peter Rosin "); +MODULE_DESCRIPTION("ALSA SoC driver for TSE-850 with PCM5142 codec"); +MODULE_LICENSE("GPL");