From patchwork Tue Nov 15 18:38:15 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Peter Rosin X-Patchwork-Id: 9430731 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 C0FCD60484 for ; Wed, 16 Nov 2016 00:17:45 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id AE0A528CBD for ; Wed, 16 Nov 2016 00:17:45 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id A069E28CC1; Wed, 16 Nov 2016 00:17:45 +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 06AD528CBD for ; Wed, 16 Nov 2016 00:17:43 +0000 (UTC) Received: by alsa0.perex.cz (Postfix, from userid 1000) id 40DCA26733F; Wed, 16 Nov 2016 01:17:42 +0100 (CET) Received: from alsa0.perex.cz (localhost [127.0.0.1]) by alsa0.perex.cz (Postfix) with ESMTP id B1ACA266942; Wed, 16 Nov 2016 01:15:22 +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 39D252668F0; Tue, 15 Nov 2016 19:39:09 +0100 (CET) Received: from EUR01-VE1-obe.outbound.protection.outlook.com (mail-ve1eur01on0114.outbound.protection.outlook.com [104.47.1.114]) by alsa0.perex.cz (Postfix) with ESMTP id 5303E26663B for ; Tue, 15 Nov 2016 19:39:03 +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=P1WAUGKu3I3+RxQDS0VAY/Eqgz1kwLzH1Y+H+yAuOh8=; b=NvSVklXpCQvyUuDoQhXHhKKcO2p/id+6yzCN0PFHrwUSTApZlLn3VCV/KwGhCN35EE2R6xfo0wjU7p1PLz+Zznsf6/UH13oiMD3e7Be3X1pfpOSd9XdvvQ39u/0jDj8dmxWfu+i0GtNyBcH9lz9ozhmb9bu/eQcUg19jV1XWwD8= Authentication-Results: spf=none (sender IP is ) smtp.mailfrom=peda@axentia.se; Received: from localhost.localdomain (217.210.101.82) by AM5PR0201MB2308.eurprd02.prod.outlook.com (10.169.243.7) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P384) id 15.1.721.10; Tue, 15 Nov 2016 18:38:58 +0000 From: Peter Rosin To: Date: Tue, 15 Nov 2016 19:38:15 +0100 Message-ID: <1479235095-13441-4-git-send-email-peda@axentia.se> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1479235095-13441-1-git-send-email-peda@axentia.se> References: <1479235095-13441-1-git-send-email-peda@axentia.se> MIME-Version: 1.0 X-Originating-IP: [217.210.101.82] X-ClientProxiedBy: DB4PR01CA0059.eurprd01.prod.exchangelabs.com (10.242.152.49) To AM5PR0201MB2308.eurprd02.prod.outlook.com (10.169.243.7) X-Microsoft-Exchange-Diagnostics: 1; AM5PR0201MB2308; 2:gfcDwXUvbmjD22K3R0jtTRe4S5R8h1WhmCjh5ZjcKJM2BOuE7LL4sNxtNUtjqmZP2GiJLYiMFZif1IgBI81RqXenibz/9xx8na7poSkcp3kvQww35vshp7VXiaSpRMoxaJG7cirkNwm7OGZjwtl9sDdzmu1+G3yNGvEduJGLXGA=; 3:3b2pZdo4KsvrZEteBO+k96+AxieovTEINal0Ddzlbv1UOESrKWLU8LD0Ba2dwPtdYozoZo73/MZvIqba1DQOd56sOnrbTF3q5Ou0ujQXVEHs53Va2Mrg8p4j3IAgE9/penKnDem/nCzQrRvOja7xggF93ObpT7dAsPywgotPB2s=; 25:MvimOL/P8P2FbysrJT2uzeI67kHKnVfEiTbMd8XQcJUiXVV1Gt8UbZX4a3AjpR1Zkt+RnNI6k+KJHHnEHNgkuEjHmMwSNYDAI+oTajeEYq8w3OCz55WiocjTWG2avxOJwECaOc4Dr4Erpn3z7wzlLzDGyepDgXszBwpRC+aq7fsu2LMUaNEbxPxvBuMEsqzpRSYLD+MFLo1MiPcXTt+J0hG/68/ZAW0gIK17PjjRdp5RjoXeCzZDETwjFYNYhxR6yB+5n1slY2Yx6mDRggidVfOwxuopfpD3JJ88BybQPl5Vdu3ES/ZJOWRkJAs4nfQJR4Y6XNHMM2In5qWKy05qrvmmCrxxaPeo3amV5kJjwxu3xHaSu9jHMNyrx2J2rIAE/Vh4rdxn5XH24aQRyqr6pXmZPGbmShJ92GffUNJnq8NiQa6jHhTpr7DatFPF/QNS X-MS-Office365-Filtering-Correlation-Id: 139274ef-ad6a-4ca8-2787-08d40d86aa46 X-Microsoft-Antispam: UriScan:; BCL:0; PCL:0; RULEID:(22001); SRVR:AM5PR0201MB2308; X-Microsoft-Exchange-Diagnostics: 1; AM5PR0201MB2308; 31:Z2YNsd1bVeCSih/90ISpgcMnBDeb7CM90TflvzYa37IXpUvQ/RraNxd6UJG+3581in+h1+McQ1gB6SfKA0PTjjz/zhGVLXdMC4T0Uem0iFA0HjSzsJb516CLiMK4vvDAmbAfnEwd1kR51jcxlx9otl/yZRZwCufmXSip4TslIriN1O43OguAWoGbdxcVWcgAfgU3jQ5ARVDLWOTa6DcWWdzPHvGdOxLPqFGVliJzdwve0Gb5cKK13gtcjCpzTKvZ; 4:BHirZ/CdHeUK2VAt+X6GE3wzkCEUZjHcoFLn9Bjyczs5qtoe7DEYABwEaLnieCW3bE1/J8+OZIaS1Ux+MAaF73Te+iOzL9y39dzFAD+hH9up3U4aZRhVIr4NBBbFunGGh7nlJWWBSPUJ1mYn8RBvB/QImtrM56/Ig4iWbznl+KDmYAspjkoSj/Di9v5Lh5xXP9RWQrpGUPZBEzUnwS2E8c3CWADXG48Zj7gGXVgyIcSSJd58ZsN0EQKufEimESF45weKqUUYxybw01yeKfrOeRuRQvKcQyZykQGKzxi/DiHsY6I9E6s9k9GHr/MIcP/1dHbRUPNNuJHd1oNJlYSAcpqqBMAd/0+8ud93ZQYOg01SUpi+58ugWK3UzBA4iu3Jg926Sl8KpuN5qCkxQL9JVTAN6Ekh0+tvlTDcCBBRlJqr/odJBKzVILBA5bS7YvocTyUVJyf+ES+e26ful4FTYuIexQJbrIVNBwY/GRs/iFvVM03WWSdt4RtrUyHcAq+VWHkY+KWiwUsAr4umOa/6KQ== X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:(21532816269658)(17755550239193); X-Exchange-Antispam-Report-CFA-Test: BCL:0; PCL:0; RULEID:(6060326)(601004)(2401047)(8121501046)(5005006)(3002001)(10201501046)(6061324)(6043046); SRVR:AM5PR0201MB2308; BCL:0; PCL:0; RULEID:; SRVR:AM5PR0201MB2308; X-Forefront-PRVS: 012792EC17 X-Forefront-Antispam-Report: SFV:NSPM; SFS:(10019020)(4630300001)(6069001)(6009001)(7916002)(199003)(189002)(81166006)(81156014)(7416002)(8676002)(42186005)(68736007)(2906002)(50226002)(305945005)(50466002)(110136003)(7846002)(47776003)(7736002)(2950100002)(575784001)(86362001)(5660300001)(66066001)(5003940100001)(6916009)(6666003)(48376002)(76176999)(33646002)(50986999)(189998001)(6116002)(101416001)(3846002)(36756003)(2351001)(106356001)(77096005)(97736004)(105586002)(4326007)(92566002)(74482002)(2004002)(42262002)(309714004); DIR:OUT; SFP:1102; SCL:1; SRVR:AM5PR0201MB2308; H:localhost.localdomain; FPR:; SPF:None; PTR:InfoNoRecords; A:1; MX: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; AM5PR0201MB2308; 23:XLXp2JALvaBY8bEyk8KeYyE5bGGbaPkItB3rpRu?= =?us-ascii?Q?jGOojaouDJjlkuCrl/dB/hc7aCbqGo5fCNGnaWbDEK4SCj9p8xUd83Z7Ki+g?= =?us-ascii?Q?NgDWcXII7ud3ro4Z+QhyQVp7E2os3v9G/IY1dihfFynV3kmh2cZRbXmgca9E?= =?us-ascii?Q?oBWUTd/03Lx0Fy6PNmpdXp2/rBzBLa4+BRS2qXHEMNg/EDCgce7rDsD+F/dj?= =?us-ascii?Q?6W41I/Cth8Wzca249xqpxGOxQlz5JlLAnulswynSczOazQ7FhauDk8sLEZn2?= =?us-ascii?Q?d/tTWG0JGJjKxwEVIMoy6mW4Axb65QMYDGvSp7al0i+QvjyyMYPejuwy281j?= =?us-ascii?Q?z2noA+c6d8F+hHRUKaYs1Tap0LiW5Wvc/XIzkXcsW3myJjwout6LxcyGLYSC?= =?us-ascii?Q?FtczCOaTh52x9SpO03ohsJTbxZhhMhgrMwh25NCpAjQWFFtkSO0AYlcRo4vq?= =?us-ascii?Q?g3TMZ3DfldTtxtA5yLXYUP4ctlX+7JppR5r23sVqauFtdVYYPD6yxR1Oavnl?= =?us-ascii?Q?VcWRoClHoB2mV0Uu6R+8dSAZiSUdzeA3n0F6C8d6HImEvVedbfs3ITt4iTD3?= =?us-ascii?Q?mnQ7xRNfG4Yll5pt71uV5a1U0dpN4xevJk7D2QNpALShDTUdKb9dU2xcu902?= =?us-ascii?Q?hLK7D6ZlvcUboLXXsUUg44D38V7CxCcW3MC/MKT+7AAISHLlriyiHWntkvn4?= =?us-ascii?Q?z7cuNbBK+HwSUuIEP1DMWbLcyN5oBOOtaHV0SKNBqU/IR9bcZCDUVvhKn5td?= =?us-ascii?Q?iBv+Ct0FkhG3TZncl1aG1jtdGqdxnjltcIuWeulycl4KCqJbo4oOHRw9f3CL?= =?us-ascii?Q?nSXu2JTrUMRxPLzrhlJMyrRL/ww+Vw2v2t5XlY8Yt6/k52yR+N8v00Run7JK?= =?us-ascii?Q?gVk+NaGZyXBqdrn9lzBEbPCmsRXXu5l8nJSbFAGIIeolS4jTSt3k65N8h75b?= =?us-ascii?Q?6dUUleL+xeLGOenfDz2KtGZl0njCrYDmKOhRtPHmJaE4ysnp9XeAVsHkwhHJ?= =?us-ascii?Q?6s+DcO675MPkaEwJupRY+Aa5tGbCEyvHoRZ6ltiqQJRKcTrOoS5yqxN9Q5rk?= =?us-ascii?Q?cWTpN66PY1Pqe7PXuLSzMBVR7JifWviJwD/fd1XdGgIeG4aGEW3GKLE5JgWD?= =?us-ascii?Q?6rDfp67sKnfg1r8EXTfx75FB4Yzh34m4Z?= X-Microsoft-Exchange-Diagnostics: 1; AM5PR0201MB2308; 6:DiDkqz08Oo9HHa2cxSKnCraAWRi4t4PiwsaYBHtRhMZzpIolLlOkSm2UsVFVzgiZ6Anneb+SxtLuRDaDvZSJLrOY/4dKVWlLQ1SZXI7nucvVRv9zGSUgdRhxuleagwoW8F9zWQJE7Z4GiUdMPlJibUTQ/wiLY7902rgGNFVhnRgtHhkRKXjZ59vDcbnzS6wmwig98jUFfpdXJuDiLJdfaQgqZTslXYBls4/BDT2eQmOYNqA6hqDK7N/jeWn51g719Hwvs/JjXcMPUDTvrYDaqHnCTPkHoXlvvu283gNMfWtagSXyMP2s+o8m9q8vPURi1lG94bWHJCV17uYZ14Krs8vCrGU0tm58C8sYi9IGdhjmxfQpUV8Re1sQMw8+agMe; 5:GVbhOrIqFBleuubRDEjrNbVgCXb1JpAmwnkah5XoOu8LTUNo3slUFtZnj+Ir5QwRCO7PwZDAfoX+t0sHfzKpbXdVgESigjBLRbTD52xdtRkBCoff1MX+Ubh1u48Ab4ZGSjEDhswbMAuYQ3fOuyWw7g==; 24:uwZrMK6VdG6lM0d/dSiIsJSbIolyVYyjqEgLM+GfjU0n85eWG9dy1ONqqm5Zi2jfnVu0gRYum2ya77qswqrFkpQzAjS6WKiUj1t1Kwnv2ZM= SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-Microsoft-Exchange-Diagnostics: 1; AM5PR0201MB2308; 7:H9EfRdj3bOvGU+K6dDo62BGIH+3SPOjYEjR3OLKrFtzb8ehRvPbFdBJiY5XJto3R90lU1QYRf3M9XthlXhgXvmpNk9t0UM+xfKm+PYVYepHcJRejA2oH8xFsOqAl18t/u5w/N/CHjW1slQmiz7xrK5bNUXbqXZRtcj6ZnwE4o1QHb20CPj9Cep9BGLl7xvHZYvUaRTCcXCNs3b4zXHw3x+m2fqk16uB5Ua9N7RFMj8p8JRfiujpni65prBqvAZ2OHQiMrpdWh95XsT0mBi5d30KpOWHJYR9b0fPMAgbCR220Kq/llJWO1WyrBb8MOeG7lVwTXaMg3rsa0VVAbZ/B8FTiIO09pN4eMABEfz16Kp0= X-OriginatorOrg: axentia.se X-MS-Exchange-CrossTenant-OriginalArrivalTime: 15 Nov 2016 18:38:58.4382 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-Transport-CrossTenantHeadersStamped: AM5PR0201MB2308 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 v4 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 e6bf00e53bf3..59baed3fd0ee 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");