From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754299AbcK1Csh (ORCPT ); Sun, 27 Nov 2016 21:48:37 -0500 Received: from relmlor3.renesas.com ([210.160.252.173]:37623 "EHLO relmlie2.idc.renesas.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1753647AbcK1Cs3 (ORCPT ); Sun, 27 Nov 2016 21:48:29 -0500 X-IronPort-AV: E=Sophos;i="5.31,561,1473087600"; d="scan'";a="226430778" Authentication-Results: spf=none (sender IP is ) smtp.mailfrom=kuninori.morimoto.gx@renesas.com; Message-ID: <8737icwcbd.wl%kuninori.morimoto.gx@renesas.com> From: Kuninori Morimoto Subject: [PATCH v5 12/14] ASoC: add simple-graph-card support User-Agent: Wanderlust/2.15.9 Emacs/24.3 Mule/6.0 To: Rob Herring , Mark Brown CC: Linux-ALSA , Liam Girdwood , Simon , Laurent , Guennadi , Grant Likely , Frank Rowand , Linux-DT , Linux-Kernel In-Reply-To: <87k2bowckx.wl%kuninori.morimoto.gx@renesas.com> References: <87k2bowckx.wl%kuninori.morimoto.gx@renesas.com> MIME-Version: 1.0 (generated by SEMI-EPG 1.14.7 - "Harue") Content-Type: text/plain; charset="US-ASCII" Date: Mon, 28 Nov 2016 02:48:17 +0000 X-Originating-IP: [211.11.155.144] X-ClientProxiedBy: TYXPR01CA0036.jpnprd01.prod.outlook.com (10.168.40.46) To PS1PR0601MB1451.apcprd06.prod.outlook.com (10.165.210.155) X-MS-Office365-Filtering-Correlation-Id: 0ecf174a-91f6-4ffd-d109-08d41739023d X-Microsoft-Antispam: UriScan:;BCL:0;PCL:0;RULEID:(22001);SRVR:PS1PR0601MB1451; X-Microsoft-Exchange-Diagnostics: 1;PS1PR0601MB1451;3:XXs1ZTOY9Ix2DUXPMDXsaDNLGmKhIhKJfYCfzYQR9N1/9yFDr4ouJ82BSUMYB1p59rAIB7McAA4wi4PA6sk7TKuvpgUibDSuLwEjawE0KiykGz0fhgV6XIPiY72bABT+teVuaTyqc303TPHtNdHcARANofzH5lmQgMB0MXn/RgU9QOHRpi2/AYgbFe2xuTFincb81KmHRP7M234FlebqiZqPJLrM3iWaDTLGnEdPgbAEMFSDfvglDkTHEgozepfr+mdJAGU4rpvKHqvvIxPw7w==;25:D2tWXwBIZtL51W0MishLnVrQH2DHGd2fKyc48m9h4b5sNqm4VBV3PFHhFqjOyCptAEUrw+q5Iefg10sxSzCujZscwDG5ZgXHaCIctpKv5aaNAWL1aUrIgX+PXIjemo/3eXY6AqaGpMLOvL0/1mWgR+YBoY/a2N/iYtX2yMnM6iHSGbKe1R+1eaKXI7wcyLTMCKQ/Aze2sQ6Uy4q4QmthfSZZyShEt9xdmmpPX2Y6qiYiwN6Yh/NgzPRrlnH6cTplc1i9mAl58GXKQbxcBie6K6dlmjP+/pjxqe0wFsv977iAx2OSQIreJIfU5tzaS5klJ4LefxdCst4U7nbHRiQUF9RxtjT4tcz3KDqQPG9VF3XLXCeb1dYp0UAWZs6V9vEy817suOB5mWaSC4K/ud7JuTwToMQTkcWpTkg8F/YmnuZ9CoCnDImphrRRwcYJzqKCn16lZvYFHcriUADuQK4TAA== X-Microsoft-Exchange-Diagnostics: 1;PS1PR0601MB1451;31:A70YUn6SB1v41CgH/K8eFPgvZ+RsfbAfSNZWTQQafnfdJlTSaFP/8ughyUVY8245LIZdrI5kuQx16CRA67Zh4/XeazUQqVXwSAmaYu+v1cOVxm7BNwMbLY46tKK3nXLb+k0hXCsCvEV5qne2UJ80adVK/mTwmz7wgm88S6TdDxzEwoPJMbSN3wtwR+AReO2jOW+xFvq+kEYOZV2hzcQ5RWL74Wer4ZwYq6nTQl7q5oWhlTXa34cYhhln/2cU96wcQgc9Y3BhT63VrWL0pmrQLEyTy0pyt9ZrUeAkONYDNyk=;20:DCQ4VgA0okQqqcKmtv5eW+tqT0EKiGQA6PBzYmutPOPY2dsykajotSktbeMaLK2/VzKI2sbo0eRIjXsfoFGTChv085levd2h9gRUKD9EswKCskTM/ZDqaX/hqUewZYtcjsb5TUxpHlC2eZHTnopNj9dpTTuBHsGUXQauatPoMRZr2jJem4bAsBYWfn8Lpm5/5zyGwc6ZZDIkyrXZfhIE7C27vcwHQZaGWNNw+vgtCrYYxXoO9o/4XMSFnojbGMq0ymgX+q+cly7wwDPpShphlHy8iUie/bq+5xbbCL6M8aq2KBTF0sofp033R/oZpC7HhZveMg2SBzUswr3h9XoBrPBV8tFTMQHjL4bNybX3fta/GuhaQtFfD9IjJbL0hLUxs+icAOnT9m78NFFI616ZlycankFC69C0f92o2hljA60vRYd0N/31HBOiMtxud1H1vLele94KOQq8XEPNv+ORtCasa4OPXusxkudJ3axfTCuqqlfdgji5FdzTuSiviDxh X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:; X-Exchange-Antispam-Report-CFA-Test: BCL:0;PCL:0;RULEID:(6060326)(6040361)(6045199)(601004)(2401047)(5005006)(8121501046)(10201501046)(3002001)(6055026)(6061324)(6041248)(20161123555025)(20161123562025)(20161123564025)(20161123560025)(20161123558021)(6072148)(6042181);SRVR:PS1PR0601MB1451;BCL:0;PCL:0;RULEID:;SRVR:PS1PR0601MB1451; X-Microsoft-Exchange-Diagnostics: 1;PS1PR0601MB1451;4:STUHmfmFY32o/0TlSNyKPXKGWG8FKJKDhTiCHqhFlq67s6KY3ODFVaWxs8Xc1X4hfhG5nigwgOxWwHnVW0XSTMUDzF1+QqgZStsHKxH7cpTVUtBylVcm3A9dxGgr9osGj4xayBqDsMOrWadlFVM2v5Nuu2al4FJh9MhMEsKNYeWlo6NrY7lYhOCFQjUp3HCqVXMAwX/kjX0k2H8GO3NUvAOYAzJQ33LP2KLVluak+JvZrWh2s7f6QDhjQA+2qWx7obCI0T9dM7suJjrKaoFRaoCPEK6MeCJ713jQV/5JFDJq83wvL8Tq7eRH+2BuL9YxhQvGsEMz83EVmhPYBTzY9jbzgowkJl+Iip5m/rywVSWyYQ6+QZst06xol3zI0/ebRMuY/50DAzzO5phiFRbpzsOPYVW1q7ChIWiu9BWmebDV98p+kSjrR/bP/R/Tvf416RL4jkE3c4vHTJOd3Id+Qdz4sMStW7q16xfAnAB16toRFoTr5qEmljGwphctVi17MTUNuAfSgMohQIRcSMNP/yNfyIr2CIhaPMKtkPMncFpeM7jlj71CsqWe9FczIbG+Cxso96zl+iMG5//N6ZkVh6/MSI31NJrHeiz7zFviO9qRkPWR3kmSogNsyfIQy4JYj/r9t9OiAVloNYep5BNTfnsb461LIyzo3nEo0j9/aM9JNPjbegEcKr3S8BnWSDbZRQ7i/I3G8yyCow3zXa40Og== X-Forefront-PRVS: 01401330D1 X-Forefront-Antispam-Report: SFV:NSPM;SFS:(10019020)(4630300001)(6009001)(7916002)(199003)(189002)(4326007)(2950100002)(7416002)(68736007)(7846002)(50466002)(2906002)(86362001)(7736002)(305945005)(5660300001)(8666005)(69596002)(97736004)(5001770100001)(4001350100001)(76176999)(83506001)(47776003)(101416001)(23726003)(106356001)(66066001)(3846002)(105586002)(6116002)(50986999)(54356999)(92566002)(42186005)(53416004)(189998001)(36756003)(38730400001)(8676002)(230783001)(81166006)(81156014)(33646002)(46406003)(6486002)(39060400001)(733004)(7059030)(2004002)(21314002)(16060500001);DIR:OUT;SFP:1102;SCL:1;SRVR:PS1PR0601MB1451;H:morimoto-PC.renesas.com;FPR:;SPF:None;PTR:InfoNoRecords;A:1;MX:1;LANG:en; X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1;PS1PR0601MB1451;23:/9DCHCl2rKJWq+jDsxJSXI4zat/6voZz8yCHC10?= =?us-ascii?Q?pJ17BM+5CiF7zOSyY6dnG2NIXzxjgKddhAqRhGUd2BwQ0afCF9kW3lYO/+Jc?= =?us-ascii?Q?fTi9P5ol1alN0QBzDCNW8xTur2kZx1eAOyJCOrzZ8sWu1sPRqlLGC+eRIqbX?= =?us-ascii?Q?HNaSnnXDSBvlgmxJc5Q++0SWD0vrRWm10zfgFWhxU1rCtn7PrXME5Zee5k+q?= =?us-ascii?Q?Q08eVIWrGYpX/Tyh6v3IV3kKBGPm9rUXDifNqbC7uZ/zuDLOGhufv6z61oKu?= =?us-ascii?Q?+RHM8a91ZjXhsuUo+fTNzXzQb8ox0jmwe7SB2+QEbMfpJJYsLVZeUvdfTDv8?= =?us-ascii?Q?nif6LPyiKx2cIM+3hEtyTrcOFps8zZFBbYxKOCEykOHDQOB4Z0ACMcUWoo+T?= =?us-ascii?Q?Q+qAXHOfo0+HOHjHVSSI9dYDZ9snqgvbiIgfFZQNM0Xu92Sx6mG/Wl6jYUo7?= =?us-ascii?Q?CLd9eim0LyqKF0PiWFmZ+ZDJQKS8I1ef3b6B9MPmzUKG1AkY30In+C1UDeJ0?= =?us-ascii?Q?M8SYuhoNST1Con1mTkf4Zbj2ZYf1AnkY2+w4dHN+Yqefz7rgMeSOAOI7ySne?= =?us-ascii?Q?tmCC41FCbBeneBo4HUJKwwpqWfNyhbHmxVA9qy7hGpFkGT9X/JC1AezHiIfu?= =?us-ascii?Q?JkomYAVsLqlVYKGXqcED03QRV9sWzVXYJfJ+5ZTVXsvKqT79qZ/WtgpwhCBe?= =?us-ascii?Q?7J50dXzp49Te3Djqy7YBcGTG1oKoR9QCVXJRv1wfRvMzOfyXzLHZgp6CekLN?= =?us-ascii?Q?vZ4JWXdQc8m5fKsH14lgoAXuBMVXvjvml0lOEy4poa3gZEQfdL55Q2PDqC3P?= =?us-ascii?Q?YjDd0/7fnZTL+1KIyMrM+7ta8/soooEMY1jSrj3hQZVqJ94foIU1E+6qwDV+?= =?us-ascii?Q?0Lb9wP0E1ClCNzrxiW7bOfRbf/mwAHcCpGYrOsrALUO1ec0PLilPdZbcgLWy?= =?us-ascii?Q?O/VVe1VxFR8Rkxa68+O9n/bLjdfxakZfW+6RjHLjAipYNQ9q4OLOBdIGRQVK?= =?us-ascii?Q?fmtpNu50d1lca0q/UWfPjVvZUTIhjtmumraxX0pG+POmjjU9ndc1zr6iM5eU?= =?us-ascii?Q?SFslBnxu6Ph8sXlUi+sLB1+3rM09VlYIpzL26ifc+voBFXEWqSoPidPHXtfd?= =?us-ascii?Q?ZyCiGecdgm8fzVzx/EP0gwsy0NoX4eSVcxozR0Sk1hfUPC03NpXsz+3RRhfF?= =?us-ascii?Q?36wNN/PfhTaT4hwQ/76aGRQaCeI25SlGDkCHislCt6ym/xYt0co/Eql30Hoi?= =?us-ascii?Q?z7SXP/wDKz0DEtGR03nE=3D?= X-Microsoft-Exchange-Diagnostics: 1;PS1PR0601MB1451;6:lZrtbk/eFbhP1rRbxLLO2vIAUcdeDUg5LY05sJrwok6IA9oPbdCUfemggNW6AeLyYMBYTILE6jYS/8x2CHy76AQHPjRFcUGQs4f4WGZZ+9SpLy4Nziit2sjqJgzPz3FHjbYZW6DZupIJM134l/uFNNboS6IPpCmrUD0LOzfsq/RUDs3Rihen892UMV9LfTiDAEvmJAkf7fubT9sJZg0SbwEfRH9M7mCxsHa2fpG+rB5MQWI7ZG8KJkOMy5s0DMMv+xF0OnpECFb/v6QmZOA8JCYszE+UrTPcGhCkMRbq6rk0qY6BKEL881pqIfs1yFTNUIANOwFI/l/gfBK/zGKy0RLWFERBxKgk23QTpSSltjJBIai5NdUbNG2FaMyQUDRWKv9oD+HmzC1TSFhOLTD4ByM+vsdGAffNyMWuR3vch5Lvd540RuwCur+Brq7oAFrxLN+Leb17uF1F8IU9RIOvpgoa63o30SCcpJy42Sp3qJcpVYCI7we9ZIF1rQHQYDEK;5:/PP1HwDUBzyp5/qX2vZZhBm31YCe+xsMxghNp2ek3zILxTtmSpiCN89j/leVdEPqh5pTNcLDQzjC8W2pQ0nTPLDbV3f/He8qbBWUPgrj9qd/rCE64Jbsm+i8bPVydxmS/CxHLUounqH0iwrSpjp9Mw==;24:cVWl7vJRAb+IlvYJmc2DGB0VuurIctAVKmS9QIiuRp3xQ5Lwv/KdduA1OH7hdBI79A3WalaACU+0w1pzmVgijesOqJedLyneWuIUy7os+i8= SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-Microsoft-Exchange-Diagnostics: 1;PS1PR0601MB1451;7:fu+SzhzHEiqI83zTVTN6HFU+5X9XUt1moqGFa+eDcb8f+ljv9VjKtht5Kr4XbcYeki6EkB21+ycCjgjd/+gnL4sxKjh/JpieNMyDx6g2dehjm+0DE7C4fajUdt1cYRFZHN/d6eFcHG44UZp10IO1kB5HgE7UF41M80oDlBKMYOiGnznAge7bDhP3Md5qXVK1WaoJaouE5dUgwkDc+bWfc9erJjFt5hy/Qs81AURiFC5MR2ZS4GiRxUUJIG8gmwyTOJL3TDgwOEXC8Sq2NLPZTuW9EldK9ziT3yAUzAZrfd7zGzm7+2GTKxy+wiJc57SOmAYbaQkQmvmav70zDl4KkBDJoKSvun8MFVuSQ7xhHChJZ4MH8D/BRijBdZ/Zh/2fktViR3A/i97WewBeIb3HubcotB3yATVe6WG1uheRNSspZH7xLYQ6HiKbptY5KClEiuDS7885TDLSw/jEZHqmbA==;20:T0v/hnr9gMCMfRXU1PIDk18HKlI0WSDv1QXhfCld05vsU8Se9/L92MaB67f34jXIH3xkS9fyWcTif8csvBvm2oXU04jSucTIHZXvkERXJX6h4yCx+21in7fQc0GIqu8LR/Btng52GtvfxiQ0NwYEiCYffx2eISR59gnynCDZTFg= X-OriginatorOrg: renesas.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 28 Nov 2016 02:48:17.7556 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-Transport-CrossTenantHeadersStamped: PS1PR0601MB1451 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Kuninori Morimoto graph base DT binding are used on V4L2, and ALSA SoC is using different style of DT. In case of simple case, ALSA SoC supports simple-card driver. In the future, V4L2 / ALSA will support HDMI, and then, DT bindings between V4L2 / ALSA should be merged somehow. This patch adds graph base DT binding with simple-card style Signed-off-by: Kuninori Morimoto --- sound/soc/generic/Kconfig | 7 + sound/soc/generic/Makefile | 2 + sound/soc/generic/simple-graph-card.c | 461 ++++++++++++++++++++++++++++++++++ 3 files changed, 470 insertions(+) create mode 100644 sound/soc/generic/simple-graph-card.c diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig index d023959..efefabd 100644 --- a/sound/soc/generic/Kconfig +++ b/sound/soc/generic/Kconfig @@ -14,3 +14,10 @@ config SND_SIMPLE_SCU_CARD help This option enables generic simple SCU sound card support. It supports DPCM of multi CPU single Codec system. + +config SND_SIMPLE_GRAPH_CARD + tristate "ASoC Simple Graph sound card support" + depends on OF + select SND_SIMPLE_CARD_UTILS + help + This option enables generic simple Graph sound card support diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile index ee750f3..94eb6f1 100644 --- a/sound/soc/generic/Makefile +++ b/sound/soc/generic/Makefile @@ -1,7 +1,9 @@ snd-soc-simple-card-utils-objs := simple-card-utils.o snd-soc-simple-card-objs := simple-card.o snd-soc-simple-scu-card-objs := simple-scu-card.o +snd-soc-simple-graph-card-objs := simple-graph-card.o obj-$(CONFIG_SND_SIMPLE_CARD_UTILS) += snd-soc-simple-card-utils.o obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o obj-$(CONFIG_SND_SIMPLE_SCU_CARD) += snd-soc-simple-scu-card.o +obj-$(CONFIG_SND_SIMPLE_GRAPH_CARD) += snd-soc-simple-graph-card.o diff --git a/sound/soc/generic/simple-graph-card.c b/sound/soc/generic/simple-graph-card.c new file mode 100644 index 0000000..fd7cddb --- /dev/null +++ b/sound/soc/generic/simple-graph-card.c @@ -0,0 +1,461 @@ +/* + * ASoC simple graph sound card support + * + * Copyright (C) 2016 Renesas Solutions Corp. + * Kuninori Morimoto + * + * based on ${LINUX}/sound/soc/generic/simple-card.c + * + * 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 +#include +#include +#include +#include + +struct asoc_simple_jack { + struct snd_soc_jack jack; + struct snd_soc_jack_pin pin; + struct snd_soc_jack_gpio gpio; +}; + +struct simple_card_data { + struct snd_soc_card snd_card; + struct simple_dai_props { + struct asoc_simple_dai cpu_dai; + struct asoc_simple_dai codec_dai; + unsigned int mclk_fs; + } *dai_props; + struct asoc_simple_jack hp_jack; + struct asoc_simple_jack mic_jack; + struct snd_soc_dai_link *dai_link; + unsigned int mclk_fs; +}; + +#define simple_priv_to_dev(priv) ((priv)->snd_card.dev) +#define simple_priv_to_link(priv, i) ((priv)->snd_card.dai_link + (i)) +#define simple_priv_to_props(priv, i) ((priv)->dai_props + (i)) + +#define PREFIX "simple-audio-card," + +#define asoc_simple_card_init_hp(card, node, sjack, prefix) \ + asoc_simple_card_init_jack(card, node, sjack, 1, prefix) +#define asoc_simple_card_init_mic(card, node, sjack, prefix) \ + asoc_simple_card_init_jack(card, node, sjack, 0, prefix) +static int asoc_simple_card_init_jack(struct snd_soc_card *card, + struct device_node *node, + struct asoc_simple_jack *sjack, + int is_hp, char *prefix) +{ + enum of_gpio_flags flags; + char prop[128]; + char *pin_name; + char *gpio_name; + int mask; + int det; + + sjack->gpio.gpio = -ENOENT; + + if (is_hp) { + snprintf(prop, sizeof(prop), "%shp-det-gpio", prefix); + pin_name = "Headphones"; + gpio_name = "Headphone detection"; + mask = SND_JACK_HEADPHONE; + } else { + snprintf(prop, sizeof(prop), "%smic-det-gpio", prefix); + pin_name = "Mic Jack"; + gpio_name = "Mic detection"; + mask = SND_JACK_MICROPHONE; + } + + det = of_get_named_gpio_flags(node, prop, 0, &flags); + if (det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (gpio_is_valid(det)) { + sjack->pin.pin = pin_name; + sjack->pin.mask = mask; + + sjack->gpio.name = gpio_name; + sjack->gpio.report = mask; + sjack->gpio.gpio = det; + sjack->gpio.invert = !!(flags & OF_GPIO_ACTIVE_LOW); + sjack->gpio.debounce_time = 150; + + snd_soc_card_jack_new(card, pin_name, mask, + &sjack->jack, + &sjack->pin, 1); + + snd_soc_jack_add_gpios(&sjack->jack, 1, + &sjack->gpio); + } + + return 0; +} + +static void asoc_simple_card_remove_jack(struct asoc_simple_jack *sjack) +{ + if (gpio_is_valid(sjack->gpio.gpio)) + snd_soc_jack_free_gpios(&sjack->jack, 1, &sjack->gpio); +} + +static int asoc_simple_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); + int ret; + + ret = clk_prepare_enable(dai_props->cpu_dai.clk); + if (ret) + return ret; + + ret = clk_prepare_enable(dai_props->codec_dai.clk); + if (ret) + clk_disable_unprepare(dai_props->cpu_dai.clk); + + return ret; +} + +static void asoc_simple_card_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); + + clk_disable_unprepare(dai_props->cpu_dai.clk); + + clk_disable_unprepare(dai_props->codec_dai.clk); +} + +static int asoc_simple_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); + unsigned int mclk, mclk_fs = 0; + int ret = 0; + + if (priv->mclk_fs) + mclk_fs = priv->mclk_fs; + else if (dai_props->mclk_fs) + mclk_fs = dai_props->mclk_fs; + + if (mclk_fs) { + mclk = params_rate(params) * mclk_fs; + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) + goto err; + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + if (ret && ret != -ENOTSUPP) + goto err; + } + return 0; +err: + return ret; +} + +static struct snd_soc_ops asoc_simple_card_ops = { + .startup = asoc_simple_card_startup, + .shutdown = asoc_simple_card_shutdown, + .hw_params = asoc_simple_card_hw_params, +}; + +static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct device *dev = simple_priv_to_dev(priv); + struct device *cpu_dev = dev->parent; + struct snd_soc_dai *codec = rtd->codec_dai; + struct snd_soc_dai *cpu = rtd->cpu_dai; + struct snd_soc_card *card = rtd->card; + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); + struct device_node *cpu_port = of_graph_get_top_port(cpu_dev); + int ret; + + ret = asoc_simple_card_init_dai(codec, &dai_props->codec_dai); + if (ret < 0) + return ret; + + ret = asoc_simple_card_init_dai(cpu, &dai_props->cpu_dai); + if (ret < 0) + return ret; + + ret = asoc_simple_card_init_hp(card, cpu_port, &priv->hp_jack, PREFIX); + if (ret < 0) + return ret; + + ret = asoc_simple_card_init_mic(card, cpu_port, &priv->hp_jack, PREFIX); + if (ret < 0) + return ret; + + return 0; +} + +static int asoc_simple_card_dai_link_of(struct device_node *cpu_ep, + struct simple_card_data *priv, + int idx) +{ + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, idx); + struct asoc_simple_dai *cpu_dai = &dai_props->cpu_dai; + struct asoc_simple_dai *codec_dai = &dai_props->codec_dai; + struct device_node *cpu_port; + struct device_node *cpu_remote_ep; + struct device_node *codec_ep; + int ret; + + codec_ep = of_graph_get_remote_endpoint(cpu_ep); + cpu_remote_ep = of_graph_get_remote_endpoint(codec_ep); + + if (cpu_ep != cpu_remote_ep) { + dev_err(dev, "endpoint parse error\n"); + ret = -EINVAL; + goto dai_link_of_err; + } + + cpu_port = cpu_ep->parent; + + ret = asoc_simple_card_parse_daifmt(dev, cpu_port, codec_ep, + PREFIX, &dai_link->dai_fmt); + if (ret < 0) + goto dai_link_of_err; + + of_property_read_u32(cpu_port, "mclk-fs", &dai_props->mclk_fs); + + ret = asoc_simple_card_parse_graph_cpu(cpu_ep, dai_link); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_parse_graph_codec(codec_ep, dai_link); + if (ret < 0) + goto dai_link_of_err; + + ret = snd_soc_of_parse_tdm_slot(cpu_ep, + &cpu_dai->tx_slot_mask, + &cpu_dai->rx_slot_mask, + &cpu_dai->slots, + &cpu_dai->slot_width); + if (ret < 0) + goto dai_link_of_err; + + ret = snd_soc_of_parse_tdm_slot(codec_ep, + &codec_dai->tx_slot_mask, + &codec_dai->rx_slot_mask, + &codec_dai->slots, + &codec_dai->slot_width); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_parse_clk_cpu(cpu_ep, dai_link, cpu_dai); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_parse_clk_codec(codec_ep, dai_link, codec_dai); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_canonicalize_dailink(dai_link); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "%s-%s", + dai_link->cpu_dai_name, + dai_link->codec_dai_name); + if (ret < 0) + goto dai_link_of_err; + + dai_link->ops = &asoc_simple_card_ops; + dai_link->init = asoc_simple_card_dai_init; + + dev_dbg(dev, "\tname : %s\n", dai_link->stream_name); + dev_dbg(dev, "\tformat : %04x\n", dai_link->dai_fmt); + dev_dbg(dev, "\tcpu : %s / %d\n", + dai_link->cpu_dai_name, + dai_props->cpu_dai.sysclk); + dev_dbg(dev, "\tcodec : %s / %d\n", + dai_link->codec_dai_name, + dai_props->codec_dai.sysclk); + + asoc_simple_card_canonicalize_cpu(dai_link, + priv->snd_card.num_links == 1); + +dai_link_of_err: + of_node_put(codec_ep); + of_node_put(cpu_remote_ep); + + return ret; +} + +static int asoc_simple_card_parse_of(struct device_node *node, + struct simple_card_data *priv) +{ + struct device *dev = simple_priv_to_dev(priv); + struct device *cpu_dev = dev->parent; + struct device_node *top_port = of_graph_get_top_port(cpu_dev); + struct snd_soc_card *card = &priv->snd_card; + struct device_node *port, *ep; + int i = 0, ret; + + if (!node) + return -EINVAL; + + for_each_of_port(node, port) { + if (!of_graph_port_type_is_sound(port)) + continue; + + /* The off-codec widgets */ + if (of_property_read_bool(port, PREFIX "widgets")) { + ret = snd_soc_of_parse_audio_simple_widgets_from_node( + &priv->snd_card, + port, PREFIX "widgets"); + if (ret) + return ret; + } + + /* DAPM routes */ + if (of_property_read_bool(port, PREFIX "routing")) { + ret = snd_soc_of_parse_audio_routing_from_node( + &priv->snd_card, + port, PREFIX "routing"); + if (ret) + return ret; + } + + /* Factor to mclk, used in hw_params() */ + of_property_read_u32(port, PREFIX "mclk-fs", &priv->mclk_fs); + + for_each_of_endpoint_in_port(port, ep) { + ret = asoc_simple_card_dai_link_of(ep, priv, i); + if (ret < 0) { + of_node_put(ep); + return ret; + } + i++; + } + } + + ret = asoc_simple_card_parse_card_name(card, top_port, PREFIX); + if (ret) + return ret; + + return 0; +} + +static int asoc_simple_card_probe(struct platform_device *pdev) +{ + struct simple_card_data *priv; + struct snd_soc_dai_link *dai_link; + struct simple_dai_props *dai_props; + struct device *dev = &pdev->dev; + struct device *cpu_dev = pdev->dev.parent; + struct device_node *cpu_node = cpu_dev->of_node; + int num, ret; + + /* Allocate the private data and the DAI link array */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + num = of_graph_get_sound_endpoint_count(cpu_node); + + dai_props = devm_kzalloc(dev, sizeof(*dai_props) * num, GFP_KERNEL); + dai_link = devm_kzalloc(dev, sizeof(*dai_link) * num, GFP_KERNEL); + if (!dai_props || !dai_link) + return -ENOMEM; + + priv->dai_props = dai_props; + priv->dai_link = dai_link; + + /* Init snd_soc_card */ + priv->snd_card.owner = THIS_MODULE; + priv->snd_card.dev = dev; + priv->snd_card.dai_link = priv->dai_link; + priv->snd_card.num_links = num; + + ret = asoc_simple_card_parse_of(cpu_node, priv); + if (ret < 0) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "parse error %d\n", ret); + goto err; + } + + /* + * FIXME + * + * This driver is assuming that it will be called from + * asoc_simple_card_try_to_probe_graph_card() which + * is using platform_device_register_full(). + * This means it is not came from DT. But this driver itself + * will be used as part of ALSA SoC (= sound card). + * Because of these background, it might fail in + * snd_pcm_lib_malloc_pages() on .hw_params. + * Because, noone cares its dma_ops, and result of get_dma_ops() + * is based on its architecture. + * So, it should call arch_setup_dma_ops() from somewhere, + * otherwise, for example, ARM is no problem, but ARM64 will be fail. + * But, of_platform_device_xxx() are not good solution today. + * This driver calls it by itself here. Please fixme + * see also + * linux/sound/soc/generic/simple-card-utils.c :: + * asoc_simple_card_try_to_probe_graph_card() + */ + of_dma_configure(dev, dev->of_node); + + snd_soc_card_set_drvdata(&priv->snd_card, priv); + + ret = devm_snd_soc_register_card(dev, &priv->snd_card); + if (ret >= 0) + return ret; +err: + asoc_simple_card_clean_reference(&priv->snd_card); + + return ret; +} + +static int asoc_simple_card_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct simple_card_data *priv = snd_soc_card_get_drvdata(card); + + asoc_simple_card_remove_jack(&priv->hp_jack); + asoc_simple_card_remove_jack(&priv->mic_jack); + + return asoc_simple_card_clean_reference(&priv->snd_card); +} + +static struct platform_driver asoc_simple_card = { + .driver = { + .name = "asoc-simple-graph-card", + }, + .probe = asoc_simple_card_probe, + .remove = asoc_simple_card_remove, +}; +module_platform_driver(asoc_simple_card); + +MODULE_ALIAS("platform:asoc-simple-graph-card"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ASoC Simple Graph Sound Card"); +MODULE_AUTHOR("Kuninori Morimoto "); -- 1.9.1