From patchwork Tue Apr 10 08:44:52 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Sean_Nyekj=C3=A6r?= X-Patchwork-Id: 10332589 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 DE4A860365 for ; Tue, 10 Apr 2018 08:45:53 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id CF23F28D4E for ; Tue, 10 Apr 2018 08:45:53 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id C29FC28C83; Tue, 10 Apr 2018 08:45:53 +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=-7.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI, T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 3455628C83 for ; Tue, 10 Apr 2018 08:45:50 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752322AbeDJIps (ORCPT ); Tue, 10 Apr 2018 04:45:48 -0400 Received: from mail-db5eur01on0133.outbound.protection.outlook.com ([104.47.2.133]:44032 "EHLO EUR01-DB5-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1752242AbeDJIpq (ORCPT ); Tue, 10 Apr 2018 04:45:46 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=prevasonline.onmicrosoft.com; s=selector1-prevas-dk; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version; bh=+AOVrCGkKt3lsk81LZGCd2RLopQAczpoJSp6rXFS7fY=; b=U9OXLuqtdH1yX4FYj/XAQixVfs6HXo7r90Ft2RcrmnDFKYxdFDHmVYW486ZzASqnyAYK/pZB+5x2+pJ1DrLHbr+IZo12hcOLLeByxebcw1Lgh7QD54oDNDN6DFKO+REBpycpoPTb6WCWAkgaZV9GI3/7DiEZ9mvsVIrrqkKTGHI= Authentication-Results: spf=none (sender IP is ) smtp.mailfrom=Sean.Nyekjaer@prevas.dk; Received: from skn.prevas.se (81.216.59.226) by HE1PR10MB0234.EURPRD10.PROD.OUTLOOK.COM (2a01:111:e400:c51b::11) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P256) id 15.20.653.12; Tue, 10 Apr 2018 08:45:41 +0000 From: Sean Nyekjaer To: jic23@kernel.org, robh+dt@kernel.org Cc: Sean Nyekjaer , knaack.h@gmx.de, lars@metafoo.de, pmeerw@pmeerw.net, mark.rutland@arm.com, linux-iio@vger.kernel.org, devicetree@vger.kernel.org Subject: [RFC PATCH v2 1/2] iio: dac: add TI DAC5755 family support Date: Tue, 10 Apr 2018 10:44:52 +0200 Message-Id: <20180410084453.6189-1-sean.nyekjaer@prevas.dk> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180322102336.32268-1-sean.nyekjaer@prevas.dk> References: <20180322102336.32268-1-sean.nyekjaer@prevas.dk> MIME-Version: 1.0 X-Originating-IP: [81.216.59.226] X-ClientProxiedBy: AM5PR0402CA0007.eurprd04.prod.outlook.com (2603:10a6:203:90::17) To HE1PR10MB0234.EURPRD10.PROD.OUTLOOK.COM (2a01:111:e400:c51b::11) X-MS-PublicTrafficType: Email X-MS-Office365-Filtering-Correlation-Id: b787ce54-c98b-4749-8c66-08d59ebf7157 X-Microsoft-Antispam: UriScan:; BCL:0; PCL:0; RULEID:(7020095)(4652020)(5600026)(4604075)(4534165)(4627221)(201703031133081)(201702281549075)(2017052603328)(7153060)(7193020); SRVR:HE1PR10MB0234; X-Microsoft-Exchange-Diagnostics: 1; HE1PR10MB0234; 3:H8dxfwJeqNJZMvsz0rAy6w405UOiGVgUfYIWiCP3BrGEi/VkRu8m+FTrwbA4GbLxyVX597TkMDD/tuuJjZbUuobzAImirTdy5NA43jffy3e8rIkTiK/iOrktB28OS/Tk2hJg87J1vVNxTLUhcgjE7/DNLQtRWlmg480p+qkEgLl0ig4lc93bRF1GU9Ihk016iJupr/sxrQ4jysZV5rGUC83sQbzPG9DTffNcZkYeczjmWbw2I5zAAHz4MhQBoDJ2; 25:xK4plmigTk595JmN0ADLK3XBY6N9MNer00N04WDeN9z2RUHD9ZL0KKAaNsXkLZuDoASV9NYpzj+twHD+Q/IZGh2WHMl3gvWO/ljVaGITeheTl3jR1IVAGZ+2UG4yPnqf0ww34aXR0tUnXEVJjiH/bjoDX1xTPdKeixfXcpO07kMu5+GXuYbXIetoduVd/QxJeDS1zUGLDBGRDrvu89HkhWh+H/vWoStnCk/AmWaFjr8Iha23d+aZwXw5Mo6wA74FdUtJ1vSJ//nanitTyyUZrxXLT5LAs/pzJlubKG211KxRuHRqM4pFsPk2dDRVHMqGj2HEG/YIh+xF/iStz/4ayg==; 31:H/DeFa8ZKJ1KeWxx8SrLodDahrgIKvze7zwKMDNavgEto2qqNp+O2dyCxGXVS050jdphzS49vr4zD4Gu055SDdLomuF5rbPjILGf6WcUJmRrI5qrfHnPhoCpLAB/tu2pzEsHQK9BVKH8CPJDEeIKz4+ypEt+hseRzzWmXkKAvL6sbHeOKrJGNn/VZKY141PdvtZ7zaVLa751HWYoQESuFzO3kMlprLj0jHNtKhi9ogw= X-MS-TrafficTypeDiagnostic: HE1PR10MB0234: X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:(31051911155226)(131327999870524)(106291317490208); X-Exchange-Antispam-Report-CFA-Test: BCL:0; PCL:0; RULEID:(6040522)(2401047)(5005006)(8121501046)(3231221)(944501327)(52105095)(93006095)(93001095)(10201501046)(3002001)(6041310)(20161123560045)(20161123564045)(20161123562045)(20161123558120)(201703131423095)(201702281528075)(20161123555045)(201703061421075)(201703061406153)(6072148)(201708071742011); SRVR:HE1PR10MB0234; BCL:0; PCL:0; RULEID:; SRVR:HE1PR10MB0234; X-Microsoft-Exchange-Diagnostics: 1; HE1PR10MB0234; 4:Njnb4I4pahLHzhY6zDezCu7kUnXuOu0rVM7g7dGqQILbJ7NTE41/Gnupc5fVvWo+j0WJbCNQg4koD/HEFPVRq5W7Y5eKYIxGfUMD/DhXZ/HAQJzmro3HpyN+k90gy6fSUSuQQbR7uODu18GMD+THPTMCvNJmDdMMWnaxA1UG2Xz9sCE7lDIF+wcDDgbx2Jd61WVf23P07dvDuyNz55SczU9o90f+U8z0fpjgFs48LeTZ7ttU8aFEMfR4uqUD5lrYUef69vCP/eZyzA4EQCNz2uyWsc4WXphSiWmFLXlIRTx9wVWgM6PLSYdml/Ex9vlUXiwiCiNWHvVHlAByrOEXFcfhXVeMF3necAv0Y5BQIBYp3k+PAI9G/CARp64+vLDJ X-Forefront-PRVS: 0638FD5066 X-Forefront-Antispam-Report: SFV:NSPM; SFS:(10019020)(366004)(396003)(39380400002)(39850400004)(376002)(346002)(199004)(189003)(386003)(16586007)(48376002)(68736007)(966005)(50466002)(51416003)(956004)(7736002)(2616005)(76176011)(478600001)(105586002)(316002)(11346002)(106356001)(52116002)(186003)(53416004)(305945005)(97736004)(72206003)(446003)(59450400001)(476003)(6506007)(486006)(74482002)(5660300001)(8666007)(1076002)(8676002)(26005)(86362001)(6306002)(575784001)(2906002)(6486002)(6666003)(69596002)(8976002)(81156014)(81166006)(8936002)(6512007)(50226002)(16526019)(4326008)(25786009)(3846002)(6116002)(66066001)(47776003)(53936002)(36756003)(2004002)(217873001); DIR:OUT; SFP:1102; SCL:1; SRVR:HE1PR10MB0234; H:skn.prevas.se; FPR:; SPF:None; LANG:en; PTR:InfoNoRecords; A:1; MX:1; Received-SPF: None (protection.outlook.com: prevas.dk does not designate permitted sender hosts) X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1; HE1PR10MB0234; 23:oLdeFYJsl3h0i25xp9X8Um9hpb8jwsjHrh0tEFGA2?= =?us-ascii?Q?TzVjka2LVMebq/LsdRY9zDIjNCS+fs0J/9ycl3N0gJFK57zRwNuIg1HtGApM?= =?us-ascii?Q?DsH7hxhhPhfckzMmGDowDeXk8YUvnzR/nJ3yNVb6WcE/GmMPwJRxBnGEl+LC?= =?us-ascii?Q?RPBZpp4T0BOves/GhHsEqH+oZ8Zy/eMadxii3yanbh5xfztdYMPKryoT3HCC?= =?us-ascii?Q?ZZxOMa4hyoRHT67pG6l3xny5X+mjhPy8WZgtvjG+fKrj8X6iP+6s1AckKywl?= =?us-ascii?Q?1TtIe3panKXhY5TOAm/WVh4aQvRF9OX2hWivF8uP6Y18fjJMYBT4WbV9228S?= =?us-ascii?Q?kixhAcJMIdAPnMs0jOfrhA2ph8ai7KNSTaswkBOk9lacq9WAJUkaLWy3ITaf?= =?us-ascii?Q?LO0arIHx/tqYO6vWXpmLF6RunPHM0As3BIcOQXcvffmw6LvTnNglsGbUxoJx?= =?us-ascii?Q?t4St1txv68jSOh9F1fG7ZtWwaz8crVVlG1QAJbsECRjc5UOKP1n+7ifvasZk?= =?us-ascii?Q?5WYInpkxeTmHCSdNRUt9KkObN0NLaAJGezGF4FcE2HckuAQT56anbYhGXTBE?= =?us-ascii?Q?gfF36rjk6ZsbWEqF8MNaMOfLpR4Lz5ofp2W0+04SvXaOeCoMBsIzUf9Y5Tb5?= =?us-ascii?Q?e7jR8trDVsPsu0Vyg3wZCuXXQ/RjhzbNH/exo6WoK03A5ZV7DuSGOTLEu5qa?= =?us-ascii?Q?tR82UsNDsLCILFOVvRFu0eqiRLtFqYQDMKNAwJ9ZEW4W0QQ214uENdUMb1l9?= =?us-ascii?Q?IELJ6fJaCCPJV3Cfq/+W+VNAYBkilN7rjdHFMBpl663bmzq3WmaIW0+Yiy6Z?= =?us-ascii?Q?cvX2GlvCZe1dwkJb14BmsFK+FMIpJzkukbjFeexOG/euBOfEnN0mKy4wn72b?= =?us-ascii?Q?9WZpGMvHmLvCTh1vedaz4xyK25gAbhupStxHzzL3ZbwF9eJfZoyXJl3Zm1Mk?= =?us-ascii?Q?zSVwVoKoK8mydOxZxhirSGGgaDh7aXMnTJd+Sk4/dl0YlTL5rf9YlkDFGi5K?= =?us-ascii?Q?6rMM16j8V41mEtBzGK7/lJ5N/qpK7zlJCcgFBnes4KchN6Wbjoyu5mUnvz3E?= =?us-ascii?Q?hVvT7stTascA0tDjTafYDkkEg5h4rPq5WCl53tfZ/TNsUrROASjFa6/tvrXn?= =?us-ascii?Q?A5FpJtV8mfrIIen8g99bbJ639CDExGsGQdYbm+MoQLXlvOnW/rPgNhNUWM3B?= =?us-ascii?Q?BCt/Yq0x7VQ4cMTF/8v2WXDT5rN/WhmKa59Sp9L8dLUh7wToi5DGe55g/9d+?= =?us-ascii?Q?4kE7XLpu4ZJQYKAuw7NbffRczJgVHf2orLO0Wu4dX26eL7DmYk/vtyeWSsu0?= =?us-ascii?Q?5GGBQ7kbLEdrPaO41tAvhNSMlYUq4X8j8OG1MVlHLaaWG4FlL4RYOmrL4nyv?= =?us-ascii?Q?LzQyAcVkc2YnlLe3nNFarM9GNb7KXMmlpI0R8I1z8oVDfQ8yBgHIGn9Rsf6W?= =?us-ascii?Q?vSQG5jtSQ=3D=3D?= X-Microsoft-Antispam-Message-Info: QGdlQuczkyjLbe9NDyy78BzMMs7JtgKB1LIeE6NBj38Mhcb1akOnP9aN5lVVLqgyw2ps37lz9rlaARc9H/HBC6gWi/BjzrHh/tj1d/om/L/nQTKHoPsP9ZTU6gx5U+W8wf5v7sPat/LW0rFiVvZPv/zsfnF1WMnrh9cbLkqneCrhPOErlqlfBhVSDNPCg201 X-Microsoft-Exchange-Diagnostics: 1; HE1PR10MB0234; 6:65i3Mi9QDzlCwkNw7Kld00ktKZdhGNSMAzTD06G3FOyorlv9UvrFSgLfmfDdYCGV9Qu01YwWLQEFAh+b5CZHfvX0gJw59HwyGq03gryaF8IvKIeTvh0SWoicIbX/zjRz63baqZbZGy4/nmQlDlfxT/pbpU2qqSknC6OvD+Eed/l29flOhIf8MJUBAe+/5Q0nOwsskD1BTANimA1aQrkdOisUd503A0A++xx5lJih1IZs0nKDzGUHUrh/mpd0vYyCY/SSTzl4bNQKCF7m90IZLHjKhyGVLcTK+V0V0/fBqBGlUFDuzasfie2ZkH5G0wNY0pBcZXbKw+Y8qhv9C4Wk4YEZfFD7HKSUxc4axoAiYoQlTK/+AcRDC4Ze4dhWN3nhDI6D4LbPvNdbWHiQ+tXqxemrJsAc5pQNYCPfBU0Ck9yy+ITtYZoO8va/iw95OYC/WEA/g32nUvX5VqQsjJWjEQ==; 5:4UMLImTHmndUo6unZlEbuOt/GEOARbNB+qQbvZWwLN/DdUx2djrW/M2Iw04h3k4MXWMXrrfqJ+yFkcQO737YPyKuwR7mmg1GtJ7exsGTt+pBow0NvQkmfz6UuGYY9tungZ//UWHRg/b81ff2HgduMdnw7Z21lMtXthPKHTTJIVA=; 24:8STKfMC5uSRhBbYCptsE93vpnw9kFxhgCO9JmIzet426P07Wdog76tT7Xxis5AVSJeptA4ytPmaBfCraCYRbsFouThNIOm4Ha/V2/nbHIa8= SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-Microsoft-Exchange-Diagnostics: 1; HE1PR10MB0234; 7:z0jHGxsojwWORn6FjrTZAofR2dGjJiMWawNdzAHlgw6Wx6d4l4gdNk8pzdWOgLMsC8+PQ0GA/6X/0leSxwtUCMQ/Nhb//S8/8L/mWGDmtZu99rqDFfBTBfqyhfp1kXVQ+bkYUE/KU4CQxNWC05YDQ+X3WzzaQ+Xpe1xkOYgmj8vNxs2VHjWHk2HVBAmoOCN6t/H3f2ZM+yVT30pRLMh7ZCR0Hh14WSNl7AkSJ6Bd5TmLE7rnK9eugaHYWMrf6hD4 X-OriginatorOrg: prevas.dk X-MS-Exchange-CrossTenant-OriginalArrivalTime: 10 Apr 2018 08:45:41.2173 (UTC) X-MS-Exchange-CrossTenant-Network-Message-Id: b787ce54-c98b-4749-8c66-08d59ebf7157 X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: d350cf71-778d-4780-88f5-071a4cb1ed61 X-MS-Exchange-Transport-CrossTenantHeadersStamped: HE1PR10MB0234 Sender: linux-iio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This patch adds support for the Texas Intruments DAC5755 Family. Signed-off-by: Sean Nyekjaer --- Still not have hardware :-( Ready next week I hope. So still RFC. v2: Addressed comments from Jonathan and Peter - improved readability - i2c return check fixed - added structure pointer instead of copying elements - splitted single and quad operations with function pointers drivers/iio/dac/Kconfig | 10 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/ti-dac5571.c | 430 +++++++++++++++++++++++++++++++++++ 3 files changed, 441 insertions(+) create mode 100644 drivers/iio/dac/ti-dac5571.c diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index 965d5c0d2468..b64fabbc74f8 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -320,6 +320,16 @@ config TI_DAC082S085 If compiled as a module, it will be called ti-dac082s085. +config TI_DAC5571 + tristate "Texas Instruments 8/10/12/16-bit 1/2/4-channel DAC driver" + depends on I2C + help + Driver for the Texas Instruments + DAC5571, DAC6571, DAC7571, DAC5574, DAC6574, DAC7574, DAC5573, + DAC6573, DAC7573, DAC8571, DAC8574. + + If compiled as a module, it will be called ti-dac5571. + config VF610_DAC tristate "Vybrid vf610 DAC driver" depends on OF diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 81e710ed7491..1ab358d522ee 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -35,4 +35,5 @@ obj-$(CONFIG_MCP4922) += mcp4922.o obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o obj-$(CONFIG_STM32_DAC) += stm32-dac.o obj-$(CONFIG_TI_DAC082S085) += ti-dac082s085.o +obj-$(CONFIG_TI_DAC5571) += ti-dac5571.o obj-$(CONFIG_VF610_DAC) += vf610_dac.o diff --git a/drivers/iio/dac/ti-dac5571.c b/drivers/iio/dac/ti-dac5571.c new file mode 100644 index 000000000000..0ee5ca1ce6eb --- /dev/null +++ b/drivers/iio/dac/ti-dac5571.c @@ -0,0 +1,430 @@ +/* + * ti-dac5571.c - Texas Instruments 8/10/12-bit 1/4-channel DAC driver + * + * Copyright (C) 2018 Prevas A/S + * + * http://www.ti.com/lit/ds/symlink/dac5571.pdf + * http://www.ti.com/lit/ds/symlink/dac6571.pdf + * http://www.ti.com/lit/ds/symlink/dac7571.pdf + * http://www.ti.com/lit/ds/symlink/dac5574.pdf + * http://www.ti.com/lit/ds/symlink/dac6574.pdf + * http://www.ti.com/lit/ds/symlink/dac7574.pdf + * http://www.ti.com/lit/ds/symlink/dac5573.pdf + * http://www.ti.com/lit/ds/symlink/dac6573.pdf + * http://www.ti.com/lit/ds/symlink/dac7573.pdf + * + * 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 + +enum chip_id { + single_8bit, single_10bit, single_12bit, + quad_8bit, quad_10bit, quad_12bit +}; + +#define DAC5571_SINGLE_CHANNEL 1 +#define DAC5571_QUAD_CHANNEL 4 + +struct dac5571_spec { + u8 num_channels; + u8 resolution; +}; + +static const struct dac5571_spec dac5571_spec[] = { + [single_8bit] = {.num_channels = 1, .resolution = 8}, + [single_10bit] = {.num_channels = 1, .resolution = 10}, + [single_12bit] = {.num_channels = 1, .resolution = 12}, + [quad_8bit] = {.num_channels = 4, .resolution = 8}, + [quad_10bit] = {.num_channels = 4, .resolution = 10}, + [quad_12bit] = {.num_channels = 4, .resolution = 12}, +}; + +struct dac5571_data { + struct i2c_client *client; + int id; + struct mutex lock; + struct regulator *vref; + u16 val[4]; + bool powerdown; + u8 powerdown_mode; + struct dac5571_spec const *spec; + int (*dac5571_cmd) (struct dac5571_data *, int, u16); + int (*dac5571_pwrdwn) (struct dac5571_data *, int, u8); + u8 buf[3] ____cacheline_aligned; +}; + +#define DAC5571_POWERDOWN(mode) ((mode) + 1) +#define DAC5571_POWERDOWN_FLAG BIT(0) +#define DAC5571_CHANNEL_SELECT BIT(1) +#define DAC5571_LOADMODE_DIRECT BIT(4) +#define DAC5571_SINGLE_PWRDWN_BITS 4 +#define DAC5571_QUAD_PWRDWN_BITS 6 + +static int dac5571_cmd_single(struct dac5571_data *data, int channel, u16 val) +{ + unsigned int shift; + + shift = 12 - data->spec->resolution; + data->buf[0] = val << shift; + data->buf[1] = val >> (8 - shift); + return i2c_master_send(data->client, data->buf, 2); +} + + +static int dac5571_cmd_quad(struct dac5571_data *data, int channel, u16 val) +{ + unsigned int shift; + + shift = 16 - data->spec->resolution; + data->buf[0] = val << shift; + data->buf[1] = (val >> (8 - shift)); + data->buf[2] = (channel << DAC5571_CHANNEL_SELECT) | + DAC5571_LOADMODE_DIRECT; + return i2c_master_send(data->client, data->buf, 3); +} + +static int dac5571_pwrdwn_single(struct dac5571_data *data, int channel, u8 pwrdwn) +{ + unsigned int shift; + + shift = 12 - data->spec->resolution; + data->buf[0] = 0; + data->buf[1] = pwrdwn << DAC5571_SINGLE_PWRDWN_BITS; + return i2c_master_send(data->client, data->buf, 2); +} + +static int dac5571_pwrdwn_quad(struct dac5571_data *data, int channel, u8 pwrdwn) +{ + unsigned int shift; + + shift = 16 - data->spec->resolution; + data->buf[0] = 0; + data->buf[1] = pwrdwn << DAC5571_QUAD_PWRDWN_BITS; + data->buf[2] = (channel << DAC5571_CHANNEL_SELECT) | + DAC5571_LOADMODE_DIRECT | DAC5571_POWERDOWN_FLAG; + return i2c_master_send(data->client, data->buf, 3); +} + +static const char *const dac5571_powerdown_modes[] = { + "1kohm_to_gnd", "100kohm_to_gnd", "three_state", +}; + +static int dac5571_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct dac5571_data *data = iio_priv(indio_dev); + + return data->powerdown_mode; +} + +static int dac5571_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct dac5571_data *data = iio_priv(indio_dev); + int ret = 0; + + if (data->powerdown_mode == mode) + return 0; + + mutex_lock(&data->lock); + if (data->powerdown) { + ret = data->dac5571_pwrdwn(data, chan->channel, + DAC5571_POWERDOWN(mode)); + if (ret <= 0) + goto out; + } + data->powerdown_mode = mode; + + out: + mutex_unlock(&data->lock); + return ret; +} + +static const struct iio_enum dac5571_powerdown_mode = { + .items = dac5571_powerdown_modes, + .num_items = ARRAY_SIZE(dac5571_powerdown_modes), + .get = dac5571_get_powerdown_mode, + .set = dac5571_set_powerdown_mode, +}; + +static ssize_t dac5571_read_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct dac5571_data *data = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", data->powerdown); +} + +static ssize_t dac5571_write_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct dac5571_data *data = iio_priv(indio_dev); + bool powerdown; + int ret; + + ret = strtobool(buf, &powerdown); + if (ret) + return ret; + + if (data->powerdown == powerdown) + return len; + + mutex_lock(&data->lock); + if (powerdown) + ret = data->dac5571_pwrdwn(data, chan->channel, + DAC5571_POWERDOWN(data->powerdown_mode)); + else + ret = data->dac5571_cmd(data, chan->channel, data->val[0]); + if (ret <= 0) + data->powerdown = powerdown; + mutex_unlock(&data->lock); + + return ret ? ret : len; +} + + +static const struct iio_chan_spec_ext_info dac5571_ext_info[] = { + { + .name = "powerdown", + .read = dac5571_read_powerdown, + .write = dac5571_write_powerdown, + .shared = IIO_SHARED_BY_TYPE, + }, + IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, &dac5571_powerdown_mode), + IIO_ENUM_AVAILABLE("powerdown_mode", &dac5571_powerdown_mode), + {}, +}; + +#define dac5571_CHANNEL(chan, name) { \ + .type = IIO_VOLTAGE, \ + .channel = (chan), \ + .address = (chan), \ + .indexed = true, \ + .output = true, \ + .datasheet_name = name, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = dac5571_ext_info, \ +} + +static const struct iio_chan_spec dac5571_channels[] = { + dac5571_CHANNEL(0, "A"), + dac5571_CHANNEL(1, "B"), + dac5571_CHANNEL(2, "C"), + dac5571_CHANNEL(3, "D"), +}; + +static int dac5571_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dac5571_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = data->val[chan->channel]; + return IIO_VAL_INT; + break; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(data->vref); + if (ret < 0) + return ret; + + *val = ret / 1000; + *val2 = data->spec->resolution; + return IIO_VAL_FRACTIONAL_LOG2; + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static int dac5571_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct dac5571_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (data->val[chan->channel] == val) + return 0; + + if (val >= (1 << data->spec->resolution) || val < 0) + return -EINVAL; + + if (data->powerdown) + return -EBUSY; + + mutex_lock(&data->lock); + ret = data->dac5571_cmd(data, chan->channel, val); + if (ret <= 0) + data->val[chan->channel] = val; + mutex_unlock(&data->lock); + return ret; + break; + + default: + return -EINVAL; + } + + return ret; +} + +static int dac5571_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT; +} + +static const struct iio_info dac5571_info = { + .read_raw = dac5571_read_raw, + .write_raw = dac5571_write_raw, + .write_raw_get_fmt = dac5571_write_raw_get_fmt, +}; + +static int dac5571_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + const struct dac5571_spec *spec; + struct dac5571_data *data; + struct iio_dev *indio_dev; + int ret, i; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = client->dev.of_node; + indio_dev->info = &dac5571_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = dac5571_channels; + + spec = &dac5571_spec[id->driver_data]; + indio_dev->num_channels = spec->num_channels; + data->spec = spec; + + data->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(data->vref)) + return PTR_ERR(data->vref); + + ret = regulator_enable(data->vref); + if (ret < 0) + return ret; + + mutex_init(&data->lock); + + switch (spec->num_channels) { + case DAC5571_SINGLE_CHANNEL: + data->dac5571_cmd = dac5571_cmd_single; + data->dac5571_pwrdwn = dac5571_pwrdwn_single; + break; + case DAC5571_QUAD_CHANNEL: + data->dac5571_cmd = dac5571_cmd_quad; + data->dac5571_pwrdwn = dac5571_pwrdwn_quad; + break; + default: + goto err; + break; + } + + for (i = 0; i < spec->num_channels; i++) { + ret = data->dac5571_cmd(data, i, 0); + if (ret <= 0) { + dev_err(dev, "failed to initialize channel %d to 0\n", i); + goto err; + } + } + + ret = iio_device_register(indio_dev); + if (ret) + goto err; + + return 0; + + err: + regulator_disable(data->vref); + return ret; +} + +static int dac5571_remove(struct i2c_client *i2c) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(i2c); + struct dac5571_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(data->vref); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id dac5571_of_id[] = { + {.compatible = "ti,dac5571"}, + {.compatible = "ti,dac6571"}, + {.compatible = "ti,dac7571"}, + {.compatible = "ti,dac5574"}, + {.compatible = "ti,dac6574"}, + {.compatible = "ti,dac7574"}, + {.compatible = "ti,dac5573"}, + {.compatible = "ti,dac6573"}, + {.compatible = "ti,dac7573"}, + {} +}; +MODULE_DEVICE_TABLE(of, dac5571_of_id); +#endif + +static const struct i2c_device_id dac5571_id[] = { + {"dac5571", single_8bit}, + {"dac6571", single_10bit}, + {"dac7571", single_12bit}, + {"dac5574", quad_8bit}, + {"dac6574", quad_10bit}, + {"dac7574", quad_12bit}, + {"dac5573", quad_8bit}, + {"dac6573", quad_10bit}, + {"dac7573", quad_12bit}, + {} +}; +MODULE_DEVICE_TABLE(i2c, dac5571_id); + +static struct i2c_driver dac5571_driver = { + .driver = { + .name = "ti-dac5571", + }, + .probe = dac5571_probe, + .remove = dac5571_remove, + .id_table = dac5571_id, +}; +module_i2c_driver(dac5571_driver); + +MODULE_AUTHOR("Sean Nyekjaer "); +MODULE_DESCRIPTION("Texas Instruments 8/10/12-bit 1/4-channel DAC driver"); +MODULE_LICENSE("GPL v2");