From nobody Mon Feb 9 05:52:55 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 04A81C001B0 for ; Mon, 10 Jul 2023 20:43:17 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232018AbjGJUnP (ORCPT ); Mon, 10 Jul 2023 16:43:15 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48672 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229560AbjGJUnN (ORCPT ); Mon, 10 Jul 2023 16:43:13 -0400 Received: from mail-ej1-x633.google.com (mail-ej1-x633.google.com [IPv6:2a00:1450:4864:20::633]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 5738C127; Mon, 10 Jul 2023 13:43:10 -0700 (PDT) Received: by mail-ej1-x633.google.com with SMTP id a640c23a62f3a-99313a34b2dso575965466b.1; Mon, 10 Jul 2023 13:43:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1689021789; x=1691613789; h=content-transfer-encoding:subject:from:cc:to:content-language :user-agent:mime-version:date:message-id:from:to:cc:subject:date :message-id:reply-to; bh=rbXqsZsv8GsMvVf0SrhDK13+rgC7D43Is3mX+A2+JOQ=; b=fmOisCcR06kzVtO1N+V7fgufsm6bztezxC0yf4AB55JQZoZH5ux6Ja1cJpTqkg9wG6 a0ZhNwaKRVyqVcsoKgV8FFJ89AXPZNEVytJ/N582FsBu9JcY0UTICFPGLA89D8QL8l9v Z5YyxEoKvmPATcm84Rzm/vWTKe6ByGw5eBqQqV95JgP8OL5d394Ay/kP0gLb4lyz29J8 2Op4d3OOpsUUb1pTdB6EaXhKN49+NnV4N80N3k2nXIdRjxZVjokWG/u2s8WsHRBEch4G 5Fu86o66dFbTspLpUeuKpuxIvK80KfglVLl8flaNO4ZBqqFYVxO1da5Wf6W2H0QNSuyd jVFw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1689021789; x=1691613789; h=content-transfer-encoding:subject:from:cc:to:content-language :user-agent:mime-version:date:message-id:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=rbXqsZsv8GsMvVf0SrhDK13+rgC7D43Is3mX+A2+JOQ=; b=NGtNv2xQcTZ82YaT9QBvg0ZCAy7r4pfuQEEmDrbvx8go8mXr1rLA7JQB21hJZtrLl9 yoHw8tBgnWXUwjF9PfXastBUQfOkkSNLQEhHJHrPDZK86KiTydPiE7ZTTbLEyz6QiNcz fAk2lQwmIECViiOPiE23DqHo8xiyWeJD9tisX3c0uWTFRWCjGhpkPsSO7P7T2ZXykOAK CU4hWA/kjEX/J0QGG09W4bJRHhdGc3WvSbKxJ/4OMfm1fhbCENerGWBAiwQTMuFQg9TR lL/eNnzHamLW07+WGhHJyAoJSBqRh9fyInk9Vu21IMpSypNmBhKszKNkw+AhthXtOzVR HTNA== X-Gm-Message-State: ABy/qLYYwrToD4NxnLyeQtU0diPGpB7cKvtLyMFkDwzTZ+oPpIpnwpXC R38EcYQq4yxtq9C2JE5I4/TY3k462aMwOg== X-Google-Smtp-Source: APBJJlF8JAHTng2hTxBt6g3CI+GR15il43L7EU86mfTZh1Q3eOVwnJFW/ZuaEIB3/lTtLcfkfKpSUQ== X-Received: by 2002:a17:906:7a07:b0:993:da91:6e0c with SMTP id d7-20020a1709067a0700b00993da916e0cmr11407511ejo.3.1689021788491; Mon, 10 Jul 2023 13:43:08 -0700 (PDT) Received: from [192.168.4.4] (host-95-237-109-246.retail.telecomitalia.it. [95.237.109.246]) by smtp.gmail.com with ESMTPSA id e17-20020a1709062c1100b009930308425csm202923ejh.31.2023.07.10.13.43.07 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Mon, 10 Jul 2023 13:43:08 -0700 (PDT) Message-ID: <87174c80-aa05-8db7-18e8-e22479d9c635@gmail.com> Date: Mon, 10 Jul 2023 22:43:07 +0200 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Thunderbird/102.11.0 Content-Language: en-US To: Jonathan Cameron , Lars-Peter Clausen , Rob Herring , Andrea Collamati Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org From: Andrea Collamati Subject: add mcp4728 I2C DAC driver Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From 01b156ca1b27be83f4c74c288dbc0bcad178fe0b Mon Sep 17 00:00:00 2001 From: Andrea Collamati Date: Mon, 10 Jul 2023 16:20:40 +0200 Subject: [PATCH] iio: add mcp4728 I2C DAC driver Microchip MCP4728 is a 12-bit quad channel digital-to-analog converter (DAC) with I2C interface. This patch adds support for per-channel gain, power state and power down mo= de control. Current state could be saved to on-chip EEPROM. Internal voltage reference and external vdd ref are supported. --- =C2=A0.../bindings/iio/dac/microchip,mcp4728.yaml=C2=A0=C2=A0 |=C2=A0 42 ++ =C2=A0drivers/iio/dac/Kconfig=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0 |=C2=A0 12 + =C2=A0drivers/iio/dac/Makefile=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0 |=C2=A0=C2=A0 1 + =C2=A0drivers/iio/dac/mcp4728.c=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0 | 641 ++++++++++++++++++ =C2=A04 files changed, 696 insertions(+) =C2=A0create mode 100644 Documentation/devicetree/bindings/iio/dac/microchi= p,mcp4728.yaml =C2=A0create mode 100644 drivers/iio/dac/mcp4728.c diff --git a/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.ya= ml b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml new file mode 100644 index 000000000000..68f4e359a921 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/microchip,mcp4728.yaml @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/dac/microchip,mcp4728.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip mcp4728 + +maintainers: +=C2=A0 - Andrea Collamati + +properties: +=C2=A0 compatible: +=C2=A0=C2=A0=C2=A0 enum: +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 - microchip,mcp4728=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0 +=C2=A0 reg: +=C2=A0=C2=A0=C2=A0 maxItems: 1 + +=C2=A0 vdd-supply: +=C2=A0=C2=A0=C2=A0 description: | +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 Provides both power and acts as the referen= ce supply on the mcp4728 +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 when Internal Vref is not selected.=C2=A0= =C2=A0=C2=A0=C2=A0 =C2=A0 + +required: +=C2=A0 - compatible +=C2=A0 - reg +=C2=A0 - vdd-supply + +additionalProperties: false + +examples: +=C2=A0 - | +=C2=A0=C2=A0=C2=A0 i2c { +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 #address-cells =3D <1>; +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 #size-cells =3D <0>; + +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 mcp4728_dac@64 { +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 compati= ble =3D "microchip,mcp4728"; +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 reg =3D= <0x60>; +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 vdd-sup= ply =3D <&vdac_vdd>; +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 }; +=C2=A0=C2=A0=C2=A0 }; diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index c0bf0d84197f..6c96cd30994a 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -376,6 +376,18 @@ config MCP4725 =C2=A0=C2=A0=C2=A0=C2=A0 =C2=A0 To compile this driver as a module, choose = M here: the module =C2=A0=C2=A0=C2=A0=C2=A0 =C2=A0 will be called mcp4725. =C2=A0 +config MCP4728 +=C2=A0=C2=A0=C2=A0 tristate "MCP4728 DAC driver" +=C2=A0=C2=A0=C2=A0 depends on I2C +=C2=A0=C2=A0=C2=A0 help +=C2=A0=C2=A0=C2=A0 =C2=A0 Say Y here if you want to build a driver for the= Microchip +=C2=A0=C2=A0=C2=A0 =C2=A0 MCP4728 quad channel, 12-bit digital-to-analog c= onverter (DAC) +=C2=A0=C2=A0=C2=A0 =C2=A0 with I2C interface. + +=C2=A0=C2=A0=C2=A0 =C2=A0 To compile this driver as a module, choose M her= e: the module +=C2=A0=C2=A0=C2=A0 =C2=A0 will be called mcp4728. + + =C2=A0config MCP4922 =C2=A0=C2=A0=C2=A0=C2=A0 tristate "MCP4902, MCP4912, MCP4922 DAC driver" =C2=A0=C2=A0=C2=A0=C2=A0 depends on SPI diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index ec3e42713f00..35ac62a61b05 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_M62332) +=3D m62332.o =C2=A0obj-$(CONFIG_MAX517) +=3D max517.o =C2=A0obj-$(CONFIG_MAX5821) +=3D max5821.o =C2=A0obj-$(CONFIG_MCP4725) +=3D mcp4725.o +obj-$(CONFIG_MCP4728) +=3D mcp4728.o =C2=A0obj-$(CONFIG_MCP4922) +=3D mcp4922.o =C2=A0obj-$(CONFIG_STM32_DAC_CORE) +=3D stm32-dac-core.o =C2=A0obj-$(CONFIG_STM32_DAC) +=3D stm32-dac.o diff --git a/drivers/iio/dac/mcp4728.c b/drivers/iio/dac/mcp4728.c new file mode 100644 index 000000000000..d304fa8ffa26 --- /dev/null +++ b/drivers/iio/dac/mcp4728.c @@ -0,0 +1,641 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Support for Microchip MCP4728 + * + * Copyright (C) 2023 Andrea Collamati + * + * Based on mcp4725 by Peter Meerwald + * + * Driver for the Microchip I2C 12-bit digital-to-analog quad channels + * converter (DAC). + * + * (7-bit I2C slave address 0x60, the three LSBs can be configured in + * hardware) + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define MCP4728_DRV_NAME "mcp4728" + +#define MCP4728_RESOLUTION 12 +#define MCP4728_N_CHANNELS 4 + +#define MCP4728_CMD_POS 3 +#define MCP4728_CMD_UDAC_POS 0 +#define MCP4728_CMD_CH_SEL_POS 1 + +#define MCP4728_CMD_VREF_MASK 0x80 +#define MCP4728_CMD_VREF_POS 7 + +#define MCP4728_CMD_PDMODE_MASK 0x60 +#define MCP4728_CMD_PDMODE_POS 5 + +#define MCP4728_CMD_GAIN_MASK 0x10 +#define MCP4728_CMD_GAIN_POS 4 + +#define MCP4728_MW_CMD 0x08 // Multiwrite Command +#define MCP4728_SW_CMD 0x0A // Sequential Write Command (include eeprom) + +#define MCP4728_READ_RESPONSE_LEN (MCP4728_N_CHANNELS * 3 * 2) // Read Mes= sage +#define MCP4728_WRITE_EEPROM_LEN \ +=C2=A0=C2=A0=C2=A0 (1 + MCP4728_N_CHANNELS * 2) // Sequential Write + +enum vref_mode { +=C2=A0=C2=A0=C2=A0 MCP4728_VREF_EXTERNAL_VDD =3D 0, +=C2=A0=C2=A0=C2=A0 MCP4728_VRED_INTERNAL_2048mV =3D 1, +}; + +enum gain_mode { +=C2=A0=C2=A0=C2=A0 MCP4728_GAIN_X1 =3D 0, +=C2=A0=C2=A0=C2=A0 MCP4728_GAIN_X2 =3D 1, +}; + +enum iio_powerdown_mode { +=C2=A0=C2=A0=C2=A0 MCP4728_IIO_1K, +=C2=A0=C2=A0=C2=A0 MCP4728_IIO_100K, +=C2=A0=C2=A0=C2=A0 MCP4728_IIO_500K, +}; + +struct mcp4728_channel_data { +=C2=A0=C2=A0=C2=A0 enum vref_mode ref_mode; +=C2=A0=C2=A0=C2=A0 enum iio_powerdown_mode pd_mode; +=C2=A0=C2=A0=C2=A0 enum gain_mode g_mode; +=C2=A0=C2=A0=C2=A0 u16 dac_value; +}; + +struct mcp4728_data { +=C2=A0=C2=A0=C2=A0 struct i2c_client *client; +=C2=A0=C2=A0=C2=A0 int id; +=C2=A0=C2=A0=C2=A0 struct regulator *vdd_reg; +=C2=A0=C2=A0=C2=A0 bool powerdown; +=C2=A0=C2=A0=C2=A0 struct mcp4728_channel_data channel_data[MCP4728_N_CHAN= NELS]; +}; + +#define MCP4728_CHAN(chan)=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 \ +=C2=A0=C2=A0=C2=A0 {=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 \ +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 .type =3D IIO_VOLTAGE, .output =3D 1= , .indexed =3D 1,=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 \ +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 .channel =3D chan, .info_mask_separa= te =3D BIT(IIO_CHAN_INFO_RAW), \ +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 .info_mask_shared_by_type =3D BIT(II= O_CHAN_INFO_SCALE),=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 \ +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 .ext_info =3D mcp4728_ext_info,=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 \ +=C2=A0=C2=A0=C2=A0 } + +static int mcp4728_suspend(struct device *dev); +static int mcp4728_resume(struct device *dev); + +static ssize_t mcp4728_store_eeprom(struct device *dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0=C2=A0=C2=A0 struct device_attribute *attr, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0=C2=A0=C2=A0 const char *buf, size_t len) +{ +=C2=A0=C2=A0=C2=A0 struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); +=C2=A0=C2=A0=C2=A0 u8 outbuf[MCP4728_WRITE_EEPROM_LEN]; +=C2=A0=C2=A0=C2=A0 int tries =3D 20; +=C2=A0=C2=A0=C2=A0 u8 inbuf[3]; +=C2=A0=C2=A0=C2=A0 bool state; +=C2=A0=C2=A0=C2=A0 int ret; +=C2=A0=C2=A0=C2=A0 unsigned int i; + +=C2=A0=C2=A0=C2=A0 ret =3D kstrtobool(buf, &state); +=C2=A0=C2=A0=C2=A0 if (ret < 0) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return ret; + +=C2=A0=C2=A0=C2=A0 if (!state) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return 0; + +=C2=A0=C2=A0=C2=A0 outbuf[0] =3D MCP4728_SW_CMD << MCP4728_CMD_POS; // Com= mand ID + +=C2=A0=C2=A0=C2=A0 for (i =3D 0; i < MCP4728_N_CHANNELS; i++) { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 struct mcp4728_channel_data *ch =3D = &(data->channel_data[i]); +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 int offset =3D 1 + i * 2; + +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 outbuf[offset] =3D ch->ref_mode << M= CP4728_CMD_VREF_POS; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 if (data->powerdown) { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 u8 mcp4728_pd_mod= e =3D ch->pd_mode + 1; + +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 outbuf[1] |=3D mc= p4728_pd_mode << MCP4728_CMD_PDMODE_POS; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 } + +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 outbuf[offset] |=3D ch->g_mode << MC= P4728_CMD_GAIN_POS; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 outbuf[offset] |=3D ch->dac_value >>= 8; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 outbuf[offset + 1] =3D ch->dac_value= & 0xff; +=C2=A0=C2=A0=C2=A0 } + +=C2=A0=C2=A0=C2=A0 ret =3D i2c_master_send(data->client, outbuf, MCP4728_W= RITE_EEPROM_LEN); +=C2=A0=C2=A0=C2=A0 if (ret < 0) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return ret; +=C2=A0=C2=A0=C2=A0 else if (ret !=3D MCP4728_WRITE_EEPROM_LEN) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return -EIO; + +=C2=A0=C2=A0=C2=A0 /* wait RDY signal for write complete, takes up to 50ms= */ +=C2=A0=C2=A0=C2=A0 while (tries--) { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 msleep(20); +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ret =3D i2c_master_recv(data->client= , inbuf, 3); +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 if (ret < 0) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return ret; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 else if (ret !=3D 3) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return -EIO; + +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 if (inbuf[0] & 0x80) // check RDY fl= ag +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 break; +=C2=A0=C2=A0=C2=A0 } + +=C2=A0=C2=A0=C2=A0 if (tries < 0) { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 dev_err(&data->client->dev, "%s fail= ed, incomplete\n", +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 __func__); +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return -EIO; +=C2=A0=C2=A0=C2=A0 } +=C2=A0=C2=A0=C2=A0 return len; +} + +static IIO_DEVICE_ATTR(store_eeprom, 0200, NULL, mcp4728_store_eeprom, 0); + +static struct attribute *mcp4728_attributes[] =3D { +=C2=A0=C2=A0=C2=A0 &iio_dev_attr_store_eeprom.dev_attr.attr, +=C2=A0=C2=A0=C2=A0 NULL, +}; + +static const struct attribute_group mcp4728_attribute_group =3D { +=C2=A0=C2=A0=C2=A0 .attrs =3D mcp4728_attributes, +}; + +enum chip_id { +=C2=A0=C2=A0=C2=A0 MCP4728, +}; + +static int mcp4728_program_channel_cfg(int channel, struct iio_dev *indio_= dev) +{ +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); +=C2=A0=C2=A0=C2=A0 struct mcp4728_channel_data *ch =3D &(data->channel_dat= a[channel]); +=C2=A0=C2=A0=C2=A0 u8 outbuf[3]; +=C2=A0=C2=A0=C2=A0 int ret; + +=C2=A0=C2=A0=C2=A0 outbuf[0] =3D MCP4728_MW_CMD << MCP4728_CMD_POS; // Com= mand ID +=C2=A0=C2=A0=C2=A0 outbuf[0] |=3D channel << MCP4728_CMD_CH_SEL_POS; // Ch= annel Selector +=C2=A0=C2=A0=C2=A0 outbuf[0] |=3D 0; // UDAC =3D 0 + +=C2=A0=C2=A0=C2=A0 outbuf[1] =3D ch->ref_mode << MCP4728_CMD_VREF_POS; +=C2=A0=C2=A0=C2=A0 if (data->powerdown) { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 u8 mcp4728_pd_mode =3D ch->pd_mode += 1; + +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 outbuf[1] |=3D mcp4728_pd_mode << MC= P4728_CMD_PDMODE_POS; +=C2=A0=C2=A0=C2=A0 } + +=C2=A0=C2=A0=C2=A0 outbuf[1] |=3D ch->g_mode << MCP4728_CMD_GAIN_POS; + +=C2=A0=C2=A0=C2=A0 outbuf[1] |=3D ch->dac_value >> 8; +=C2=A0=C2=A0=C2=A0 outbuf[2] =3D ch->dac_value & 0xff; + +=C2=A0=C2=A0=C2=A0 ret =3D i2c_master_send(data->client, outbuf, 3); +=C2=A0=C2=A0=C2=A0 if (ret < 0) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return ret; +=C2=A0=C2=A0=C2=A0 else if (ret !=3D 3) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return -EIO; +=C2=A0=C2=A0=C2=A0 else +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return 0; +} + +// powerdown mode +static const char *const mcp4728_powerdown_modes[] =3D { +=C2=A0=C2=A0=C2=A0 "1kohm_to_gnd", +=C2=A0=C2=A0=C2=A0 "100kohm_to_gnd", +=C2=A0=C2=A0=C2=A0 "500kohm_to_gnd" +}; + +static int mcp4728_get_powerdown_mode(struct iio_dev *indio_dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 const struct iio_chan_spec *chan) +{ +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); + +=C2=A0=C2=A0=C2=A0 return data->channel_data[chan->channel].pd_mode; +} + +static int mcp4728_set_powerdown_mode(struct iio_dev *indio_dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 const struct iio_chan_spec *chan, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 unsigned int mode) +{ +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); + +=C2=A0=C2=A0=C2=A0 data->channel_data[chan->channel].pd_mode =3D mode; + +=C2=A0=C2=A0=C2=A0 return 0; +} + +static ssize_t mcp4728_read_powerdown(struct iio_dev *indio_dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 uintptr_t private, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 const struct iio_chan_spec *chan, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 char *buf) +{ +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); + +=C2=A0=C2=A0=C2=A0 return sysfs_emit(buf, "%d\n", data->powerdown); +} + +static ssize_t mcp4728_write_powerdown(struct iio_dev *indio_dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 uintptr_t private, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 const struct iio_chan_spec *chan, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 const char *buf, size_t len) +{ +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); +=C2=A0=C2=A0=C2=A0 bool state; +=C2=A0=C2=A0=C2=A0 int ret; + +=C2=A0=C2=A0=C2=A0 ret =3D kstrtobool(buf, &state); +=C2=A0=C2=A0=C2=A0 if (ret) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return ret; + +=C2=A0=C2=A0=C2=A0 if (state) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ret =3D mcp4728_suspend(&data->clien= t->dev); +=C2=A0=C2=A0=C2=A0 else +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ret =3D mcp4728_resume(&data->client= ->dev); +=C2=A0=C2=A0=C2=A0 if (ret < 0) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return ret; + +=C2=A0=C2=A0=C2=A0 return len; +} + +static const struct iio_enum mcp4728_powerdown_mode_enum =3D { +=C2=A0=C2=A0=C2=A0 .items =3D mcp4728_powerdown_modes, +=C2=A0=C2=A0=C2=A0 .num_items =3D ARRAY_SIZE(mcp4728_powerdown_modes), +=C2=A0=C2=A0=C2=A0 .get =3D mcp4728_get_powerdown_mode, +=C2=A0=C2=A0=C2=A0 .set =3D mcp4728_set_powerdown_mode, +}; + +// vref mode +static const char *const mcp4728_vref_modes[] =3D { +=C2=A0=C2=A0=C2=A0 "vdd_ext", +=C2=A0=C2=A0=C2=A0 "internal", +}; + +static int mcp4728_get_vref_mode(struct iio_dev *indio_dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0const struct iio_chan_spec *chan) +{ +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); + +=C2=A0=C2=A0=C2=A0 return data->channel_data[chan->channel].ref_mode; +} + +static int mcp4728_set_vref_mode(struct iio_dev *indio_dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0const struct iio_chan_spec *chan, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0unsigned int mode) +{ +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); +=C2=A0=C2=A0=C2=A0 int ret; + +=C2=A0=C2=A0=C2=A0 data->channel_data[chan->channel].ref_mode =3D mode; + +=C2=A0=C2=A0=C2=A0 if (mode =3D=3D MCP4728_VREF_EXTERNAL_VDD && +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 data->channel_data[chan->channel].g_= mode =3D=3D MCP4728_GAIN_X2) { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 dev_warn( +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 &data->client->de= v, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "CH%d: Gain x2 no= t effective when vref is vdd, force to x1", +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 chan->channel); +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 data->channel_data[chan->channel].g_= mode =3D MCP4728_GAIN_X1; +=C2=A0=C2=A0=C2=A0 } + +=C2=A0=C2=A0=C2=A0 ret =3D mcp4728_program_channel_cfg(chan->channel, indi= o_dev); + +=C2=A0=C2=A0=C2=A0 return ret; +} +static const struct iio_enum mcp4728_vref_mode_enum =3D { +=C2=A0=C2=A0=C2=A0 .items =3D mcp4728_vref_modes, +=C2=A0=C2=A0=C2=A0 .num_items =3D ARRAY_SIZE(mcp4728_vref_modes), +=C2=A0=C2=A0=C2=A0 .get =3D mcp4728_get_vref_mode, +=C2=A0=C2=A0=C2=A0 .set =3D mcp4728_set_vref_mode, +}; + +// gain +static const char *const mcp4728_gain_modes[] =3D { +=C2=A0=C2=A0=C2=A0 "x1", +=C2=A0=C2=A0=C2=A0 "x2", +}; + +static int mcp4728_get_gain_mode(struct iio_dev *indio_dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0const struct iio_chan_spec *chan) +{ +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); + +=C2=A0=C2=A0=C2=A0 return data->channel_data[chan->channel].g_mode; +} + +static int mcp4728_set_gain_mode(struct iio_dev *indio_dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0const struct iio_chan_spec *chan, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0unsigned int mode) +{ +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); +=C2=A0=C2=A0=C2=A0 int ret; + +=C2=A0=C2=A0=C2=A0 if (mode =3D=3D MCP4728_GAIN_X2 && +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 data->channel_data[chan->channel].re= f_mode =3D=3D +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 MCP4728_VREF_EXTE= RNAL_VDD) { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 dev_err(&data->client->dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "CH%d: Gain x2 no= t effective when vref is vdd", +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 chan->channel); +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return -EINVAL; +=C2=A0=C2=A0=C2=A0 } + +=C2=A0=C2=A0=C2=A0 data->channel_data[chan->channel].g_mode =3D mode; + +=C2=A0=C2=A0=C2=A0 ret =3D mcp4728_program_channel_cfg(chan->channel, indi= o_dev); + +=C2=A0=C2=A0=C2=A0 return ret; +} + +static const struct iio_enum mcp4728_gain_mode_enum =3D { +=C2=A0=C2=A0=C2=A0 .items =3D mcp4728_gain_modes, +=C2=A0=C2=A0=C2=A0 .num_items =3D ARRAY_SIZE(mcp4728_gain_modes), +=C2=A0=C2=A0=C2=A0 .get =3D mcp4728_get_gain_mode, +=C2=A0=C2=A0=C2=A0 .set =3D mcp4728_set_gain_mode, +}; + +static const struct iio_chan_spec_ext_info mcp4728_ext_info[] =3D { +=C2=A0=C2=A0=C2=A0 { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 .name =3D "powerdown", +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 .read =3D mcp4728_read_powerdown, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 .write =3D mcp4728_write_powerdown, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 .shared =3D IIO_SEPARATE, +=C2=A0=C2=A0=C2=A0 }, +=C2=A0=C2=A0=C2=A0 IIO_ENUM("powerdown_mode", IIO_SEPARATE, &mcp4728_power= down_mode_enum), +=C2=A0=C2=A0=C2=A0 IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 &mcp= 4728_powerdown_mode_enum), +=C2=A0=C2=A0=C2=A0 IIO_ENUM("vref_mode", IIO_SEPARATE, &mcp4728_vref_mode_= enum), +=C2=A0=C2=A0=C2=A0 IIO_ENUM_AVAILABLE("vref_mode", IIO_SHARED_BY_TYPE, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 &mcp= 4728_vref_mode_enum), +=C2=A0=C2=A0=C2=A0 IIO_ENUM("gain_mode", IIO_SEPARATE, &mcp4728_gain_mode_= enum), +=C2=A0=C2=A0=C2=A0 IIO_ENUM_AVAILABLE("gain_mode", IIO_SHARED_BY_TYPE, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0 &mcp= 4728_gain_mode_enum), +=C2=A0=C2=A0=C2=A0 {}, +}; + +static const struct iio_chan_spec mcp4728_channels[MCP4728_N_CHANNELS] =3D= { +=C2=A0=C2=A0=C2=A0 MCP4728_CHAN(0), +=C2=A0=C2=A0=C2=A0 MCP4728_CHAN(1), +=C2=A0=C2=A0=C2=A0 MCP4728_CHAN(2), +=C2=A0=C2=A0=C2=A0 MCP4728_CHAN(3), +}; + +static int mcp4728_full_scale_mV(u32 *full_scale_mV, int channel, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0struct mcp4728_data *data) +{ +=C2=A0=C2=A0=C2=A0 int ret; + +=C2=A0=C2=A0=C2=A0 if (data->channel_data[channel].ref_mode =3D=3D MCP4728= _VREF_EXTERNAL_VDD) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ret =3D regulator_get_voltage(data->= vdd_reg); +=C2=A0=C2=A0=C2=A0 else +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ret =3D 2048000; + +=C2=A0=C2=A0=C2=A0 if (ret < 0) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return ret; + +=C2=A0=C2=A0=C2=A0 if (ret =3D=3D 0) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return -EINVAL; + +=C2=A0=C2=A0=C2=A0 *full_scale_mV =3D ret / 1000; +=C2=A0=C2=A0=C2=A0 return 0; +} + +static u32 mcp4728_raw_to_mV(u32 raw, int channel, struct mcp4728_data *da= ta) +{ +=C2=A0=C2=A0=C2=A0 int ret; +=C2=A0=C2=A0=C2=A0 u32 full_scale_mV; + +=C2=A0=C2=A0=C2=A0 ret =3D mcp4728_full_scale_mV(&full_scale_mV, channel, = data); +=C2=A0=C2=A0=C2=A0 if (ret) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return ret; + +=C2=A0=C2=A0=C2=A0 return (((raw + 1) * full_scale_mV) >> MCP4728_RESOLUTI= ON); +} + +static int mcp4728_read_raw(struct iio_dev *indio_dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 struct iio_chan_spec const *chan, int *val, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 int *val2, long mask) +{ +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); +=C2=A0=C2=A0=C2=A0 int ret; + +=C2=A0=C2=A0=C2=A0 switch (mask) { +=C2=A0=C2=A0=C2=A0 case IIO_CHAN_INFO_RAW: +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 *val =3D data->channel_data[chan->ch= annel].dac_value; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return IIO_VAL_INT; +=C2=A0=C2=A0=C2=A0 case IIO_CHAN_INFO_SCALE: +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 if (data->channel_data[chan->channel= ].ref_mode =3D=3D +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 MCP4728_VREF_EXTE= RNAL_VDD) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ret =3D regulator= _get_voltage(data->vdd_reg); +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 else +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ret =3D 2048000; + +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 if (ret < 0) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return ret; + +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 *val =3D ret / 1000; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 *val2 =3D MCP4728_RESOLUTION; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return IIO_VAL_FRACTIONAL_LOG2; +=C2=A0=C2=A0=C2=A0 } +=C2=A0=C2=A0=C2=A0 return -EINVAL; +} + +static int mcp4728_write_raw(struct iio_dev *indio_dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0=C2=A0 struct iio_chan_spec const *chan, int val, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0=C2=A0 int val2, long mask) +{ +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); +=C2=A0=C2=A0=C2=A0 int ret; + +=C2=A0=C2=A0=C2=A0 switch (mask) { +=C2=A0=C2=A0=C2=A0 case IIO_CHAN_INFO_RAW: +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 if (val < 0 || val > GENMASK(MCP4728= _RESOLUTION - 1, 0)) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return -EINVAL; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 data->channel_data[chan->channel].da= c_value =3D val; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ret =3D mcp4728_program_channel_cfg(= chan->channel, indio_dev); +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 break; +=C2=A0=C2=A0=C2=A0 default: +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ret =3D -EINVAL; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 break; +=C2=A0=C2=A0=C2=A0 } + +=C2=A0=C2=A0=C2=A0 return ret; +} + +static const struct iio_info mcp4728_info =3D { +=C2=A0=C2=A0=C2=A0 .read_raw =3D mcp4728_read_raw, +=C2=A0=C2=A0=C2=A0 .write_raw =3D mcp4728_write_raw, +=C2=A0=C2=A0=C2=A0 .attrs =3D &mcp4728_attribute_group, +}; + +static int mcp4728_suspend(struct device *dev) +{ +=C2=A0=C2=A0=C2=A0 struct iio_dev *indio_dev =3D i2c_get_clientdata(to_i2c= _client(dev)); +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); +=C2=A0=C2=A0=C2=A0 int err =3D 0; +=C2=A0=C2=A0=C2=A0 unsigned int i; + +=C2=A0=C2=A0=C2=A0 data->powerdown =3D true; + +=C2=A0=C2=A0=C2=A0 for (i =3D 0; i < MCP4728_N_CHANNELS; i++) { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 int ret =3D mcp4728_program_channel_= cfg(i, indio_dev); + +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 if (ret) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 err =3D ret; //sa= ve last error +=C2=A0=C2=A0=C2=A0 } +=C2=A0=C2=A0=C2=A0 return err; +} + +static int mcp4728_resume(struct device *dev) +{ +=C2=A0=C2=A0=C2=A0 struct iio_dev *indio_dev =3D i2c_get_clientdata(to_i2c= _client(dev)); +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); +=C2=A0=C2=A0=C2=A0 int err =3D 0; +=C2=A0=C2=A0=C2=A0 unsigned int i; + +=C2=A0=C2=A0=C2=A0 data->powerdown =3D false; + +=C2=A0=C2=A0=C2=A0 for (i =3D 0; i < MCP4728_N_CHANNELS; i++) { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 int ret =3D mcp4728_program_channel_= cfg(i, indio_dev); + +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 if (ret) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 err =3D ret; //sa= ve last error +=C2=A0=C2=A0=C2=A0 } +=C2=A0=C2=A0=C2=A0 return err; +} +static DEFINE_SIMPLE_DEV_PM_OPS(mcp4728_pm_ops, mcp4728_suspend, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 mcp4728_resume); + +static int mcp4728_init_channels_data(struct mcp4728_data *data) +{ +=C2=A0=C2=A0=C2=A0 u8 inbuf[MCP4728_READ_RESPONSE_LEN]; +=C2=A0=C2=A0=C2=A0 int ret; +=C2=A0=C2=A0=C2=A0 unsigned int i; + +=C2=A0=C2=A0=C2=A0 ret =3D i2c_master_recv(data->client, inbuf, MCP4728_RE= AD_RESPONSE_LEN); +=C2=A0=C2=A0=C2=A0 if (ret < 0) { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 dev_err(&data->client->dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "failed to read m= cp4728 conf. Err=3D%d\n", ret); +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return ret; +=C2=A0=C2=A0=C2=A0 } else if (ret !=3D MCP4728_READ_RESPONSE_LEN) { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 dev_err(&data->client->dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "failed to read m= cp4728 conf. Wrong Response Len ret=3D%d\n", +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ret); +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return -EIO; +=C2=A0=C2=A0=C2=A0 } + +=C2=A0=C2=A0=C2=A0 for (i =3D 0; i < MCP4728_N_CHANNELS; i++) { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 struct mcp4728_channel_data *ch =3D = &(data->channel_data[i]); +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 u8 r2 =3D inbuf[i * 6 + 1]; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 u8 r3 =3D inbuf[i * 6 + 2]; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 u32 dac_mv; + +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ch->dac_value =3D (r2 & 0x0F) << 8 |= r3; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ch->ref_mode =3D (r2 & MCP4728_CMD_V= REF_MASK) >> +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0 MCP4728_CMD_VREF_POS; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ch->pd_mode =3D (r2 & MCP4728_CMD_PD= MODE_MASK) >> +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0 MCP4728_CMD_PDMODE_POS; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ch->g_mode =3D (r2 & MCP4728_CMD_GAI= N_MASK) >> +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0=C2=A0 MCP4728_CMD_GAIN_POS; +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 if (ch->g_mode =3D=3D MCP4728_GAIN_X= 2 && +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 ch->ref_mode =3D= =3D MCP4728_VREF_EXTERNAL_VDD) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 dev_warn(&data->c= lient->dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0"CH%d: Gain x2 not effective when vref is vdd", +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 =C2=A0i); + +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 dac_mv =3D mcp4728_raw_to_mV(ch->dac= _value, i, data); +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 dev_info(&data->client->dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0"CH%d: Volt= age=3D%dmV VRef=3D%d PowerDown=3D%d Gain=3D%d\n", i, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0dac_mv, ch-= >ref_mode, ch->pd_mode, ch->g_mode); +=C2=A0=C2=A0=C2=A0 } + +=C2=A0=C2=A0=C2=A0 return 0; +} + +static int mcp4728_probe(struct i2c_client *client, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0const struc= t i2c_device_id *id) +{ +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data; +=C2=A0=C2=A0=C2=A0 struct iio_dev *indio_dev; +=C2=A0=C2=A0=C2=A0 int err; + +=C2=A0=C2=A0=C2=A0 indio_dev =3D devm_iio_device_alloc(&client->dev, sizeo= f(*data)); +=C2=A0=C2=A0=C2=A0 if (indio_dev =3D=3D NULL) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return -ENOMEM; +=C2=A0=C2=A0=C2=A0 data =3D iio_priv(indio_dev); +=C2=A0=C2=A0=C2=A0 i2c_set_clientdata(client, indio_dev); +=C2=A0=C2=A0=C2=A0 data->client =3D client; +=C2=A0=C2=A0=C2=A0 if (dev_fwnode(&client->dev)) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 data->id =3D (uintptr_t)device_get_m= atch_data(&client->dev); +=C2=A0=C2=A0=C2=A0 else +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 data->id =3D id->driver_data; + +=C2=A0=C2=A0=C2=A0 data->vdd_reg =3D devm_regulator_get(&client->dev, "vdd= "); +=C2=A0=C2=A0=C2=A0 if (IS_ERR(data->vdd_reg)) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 return PTR_ERR(data->vdd_reg); + +=C2=A0=C2=A0=C2=A0 err =3D regulator_enable(data->vdd_reg); +=C2=A0=C2=A0=C2=A0 if (err) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 goto err_disable_vdd_reg; + +=C2=A0=C2=A0=C2=A0 err =3D mcp4728_init_channels_data(data); +=C2=A0=C2=A0=C2=A0 if (err) { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 dev_err(&client->dev, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 "failed to read m= cp4728 current configuration\n"); +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 goto err_disable_vdd_reg; +=C2=A0=C2=A0=C2=A0 } + +=C2=A0=C2=A0=C2=A0 indio_dev->name =3D id->name; +=C2=A0=C2=A0=C2=A0 indio_dev->info =3D &mcp4728_info; +=C2=A0=C2=A0=C2=A0 indio_dev->channels =3D mcp4728_channels; +=C2=A0=C2=A0=C2=A0 indio_dev->num_channels =3D MCP4728_N_CHANNELS; +=C2=A0=C2=A0=C2=A0 indio_dev->modes =3D INDIO_DIRECT_MODE; + +=C2=A0=C2=A0=C2=A0 err =3D iio_device_register(indio_dev); +=C2=A0=C2=A0=C2=A0 if (err) +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 goto err_disable_vdd_reg; + +=C2=A0=C2=A0=C2=A0 return 0; + +err_disable_vdd_reg: +=C2=A0=C2=A0=C2=A0 regulator_disable(data->vdd_reg); + +=C2=A0=C2=A0=C2=A0 return err; +} + +static int mcp4728_remove(struct i2c_client *client) +{ +=C2=A0=C2=A0=C2=A0 struct iio_dev *indio_dev =3D i2c_get_clientdata(client= ); +=C2=A0=C2=A0=C2=A0 struct mcp4728_data *data =3D iio_priv(indio_dev); + +=C2=A0=C2=A0=C2=A0 iio_device_unregister(indio_dev); +=C2=A0=C2=A0=C2=A0 regulator_disable(data->vdd_reg); +=C2=A0=C2=A0=C2=A0 return 0; +} + +static const struct i2c_device_id mcp4728_id[] =3D { { "mcp4728", MCP4728 = }, {} }; +MODULE_DEVICE_TABLE(i2c, mcp4728_id); + +static const struct of_device_id mcp4728_of_match[] =3D { +=C2=A0=C2=A0=C2=A0 { .compatible =3D "microchip,mcp4728", .data =3D (void = *)MCP4728 }, +=C2=A0=C2=A0=C2=A0 {} +}; +MODULE_DEVICE_TABLE(of, mcp4728_of_match); + +static struct i2c_driver mcp4728_driver =3D { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 .driver =3D { +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 .name =3D MCP4728_DRV_NAME, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 .of_match_table =3D mcp4728_of_match, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2= =A0 .pm =3D pm_sleep_ptr(&mcp4728_pm_ops), +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 }, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 .probe =3D mcp4728_probe, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 .remove =3D mcp4728_remove, +=C2=A0=C2=A0=C2=A0 =C2=A0=C2=A0=C2=A0 .id_table =3D mcp4728_id, +}; +module_i2c_driver(mcp4728_driver); + +MODULE_AUTHOR("Andrea Collamati "); +MODULE_DESCRIPTION("MCP4728 12-bit DAC"); +MODULE_LICENSE("GPL"); --=20 2.17.1