From patchwork Thu Sep 17 04:22:42 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kuninori Morimoto X-Patchwork-Id: 7201831 Return-Path: X-Original-To: patchwork-linux-sh@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 379FEBEEC1 for ; Thu, 17 Sep 2015 04:22:51 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 91C64208F5 for ; Thu, 17 Sep 2015 04:22:49 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 05DC0208F4 for ; Thu, 17 Sep 2015 04:22:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751385AbbIQEWr (ORCPT ); Thu, 17 Sep 2015 00:22:47 -0400 Received: from relmlor2.renesas.com ([210.160.252.172]:13861 "EHLO relmlie1.idc.renesas.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1751375AbbIQEWq (ORCPT ); Thu, 17 Sep 2015 00:22:46 -0400 Received: from unknown (HELO relmlir3.idc.renesas.com) ([10.200.68.153]) by relmlie1.idc.renesas.com with ESMTP; 17 Sep 2015 13:22:45 +0900 Received: from relmlac2.idc.renesas.com (relmlac2.idc.renesas.com [10.200.69.22]) by relmlir3.idc.renesas.com (Postfix) with ESMTP id 2765C411A9; Thu, 17 Sep 2015 13:22:45 +0900 (JST) Received: by relmlac2.idc.renesas.com (Postfix, from userid 0) id 1FB9E2806E; Thu, 17 Sep 2015 13:22:45 +0900 (JST) Received: from relmlac2.idc.renesas.com (localhost [127.0.0.1]) by relmlac2.idc.renesas.com (Postfix) with ESMTP id 16D502806D; Thu, 17 Sep 2015 13:22:45 +0900 (JST) Received: from relmlii1.idc.renesas.com [10.200.68.65] by relmlac2.idc.renesas.com with ESMTP id PAB15582; Thu, 17 Sep 2015 13:22:45 +0900 X-IronPort-AV: E=Sophos;i="5.17,543,1437404400"; d="scan'";a="194624702" Received: from mail-pu1apc01lp0017.outbound.protection.outlook.com (HELO APC01-PU1-obe.outbound.protection.outlook.com) ([65.55.88.17]) by relmlii1.idc.renesas.com with ESMTP/TLS/AES256-SHA; 17 Sep 2015 13:22:44 +0900 Authentication-Results: spf=none (sender IP is ) smtp.mailfrom=kuninori.morimoto.gx@renesas.com; Received: from morimoto-PC.renesas.com (211.11.155.144) by SG2PR06MB0601.apcprd06.prod.outlook.com (10.161.10.151) with Microsoft SMTP Server (TLS) id 15.1.268.17; Thu, 17 Sep 2015 04:22:42 +0000 Message-ID: <87oah17k6j.wl%kuninori.morimoto.gx@renesas.com> From: Kuninori Morimoto Subject: [PATCH 05/20 v2] ASoC: add ak4613 support User-Agent: Wanderlust/2.15.9 Emacs/24.3 Mule/6.0 To: Simon , Magnus , Linux-SH In-Reply-To: <87vbb97kci.wl%kuninori.morimoto.gx@renesas.com> References: <87vbb97kci.wl%kuninori.morimoto.gx@renesas.com> MIME-Version: 1.0 (generated by SEMI-EPG 1.14.7 - "Harue") Date: Thu, 17 Sep 2015 04:22:42 +0000 X-Originating-IP: [211.11.155.144] X-ClientProxiedBy: KAWPR01CA0005.jpnprd01.prod.outlook.com (25.161.24.15) To SG2PR06MB0601.apcprd06.prod.outlook.com (25.161.10.151) X-Microsoft-Exchange-Diagnostics: 1; SG2PR06MB0601; 2:RFOtkoboGMO0CSadnkR2gLoKZla5RKaqKEdKsp8o/rRzFUHT4HxHisqwVXPcXqBHHazItAL0DB7BczlO9mM97xTGiUFIhPidKdcXyVRT1SyrPaOdtEMLTukGv/LseN1dm7GAtDk/5i/NDU4WVQwmP9OATth7c8XiOtWwRFAay40=; 3:ZC/b/iisWqVaZN19RjkC2Q65BxnhpXubuj5MUEboVZV6cq68853eJV8xHv350A+oEvjZ+1TRxF+EF7bW9K5ZKCsU1lUK1FScj3m0eVVEK5Z+UvLmuSpNPu57w2m/p4BXpIjaSTB7uKjzFEfVCDpLqA==; 25:Z5zxqbr2+YrjmpyMBCGkFn8VCbf1qz8DGpkwobCKOMekcgCQXpe8hRtpFoycvp2PaBw2aDaft3Z6pgBlgAQvqb09UtkHz7VGaN5cw7UBIoyCOohQDDCqaBWFD+IO64dweXeaI64E18TrdBzQTB85ohFgFmoROZy0ZVpPS5xJVCAKwZNFhPaQl4XKGDltspMVbDWLqkQKcDGq9gYWp69672WTreulMa7Oy55dHdM6hDqLYHwGRfesagzjkKdvAhUGL4sNvhORQmFnqLYO1zGKwA== X-Microsoft-Antispam: UriScan:;BCL:0;PCL:0;RULEID:;SRVR:SG2PR06MB0601; X-Microsoft-Exchange-Diagnostics: 1; SG2PR06MB0601; 20:jSEOibEM+OaWB+WoVGZ5JjSlQSJHy8ZfGAd9K7KhDAik83rGYEvEhpQNlS9XzUvL6GKiXyvSOkFYwjyMqRSAZaVvqLep0eOq4Up8YXVB4lNSndfF3WrAbC6DSNUlNE7MhYK3oOxTxXI8MD/iKc/W8DNjiqE3UlrCgQ/tO8QTbr+ch2CB+tCAQsjxtfR/aWlfxQ5BPcFMc+dz5dn9HO9fZNk+Bkkurmd7vpDix2mE6TXgYUgk12XGsRRli+XKj5Ht5P0m5jw0i6T22OcuT8RF1Gg3Vhxivaj403u5vJCiQbBPc3mbHVpsFdXN5dSCFLwMBJ6Sl/CSJcVwNpV5vfhLslxZ7S13iMiyNzMh21vZQJ0zx4sV2eRGFBNqM7lveVyRSZ/6U7tE8P6i313g2e+uzhpvO85yfJQ9gQSpar4opzU0va9NtQQBzUfEO77FPyT7sQqT4134PnpOyTfGJsOF0Fe0OHtCrCnOlWRcMC96JnedjvL11NUFlVKwwzYS66UU; 4:hInXd51DKhsjw8OS+1ceZRaKF+J4Aty+aP98uLtIRKYlvG9PCVvhITePDdc9I7WStSTn6NDyE4XJw2k/6tCGphFnmtL9S8AiqUfzcwqAsmUbkTQ8I3vj2nSr6FLANpRSCnXUJ/kowTuqKI4zC9QbE1rdVASAFvOqQj+TW0yFmwREgHXS+TVSFKllxJIR0vRzf+0zanpMJmiv01ua/bEhNCS+AplozUYynIW5p2hHml3Q+A6zvMA/txqgdJtsbsU9VTFXTVm+EETpZYUawDnv4rOxNmwNwq+CfSd2L6x2UYpTf38VqU0a193E8fGzvBNipV7Bkkv3msZPeQu6MOznyg== X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:; X-Exchange-Antispam-Report-CFA-Test: BCL:0; PCL:0; RULEID:(601004)(520078)(8121501046)(5005006)(3002001); SRVR:SG2PR06MB0601; BCL:0; PCL:0; RULEID:; SRVR:SG2PR06MB0601; X-Forefront-PRVS: 07025866F6 X-Forefront-Antispam-Report: SFV:NSPM; SFS:(10019020)(6009001)(189002)(199003)(36756003)(46406003)(76176999)(107886002)(19580405001)(5007970100001)(5001770100001)(92566002)(575784001)(2950100001)(5001860100001)(5001830100001)(122386002)(5004730100002)(40100003)(19580395003)(81156007)(86362001)(97736004)(4001540100001)(54356999)(189998001)(33646002)(77096005)(106356001)(229853001)(83506001)(50466002)(5001960100002)(4001350100001)(87976001)(68736005)(69596002)(47776003)(62966003)(23726002)(42186005)(66066001)(105586002)(64706001)(77156002)(101416001)(46102003)(53416004)(50986999)(2004002); DIR:OUT; SFP:1102; SCL:1; SRVR:SG2PR06MB0601; H:morimoto-PC.renesas.com; FPR:; SPF:None; PTR:InfoNoRecords; MX:1; A:1; LANG:en; X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1; SG2PR06MB0601; 23:AeJ+6+MiQRb0AHEGNTyCNtES0SWbQiemrLoKlj39V?= =?us-ascii?Q?pQiZeOqteQ4vDBXwehvGhuxoKqpHlNGZZIS7uGQ/DMEqrAUoWfBcdZDTjC4h?= =?us-ascii?Q?fbt/uFmSWIDnRlaAZQ+wHY8YV9M8Mst9Lxgpwdyur8cjL5WQWaIkwYWvLFF1?= =?us-ascii?Q?Jn+QPFc9+WEkkXoa6YpCuIob/mP+kxF7pnxFeQEpTmK+v9xiAXYAUV07VFgB?= =?us-ascii?Q?+LX357q5UH7BDNyXVGtfk3tp90WQDgaYAczG5/XN0dqBESD0sMcPAX+wXuF6?= =?us-ascii?Q?VpmqtcztAHmawznqIOfWk2tTNSfz7lYXu/iVYqeubxcN2H567nCu9vLqrSi1?= =?us-ascii?Q?4kXGud5CcooNIdRq/wHBOyGA/Q7R6/n4dMoPE0Fize2xCBHmtS7bUPYXuBaW?= =?us-ascii?Q?h6c8X51Fdp8kpMiNJ6i8SF+ZBdjXMETnYHCP66d1svrVZ60JjRYn1i3yhJCk?= =?us-ascii?Q?dEHsPTvI92MN/rZ2kCjcG79xmcND1nLV4r+oN6vVOsnTNIFozDdteXq1pWAL?= =?us-ascii?Q?H9w0BX4RhT4fIluzQNPWaUME3v21zEfVGicfTEcY8qqARXGdongTdLUekKd3?= =?us-ascii?Q?qfMOROCisqCzo8lO4S5M9bcsruuPhB0fOvBreYgfWl7yggVsrKDu4DUFqdGS?= =?us-ascii?Q?gRsyuEcHKIwgZ5o/XT06soBPTe3o8PNi+mFtknC0fMgEDPuo1iQZe09LCV26?= =?us-ascii?Q?i8Wr4fWuyUDl6ndhMkBuyl9bcrOJp8dxheSFL4BTy9Jzbil8TKmoHzY8/0JB?= =?us-ascii?Q?C0nCx2kW79oTyF0pHlhbyC6IWMhgAoFztmMvrPVi2Q4shHrxgKh4jxsX3Drp?= =?us-ascii?Q?t8knTVvWwoD2BxBgBtK59xclGucSUsYxTljRI4JdQ9Y2W0GbUfmkk72Z51XK?= =?us-ascii?Q?saVC1Y7w3ELAUutmaH+tPQITdpxNtb8nCvNfQflh4sOGVQD7BoDl1mN5mr7T?= =?us-ascii?Q?aXbFMPTUnMg4adX9zKjcfjVg354AujdHV/w5HGRqua6vfFe/p4HMtz6nvLUp?= =?us-ascii?Q?QvLOtbQEZl0LazECbGKhzbXwL+Shk1U2ZrX5Zb5H+A3vybL2Q1LAMk5mLo3c?= =?us-ascii?Q?osZhmEFyl/q2d3kJorW6kFwczDSqlDAqBVbOruvjaoa24ol5Wqbo/n8lev0c?= =?us-ascii?Q?fXgV7SJmUPI9DC8O/pmGfcMM82f9bImFrcS9rC5m26Bl87cgWDQOwOW7s6Nt?= =?us-ascii?Q?3gvliRneuY2xwV+ve+eM888aW1SU3fJ2Y0FFfaeXEWzLhzlB9gts7n/d+ORZ?= =?us-ascii?Q?PqnrwhZg0tsCjihJL5VzMsg2bMPfhZRuog9k1YAOVxfDAqsAilorHOGlcP4u?= =?us-ascii?Q?fHTc7QGOCXZLxIgBhPvDCQ=3D?= X-Microsoft-Exchange-Diagnostics: 1; SG2PR06MB0601; 5:7FIMA/IHaDKG2PxidEzhvR0I1G4LEcGB59HBB4uJXOBvTYRSh+Eqo6QmOGH8okDBLX1JCkHfFv7+UTLYr/qn6L8F3sDqld8AfyG3xChcGvZ7MyhcC2vfoMVU0/MDh8zYSbw8xmuRKLclakbMeBBK7A==; 24:djnJsFzeMBcCxWbopzIM8+uA1BOo6eAtpPfKByS/+CvHIOLuGn979M5+1SeISwO7zXxljpaa36+zp1lsaOBPGD181V31Q+dR6pG9BNFaCHI=; 20:6UV3Q2Z0buNldQEQRYiyrZT7juWtejmPPWaU9WDNhpZOcL/FqYvq5pcUN3Jj29CKOWBAGnAfk1cDezHPYaWZqrkfcLzqhVDO4SB+ZjlIoGJZVFE/e2EuNjwNkGCfo9smJWMQre3TQH4TbYtu2EOChnHWhqJMZxdKB8Pga0o7n4w= SpamDiagnosticOutput: 1:23 SpamDiagnosticMetadata: NSPM X-OriginatorOrg: renesas.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 17 Sep 2015 04:22:42.4390 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-Transport-CrossTenantHeadersStamped: SG2PR06MB0601 Sender: linux-sh-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-sh@vger.kernel.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Kuninori Morimoto Signed-off-by: Kuninori Morimoto --- v1 -> v2 - already posted, not yet merged Documentation/devicetree/bindings/sound/ak4613.txt | 17 + sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/ak4613.c | 469 +++++++++++++++++++++ 4 files changed, 493 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/ak4613.txt create mode 100644 sound/soc/codecs/ak4613.c diff --git a/Documentation/devicetree/bindings/sound/ak4613.txt b/Documentation/devicetree/bindings/sound/ak4613.txt new file mode 100644 index 0000000..15a9195 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/ak4613.txt @@ -0,0 +1,17 @@ +AK4613 I2C transmitter + +This device supports I2C mode only. + +Required properties: + +- compatible : "asahi-kasei,ak4613" +- reg : The chip select number on the I2C bus + +Example: + +&i2c { + ak4613: ak4613@0x10 { + compatible = "asahi-kasei,ak4613"; + reg = <0x10>; + }; +}; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 0c9733e..a92e4d4 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -36,6 +36,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C select SND_SOC_AK4554 + select SND_SOC_AK4613 if I2C select SND_SOC_AK4641 if I2C select SND_SOC_AK4642 if I2C select SND_SOC_AK4671 if I2C @@ -319,6 +320,10 @@ config SND_SOC_AK4535 config SND_SOC_AK4554 tristate "AKM AK4554 CODEC" +config SND_SOC_AK4613 + tristate "AKM AK4613 CODEC" + depends on I2C + config SND_SOC_AK4641 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 4a32077..5b6c8af 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -26,6 +26,7 @@ snd-soc-ads117x-objs := ads117x.o snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o snd-soc-ak4554-objs := ak4554.o +snd-soc-ak4613-objs := ak4613.o snd-soc-ak4641-objs := ak4641.o snd-soc-ak4642-objs := ak4642.o snd-soc-ak4671-objs := ak4671.o @@ -216,6 +217,7 @@ obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_AK4554) += snd-soc-ak4554.o +obj-$(CONFIG_SND_SOC_AK4613) += snd-soc-ak4613.o obj-$(CONFIG_SND_SOC_AK4641) += snd-soc-ak4641.o obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c new file mode 100644 index 0000000..fd96a8f --- /dev/null +++ b/sound/soc/codecs/ak4613.c @@ -0,0 +1,469 @@ +/* + * ak4613.c -- Asahi Kasei ALSA Soc Audio driver + * + * Copyright (C) 2015 Renesas Electronics Corporation + * Kuninori Morimoto + * + * Based on ak4642.c by Kuninori Morimoto + * Based on wm8731.c by Richard Purdie + * Based on ak4535.c by Richard Purdie + * Based on wm8753.c by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PW_MGMT1 0x00 /* Power Management 1 */ +#define PW_MGMT2 0x01 /* Power Management 2 */ +#define PW_MGMT3 0x02 /* Power Management 3 */ +#define CTRL1 0x03 /* Control 1 */ +#define CTRL2 0x04 /* Control 2 */ +#define DEMP1 0x05 /* De-emphasis1 */ +#define DEMP2 0x06 /* De-emphasis2 */ +#define OFD 0x07 /* Overflow Detect */ +#define ZRD 0x08 /* Zero Detect */ +#define ICTRL 0x09 /* Input Control */ +#define OCTRL 0x0a /* Output Control */ +#define LOUT1 0x0b /* LOUT1 Volume Control */ +#define ROUT1 0x0c /* ROUT1 Volume Control */ +#define LOUT2 0x0d /* LOUT2 Volume Control */ +#define ROUT2 0x0e /* ROUT2 Volume Control */ +#define LOUT3 0x0f /* LOUT3 Volume Control */ +#define ROUT3 0x10 /* ROUT3 Volume Control */ +#define LOUT4 0x11 /* LOUT4 Volume Control */ +#define ROUT4 0x12 /* ROUT4 Volume Control */ +#define LOUT5 0x13 /* LOUT5 Volume Control */ +#define ROUT5 0x14 /* ROUT5 Volume Control */ +#define LOUT6 0x15 /* LOUT6 Volume Control */ +#define ROUT6 0x16 /* ROUT6 Volume Control */ + +/* PW_MGMT1 */ +#define RSTN BIT(0) +#define PMDAC BIT(1) +#define PMADC BIT(2) +#define PMVR BIT(3) + +/* PW_MGMT2 */ +#define PMAD_ALL 0x7 + +/* PW_MGMT3 */ +#define PMDA_ALL 0x3f + +/* CTRL1 */ +#define DIF0 BIT(3) +#define DIF1 BIT(4) +#define DIF2 BIT(5) +#define TDM0 BIT(6) +#define TDM1 BIT(7) +#define NO_FMT (0xff) +#define FMT_MASK (0xf8) + +/* CTRL2 */ +#define DFS_NORMAL_SPEED (0 << 2) +#define DFS_DOUBLE_SPEED (1 << 2) +#define DFS_QUAD_SPEED (2 << 2) + +struct ak4613_priv { + struct mutex lock; + + unsigned int fmt; + u8 fmt_ctrl; + int cnt; +}; + +struct ak4613_formats { + unsigned int width; + unsigned int fmt; +}; + +struct ak4613_interface { + struct ak4613_formats capture; + struct ak4613_formats playback; +}; + +static const struct reg_default ak4613_reg[] = { + { 0x0, 0x0f }, { 0x1, 0x07 }, { 0x2, 0x3f }, { 0x3, 0x20 }, + { 0x4, 0x20 }, { 0x5, 0x55 }, { 0x6, 0x05 }, { 0x7, 0x07 }, + { 0x8, 0x0f }, { 0x9, 0x07 }, { 0xa, 0x3f }, { 0xb, 0x00 }, + { 0xc, 0x00 }, { 0xd, 0x00 }, { 0xe, 0x00 }, { 0xf, 0x00 }, + { 0x10, 0x00 }, { 0x11, 0x00 }, { 0x12, 0x00 }, { 0x13, 0x00 }, + { 0x14, 0x00 }, { 0x15, 0x00 }, { 0x16, 0x00 }, +}; + +#define AUDIO_IFACE_IDX_TO_VAL(i) (i << 3) +#define AUDIO_IFACE(b, fmt) { b, SND_SOC_DAIFMT_##fmt } +static const struct ak4613_interface ak4613_iface[] = { + /* capture */ /* playback */ + [0] = { AUDIO_IFACE(24, LEFT_J), AUDIO_IFACE(16, RIGHT_J) }, + [1] = { AUDIO_IFACE(24, LEFT_J), AUDIO_IFACE(20, RIGHT_J) }, + [2] = { AUDIO_IFACE(24, LEFT_J), AUDIO_IFACE(24, RIGHT_J) }, + [3] = { AUDIO_IFACE(24, LEFT_J), AUDIO_IFACE(24, LEFT_J) }, + [4] = { AUDIO_IFACE(24, I2S), AUDIO_IFACE(24, I2S) }, +}; + +static const struct regmap_config ak4613_regmap_cfg = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x16, + .reg_defaults = ak4613_reg, + .num_reg_defaults = ARRAY_SIZE(ak4613_reg), +}; + +static const struct of_device_id ak4613_of_match[] = { + { .compatible = "asahi-kasei,ak4613", .data = &ak4613_regmap_cfg }, + {}, +}; +MODULE_DEVICE_TABLE(of, ak4613_of_match); + +static const struct i2c_device_id ak4613_i2c_id[] = { + { "ak4613", (kernel_ulong_t)&ak4613_regmap_cfg }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak4613_i2c_id); + +static const struct snd_soc_dapm_widget ak4613_dapm_widgets[] = { + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("LOUT3"), + SND_SOC_DAPM_OUTPUT("LOUT4"), + SND_SOC_DAPM_OUTPUT("LOUT5"), + SND_SOC_DAPM_OUTPUT("LOUT6"), + + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("ROUT3"), + SND_SOC_DAPM_OUTPUT("ROUT4"), + SND_SOC_DAPM_OUTPUT("ROUT5"), + SND_SOC_DAPM_OUTPUT("ROUT6"), + + /* Inputs */ + SND_SOC_DAPM_INPUT("LIN1"), + SND_SOC_DAPM_INPUT("LIN2"), + + SND_SOC_DAPM_INPUT("RIN1"), + SND_SOC_DAPM_INPUT("RIN2"), + + /* DAC */ + SND_SOC_DAPM_DAC("DAC1", NULL, PW_MGMT3, 0, 0), + SND_SOC_DAPM_DAC("DAC2", NULL, PW_MGMT3, 1, 0), + SND_SOC_DAPM_DAC("DAC3", NULL, PW_MGMT3, 2, 0), + SND_SOC_DAPM_DAC("DAC4", NULL, PW_MGMT3, 3, 0), + SND_SOC_DAPM_DAC("DAC5", NULL, PW_MGMT3, 4, 0), + SND_SOC_DAPM_DAC("DAC6", NULL, PW_MGMT3, 5, 0), + + /* ADC */ + SND_SOC_DAPM_ADC("ADC1", NULL, PW_MGMT2, 0, 0), + SND_SOC_DAPM_ADC("ADC2", NULL, PW_MGMT2, 1, 0), +}; + +static const struct snd_soc_dapm_route ak4613_intercon[] = { + {"LOUT1", NULL, "DAC1"}, + {"LOUT2", NULL, "DAC2"}, + {"LOUT3", NULL, "DAC3"}, + {"LOUT4", NULL, "DAC4"}, + {"LOUT5", NULL, "DAC5"}, + {"LOUT6", NULL, "DAC6"}, + + {"ROUT1", NULL, "DAC1"}, + {"ROUT2", NULL, "DAC2"}, + {"ROUT3", NULL, "DAC3"}, + {"ROUT4", NULL, "DAC4"}, + {"ROUT5", NULL, "DAC5"}, + {"ROUT6", NULL, "DAC6"}, + + {"DAC1", NULL, "Playback"}, + {"DAC2", NULL, "Playback"}, + {"DAC3", NULL, "Playback"}, + {"DAC4", NULL, "Playback"}, + {"DAC5", NULL, "Playback"}, + {"DAC6", NULL, "Playback"}, + + {"Capture", NULL, "ADC1"}, + {"Capture", NULL, "ADC2"}, + + {"ADC1", NULL, "LIN1"}, + {"ADC2", NULL, "LIN2"}, + + {"ADC1", NULL, "RIN1"}, + {"ADC2", NULL, "RIN2"}, +}; + +static void ak4613_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct ak4613_priv *priv = snd_soc_codec_get_drvdata(codec); + struct device *dev = codec->dev; + + mutex_lock(&priv->lock); + priv->cnt--; + if (priv->cnt < 0) { + dev_err(dev, "unexpected counter error\n"); + priv->cnt = 0; + } + if (!priv->cnt) + priv->fmt_ctrl = NO_FMT; + mutex_unlock(&priv->lock); +} + +static int ak4613_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + struct ak4613_priv *priv = snd_soc_codec_get_drvdata(codec); + + fmt &= SND_SOC_DAIFMT_FORMAT_MASK; + + switch (fmt) { + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_I2S: + priv->fmt = fmt; + + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct ak4613_priv *priv = snd_soc_codec_get_drvdata(codec); + const struct ak4613_formats *fmts; + struct device *dev = codec->dev; + unsigned int width = params_width(params); + unsigned int fmt = priv->fmt; + unsigned int rate; + int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int i, ret; + u8 fmt_ctrl, ctrl2; + + rate = params_rate(params); + switch (rate) { + case 32000: + case 44100: + case 48000: + ctrl2 = DFS_NORMAL_SPEED; + break; + case 88200: + case 96000: + ctrl2 = DFS_DOUBLE_SPEED; + break; + case 176400: + case 192000: + ctrl2 = DFS_QUAD_SPEED; + break; + default: + return -EINVAL; + } + + /* + * FIXME + * + * It doesn't support TDM at this point + */ + fmt_ctrl = NO_FMT; + for (i = 0; i < ARRAY_SIZE(ak4613_iface); i++) { + fmts = (is_play) ? &ak4613_iface[i].playback : + &ak4613_iface[i].capture; + + if (fmts->fmt != fmt) + continue; + + if (fmt == SND_SOC_DAIFMT_RIGHT_J) { + if (fmts->width != width) + continue; + } else { + if (fmts->width < width) + continue; + } + + fmt_ctrl = AUDIO_IFACE_IDX_TO_VAL(i); + break; + } + + ret = -EINVAL; + if (fmt_ctrl == NO_FMT) + goto hw_params_end; + + mutex_lock(&priv->lock); + if ((priv->fmt_ctrl == NO_FMT) || + (priv->fmt_ctrl == fmt_ctrl)) { + priv->fmt_ctrl = fmt_ctrl; + priv->cnt++; + ret = 0; + } + mutex_unlock(&priv->lock); + + if (ret < 0) + goto hw_params_end; + + snd_soc_update_bits(codec, CTRL1, FMT_MASK, fmt_ctrl); + snd_soc_write(codec, CTRL2, ctrl2); + +hw_params_end: + if (ret < 0) + dev_warn(dev, "unsupported data width/format combination\n"); + + return ret; +} + +static int ak4613_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u8 mgmt1 = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + mgmt1 |= RSTN; + /* fall through */ + case SND_SOC_BIAS_PREPARE: + mgmt1 |= PMADC | PMDAC; + /* fall through */ + case SND_SOC_BIAS_STANDBY: + mgmt1 |= PMVR; + /* fall through */ + case SND_SOC_BIAS_OFF: + default: + break; + } + + snd_soc_write(codec, PW_MGMT1, mgmt1); + + return 0; +} + +static const struct snd_soc_dai_ops ak4613_dai_ops = { + .shutdown = ak4613_dai_shutdown, + .set_fmt = ak4613_dai_set_fmt, + .hw_params = ak4613_dai_hw_params, +}; + +#define AK4613_PCM_RATE (SNDRV_PCM_RATE_32000 |\ + SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_64000 |\ + SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000 |\ + SNDRV_PCM_RATE_176400 |\ + SNDRV_PCM_RATE_192000) +#define AK4613_PCM_FMTBIT (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_driver ak4613_dai = { + .name = "ak4613-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AK4613_PCM_RATE, + .formats = AK4613_PCM_FMTBIT, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AK4613_PCM_RATE, + .formats = AK4613_PCM_FMTBIT, + }, + .ops = &ak4613_dai_ops, + .symmetric_rates = 1, +}; + +static int ak4613_resume(struct snd_soc_codec *codec) +{ + struct regmap *regmap = dev_get_regmap(codec->dev, NULL); + + regcache_mark_dirty(regmap); + return regcache_sync(regmap); +} + +static struct snd_soc_codec_driver soc_codec_dev_ak4613 = { + .resume = ak4613_resume, + .set_bias_level = ak4613_set_bias_level, + .dapm_widgets = ak4613_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak4613_dapm_widgets), + .dapm_routes = ak4613_intercon, + .num_dapm_routes = ARRAY_SIZE(ak4613_intercon), +}; + +static int ak4613_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct device_node *np = dev->of_node; + const struct regmap_config *regmap_cfg; + struct regmap *regmap; + struct ak4613_priv *priv; + + regmap_cfg = NULL; + if (np) { + const struct of_device_id *of_id; + + of_id = of_match_device(ak4613_of_match, dev); + if (of_id) + regmap_cfg = of_id->data; + } else { + regmap_cfg = (const struct regmap_config *)id->driver_data; + } + + if (!regmap_cfg) + return -EINVAL; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->fmt_ctrl = NO_FMT; + priv->cnt = 0; + + mutex_init(&priv->lock); + + i2c_set_clientdata(i2c, priv); + + regmap = devm_regmap_init_i2c(i2c, regmap_cfg); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return snd_soc_register_codec(dev, &soc_codec_dev_ak4613, + &ak4613_dai, 1); +} + +static int ak4613_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static struct i2c_driver ak4613_i2c_driver = { + .driver = { + .name = "ak4613-codec", + .owner = THIS_MODULE, + .of_match_table = ak4613_of_match, + }, + .probe = ak4613_i2c_probe, + .remove = ak4613_i2c_remove, + .id_table = ak4613_i2c_id, +}; + +module_i2c_driver(ak4613_i2c_driver); + +MODULE_DESCRIPTION("Soc AK4613 driver"); +MODULE_AUTHOR("Kuninori Morimoto "); +MODULE_LICENSE("GPL v2");