From nobody Tue Oct 7 13:14:31 2025 Received: from mail-wm1-f47.google.com (mail-wm1-f47.google.com [209.85.128.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E7E16221FDE; Wed, 9 Jul 2025 16:48:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752079719; cv=none; b=ED/hXz2kUeCJJT+0ElzUl06YVqGputsH4jPvJvMb/nCd9N5icGqO2eh01NQ8yvA/8l8flnQvA8cNwey/vFdVt/5TREQIGqAGyTDOscxEia5w02ARW9rAYzfGBaV1LzzQK2DmwbpiUMUcxEAkmucDbYTjtOcDk9pYiKuCJfqmQ1k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752079719; c=relaxed/simple; bh=4B3ke6gHsIh+dcBfkcHV8Tx4Nx1vxSrvf5z8cq2y0Cg=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=Ns4HX5GJqkfWJV/+GpWbxt33b1acM5RjMcp4mxGTCAJJ8G5O3w0l6auzcRQGsz07nip1YXT5fnbzaZvkHusQxW5FuqVWPhslhbU0JnFDnd68prvdLs3feC+X8xHuanLEGPPOrep21lFSq9UNmQiQ0lUayIr7F07KD3omLJ6O2Ao= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=VNm8gdbL; arc=none smtp.client-ip=209.85.128.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="VNm8gdbL" Received: by mail-wm1-f47.google.com with SMTP id 5b1f17b1804b1-450ce3a2dd5so603855e9.3; Wed, 09 Jul 2025 09:48:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1752079716; x=1752684516; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=UM1sQgyzQVuS7DSOaoE99H993YRDLfxU8AAzZJx9mZI=; b=VNm8gdbL3bNCG6UpcaCqA2MZJReZMVU6MTg6LM3xGoFVoYeToIT5JMhTa9KR/V1AjV qIiLgToiq+rZTdeNDs4JnbrEmgEP63av+o3OQChs64pXpXRyvPZi9KykqltbNkfleVgV dbQsd0ZYMypxjkOosBDTJDX3ru5xzPqEa55zayBMkqMHq7RVwDWISOx4VU4Qc0b7tyXc i1rz2Beq1XWlTF348CyogLon2Tm6QvxofkIIeT15pT+M//rgtnKiR/FsSeYO4Vzb3XLN zmb/x+0uXlI7Glk50RSVF3WwXZ8pvH/RiM0+0VQeiT6VK1Y8QD7T3sUxkHmULRUkQCbq fS3Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752079716; x=1752684516; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=UM1sQgyzQVuS7DSOaoE99H993YRDLfxU8AAzZJx9mZI=; b=CSXIOiJaHO+2t9fl6xJ/IFDo6q8pKpvH836NIbgUfKv8IYISbeXkEk/mCILX3yYJnW c/sT1YhaUPQxsiJLz/EM5mtnjZlgU719wAa0RhkcioZoDHpTVgj0EdE+/KyiGfgfUTrx 5ffGxDhw710NxpRmas6pyUkvF7wtCN7kkBxYxVVQtg+ouOP9VV3Cn2XoYn6G8hC3McgI 2A4Siqx/82s1VkbKMf5bS/7KWMkosA0jxyOxuG1lvoN8/8jnSKeA0GAd828wsf7lP8OV 38GXeEQKmBvwd28Cpau7CR0CKkTreLUZ9z17yzI4/Fg8XXVUgaJmZW/6gI9L7LNyYBaD tYPg== X-Forwarded-Encrypted: i=1; AJvYcCU6HcvkaVIMh3PxHOnR0lgt0g3j9PNVYl4ywcHfDjTMwHIjn3sJpb5U12ei1jhc+VI/wKI1Jmreczgk@vger.kernel.org, AJvYcCUByUUJSbgm2+92+nOQIIb7XD2UQotOLjkucwKREoBf8a8Dirs52NjHZcx1DyQKYZP1ptQs5swZyo8f@vger.kernel.org, AJvYcCUksHXBFt/PrGFIjpErA9V+QHbY16C/MaLNbioa58uttijlgpS5GmZyRuNcX86o1+Xi14p6C2K8EXa+f0I=@vger.kernel.org, AJvYcCX9f/r0l4UFpoyz32KBAjcuYXn7z1qFVuUrtW3m3mHT1EbXMBHQkf61VlLXZX3jILfo24Ul9BTVeqdeWvSt@vger.kernel.org X-Gm-Message-State: AOJu0YyHHt5q3HLFPR9NtNbGZBJyEgrXwnDEDtfipfc3DBebkN51e76e 5K6MWJGS3/R2JD2ksDdkmeunqe+S/tbyB6gH/670J5RYzJvo7Kk/fTZE X-Gm-Gg: ASbGncvWWSqZjvP1SZhZ4ZtQElTDfYA/T+zA4q/AdEHA00fTTM5k5uDIECu+vmyCjvP OTgTvuFEeWXzsJqCGoVlQQzbQfGC9SoUJDC3vsT3DVqQYoQN+SCd//IL7SAd+r8cAHiskCdNTqE yto85yc5qUmrsS0SNAU95FfHzJHPeZPTSviva+4PzDfg/xXw5huk/byuvJDzeWa++ysO7eMC9XF 08A8PFG9OVOiCtcvvPDbVZr8Hx503EOPlk590J0G3WeEF4srfPwf1Ur+EF+j1+5m0WBMx3DKpS1 tyZqjlIywAqedt5Kdk4qCzvCprU7vx5YXlxZN1g45HkIwfvzdlYg8M22Kis9L+ijkqPrj7shy0D 3kJAntTAtHlWU0fI8CWTJAiAkLbytXQATO9Q0U1l7eyT3cuRVpXaR+YdwCLUY7JI= X-Google-Smtp-Source: AGHT+IEu+HMu6WZv/81rFvILzebsCFW6MqzVp/YWaGe5xSfgMvDm9s0bhVMfD4+35uTgph3vOVk62Q== X-Received: by 2002:a05:600c:1e0d:b0:453:483b:626c with SMTP id 5b1f17b1804b1-454d53bda78mr28493545e9.23.1752079715939; Wed, 09 Jul 2025 09:48:35 -0700 (PDT) Received: from skynet.lan (2a02-9142-4580-2e00-0000-0000-0000-0008.red-2a02-914.customerbaf.ipv6.rima-tde.net. [2a02:9142:4580:2e00::8]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-454d50df125sm30634915e9.19.2025.07.09.09.48.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Jul 2025 09:48:34 -0700 (PDT) From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= To: jdelvare@suse.com, linux@roeck-us.net, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, corbet@lwn.net, linux-hwmon@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org Cc: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Subject: [PATCH v3 1/3] docs: hwmon: add emc2101.rst to docs Date: Wed, 9 Jul 2025 18:48:27 +0200 Message-Id: <20250709164829.3072944-2-noltari@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250709164829.3072944-1-noltari@gmail.com> References: <20250709164829.3072944-1-noltari@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Add description of emc2101 driver. Signed-off-by: =C3=81lvaro Fern=C3=A1ndez Rojas --- Documentation/hwmon/emc2101.rst | 61 +++++++++++++++++++++++++++++++++ Documentation/hwmon/index.rst | 1 + 2 files changed, 62 insertions(+) create mode 100644 Documentation/hwmon/emc2101.rst v3: drop emc2101-r (same chip, loads config from EEPROM). v2: add emc2101 to index.rst diff --git a/Documentation/hwmon/emc2101.rst b/Documentation/hwmon/emc2101.= rst new file mode 100644 index 000000000000..3a8d61fc9de3 --- /dev/null +++ b/Documentation/hwmon/emc2101.rst @@ -0,0 +1,61 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver emc2101 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Supported chips: + Microchip EMC2101 + + Addresses scanned: I2C 0x4c + + Prefix: 'emc2101' + + Datasheet: Publicly available at the Microchip website : + https://www.microchip.com/en-us/product/EMC2101 + +Description: +------------ +This driver implements support for Microchip EMC2101 RPM-based PWM Fan Con= troller. +The EMC2101 Fan Controller supports up to 1 controlled PWM fan based on an +external temperature diode. +Fan rotation speed is reported in RPM. +The driver supports the eight entries temperature look up table to automat= ically +adjust the fan speed. + +The driver provides the following sysfs interfaces in hwmon subsystem: + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D +fan1_div RW file for fan target duty cycle divider (0..25= 5) +fan1_input RO file for TACH1 input (in RPM) +fan1_min RW file for TACH1 min RPM +fan1_min_alarm RO file for TACH1 min RPM alarm indication +fan1_spin_up_abort RW file for fan spin up abort on low RPM +fan1_spin_up_power RW file for fan spin up power (in percentage) +fan1_spin_up_time RW file for fan spin up time (in ms) +fan1_standby RW file for fan standby mode +pwm1 RW file for fan target duty cycle (0..63) +pwm1_auto_channels_temp RW file for fan temperature sensor (external, fo= rce) +pwm1_auto_point[1-8]_pwm RW files for look up table fan speed +pwm1_auto_point[1-8]_temp RW files for look up table temperature +pwm1_auto_point_temp_hyst RW file for look up table temperature hysteresis +pwm1_enable RW file for fan config (manual, look up table) +pwm1_freq RW file for fan target frequency +pwm1_mode RW file for pwm mode (DAC, PWM) +pwm1_polarity_invert RW file for fan polarity inversion +temp[1-3]_label RO files for temperature labels +temp1_input RO file for internal temperature +temp1_max RW file for max internal temperature +temp1_max_alarm RO file for max internal temperature alarm indic= ation +temp2_crit RW file for crit external temperature +temp2_crit_alarm RO file for crit external temperature alarm indi= cation +temp2_crit_hyst RW file for crit external temperature hysteresis +temp2_fault RO file for external temperature failure indicat= ion +temp2_input RO file for external temperature +temp2_max RW file for max external temperature +temp2_max_alarm RO file for max external temperature alarm indic= ation +temp2_min RW file for min external temperature +temp2_min_alarm RO file for min external temperature alarm indic= ation +temp2_type RW file for external temperature type (CPU, 2N39= 04) +temp3 RW file for forced temperature +update_interval RW file for temperature sensor update interval +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index b45bfb4ebf30..c068462764d0 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -69,6 +69,7 @@ Hardware Monitoring Kernel Drivers ds1621 ds620 emc1403 + emc2101 emc2103 emc2305 emc6w201 --=20 2.39.5 From nobody Tue Oct 7 13:14:31 2025 Received: from mail-wm1-f45.google.com (mail-wm1-f45.google.com [209.85.128.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0C992223338; Wed, 9 Jul 2025 16:48:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752079721; cv=none; b=DdrTjiitaDlK2iFotqfGPf5VEotlV7U0g+2KylbnnGwykX47vRDTX1nbyQSeVY357Z6g9UieSk2mOl/nAWMQg/vPOZHWtl1NNE17uZYX6VxRMgosGfPU+kuifywotzFaW701TfYqBPg9GVGPsN5uTFBqLKl5CRVyGny4Pwdj3YM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752079721; c=relaxed/simple; bh=RxNYHRjvRwbxissHn18e450qA3JeC6FShWF2D/8oXRc=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=gXwoDmruWs/cGh4VaXXPPdRj2BwyX+Fau6nrO0VXsEo9KMlet81mJCEVsl6yfOH2f5bKlylG7arMzeiL0yLFA+9Wmf2/tGLdrdoQ1S7H4SZUc/Ej32Ka5pCk11CIiQ6RRK7eXMd89qPSjZB1sxmL5Hjtwf3nB8uivl7pFLvAAnw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=kzo37hbI; arc=none smtp.client-ip=209.85.128.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="kzo37hbI" Received: by mail-wm1-f45.google.com with SMTP id 5b1f17b1804b1-451d54214adso471875e9.3; Wed, 09 Jul 2025 09:48:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1752079718; x=1752684518; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=aW+6pvesmNnQet8sOGIglZFbJFOJ9GWYSbg9Z1nNP3U=; b=kzo37hbI7TwzsHQy/uGC036cd528dgJBkXaFHtlD3av2coC/v1qVCRPPodLbLUtlzo 3l3wJfPdAShhPm7187kwsI7ZOQshzk7eQWd2yb3YAZfdrAfCTt8qZyP8EBCefiF/Oh4U t2PtjjhnAeBqMXwS744IHrQuTPDU0YS4SupvAq6qd/I284BfL4mLJCM9RZO8Zu8TShGz moTnHFm6n/FZIG4IKg/KgMIQ/3+/I6Me8cscxD70fHxYganxCYCS91MTJO52T9uanZOY e2za6uENwlG1pAjiw9Ue03dDOCjClNc+Y8m3HQD8A2Zy9BcTcItFDeAv3OVmX7gr1l53 TlCw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752079718; x=1752684518; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=aW+6pvesmNnQet8sOGIglZFbJFOJ9GWYSbg9Z1nNP3U=; b=HoeDr9dcqLRQ0ziKHqP/2plj96NE69QrfuJPYgIUmmfuUTrRsplPBSOXYjCIpunoCb CAaOXPmQLLJv6QL1gpMOdSgo6v9kc9QeJAG0FzN2+0KYYWJoqAmpGMa21FHUdJft6zsC xRhWNVhlVXJMaoZksbsy9pSzYODgWD/QJLPtaW31qaNAR8HC/uow4+mNifCk/EsMRFcd hKGo92lEWabu2wg3qfXpdMTb2Dy2oTv7BneNLJjGXEoMrv5aXEACLf0PQSTJt3et9HML jqrXaZ9TlhxwTK7/5hB4Sl2exCa3w6y9QisZMHixP7F1WVPCYedhNT3nvZfJjdk7Q8az V41w== X-Forwarded-Encrypted: i=1; AJvYcCWQ6RsW0KflZbFi3rJRb0lzoju/OcKcOJg8d/DIc349zLdpcCAa4WaERN7SOHbYjr+3vsn9X+0K9bTh@vger.kernel.org, AJvYcCXNS0JhH3GQLzI5nylGpxx3E1m/glDEFEskbnPgzh+LbZNSnLs3UImy7uUU7geIjG/Lmb1Nu9M88dFu7zE=@vger.kernel.org, AJvYcCXWtFHrF06YAsx7Im3eblCBjP8+0ttzbdeCKAwpzM4Qh2ufL4cnRJtIuJJHQOcXxImohqKSPpsI+xeM@vger.kernel.org, AJvYcCXa04e6urGQKxFb2Gl3Q8Wa7BEfZjT/69XGxzdyuMkICajAAUp8HMhPhRSHxH1RXcftQsuWPFILYkgNl4H2@vger.kernel.org X-Gm-Message-State: AOJu0YxOONRoR1bS5YCmVgZ4g2nB6hG3bguzwHA/dgewCoFdV0NqlBXZ y1IP6VPrBNVXFd5D4DefFyTkgUFCs7FKqe0h2BE4qtH2IFGP4FD7MfgR X-Gm-Gg: ASbGncsXSNelqFFqSKQaIQXQ98EwUObCDS3X2TO3ETw+HkRQ2T4ByGLigGzG71DLqTA FHEQ/BWpge54csm+FBw3D3xlcV9frFuXD0RrOCuWBnndgFv/cyK0ORDy9MEjDklnun7VLY0Hh8b osTzUjX2/iUIp5GAhrXAiDNpPBEtLrYEyH0JFcpgBPGFSVkQkL8gOU2Eg8zQB4UeLure7SSSNPt idRY8GWG67RrLOm8xJt2v8nWE7Q+B052dF5zgIZBsvG4HWJcE3l6hhA82q8Ot4KdhDcMoNuJM/w 4orx97392iR2aLtN7f8T+lujho0Nj6Z1899jJ1B8mgflvZ0130xZCHQynVg15VERgfCjAq3ZSq6 E5PlPve2QExRD6oefYceAzo4CnQUitw2ppyDAU7pjU5CpvzwHOsHXtFB9iWQ/y68= X-Google-Smtp-Source: AGHT+IHPW4nSqrMdmS43S2i2mtnBvt8UJAQyIY46BMAdgXyyUAUKk6tePIGQqYWemQlnRednj5ONfQ== X-Received: by 2002:a05:6000:4023:b0:3a4:e387:c0bb with SMTP id ffacd0b85a97d-3b5e455b399mr2535466f8f.59.1752079718115; Wed, 09 Jul 2025 09:48:38 -0700 (PDT) Received: from skynet.lan (2a02-9142-4580-2e00-0000-0000-0000-0008.red-2a02-914.customerbaf.ipv6.rima-tde.net. [2a02:9142:4580:2e00::8]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-454d50df125sm30634915e9.19.2025.07.09.09.48.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Jul 2025 09:48:36 -0700 (PDT) From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= To: jdelvare@suse.com, linux@roeck-us.net, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, corbet@lwn.net, linux-hwmon@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org Cc: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Subject: [PATCH v3 2/3] dt-bindings: hwmon: Add Microchip EMC2101 support Date: Wed, 9 Jul 2025 18:48:28 +0200 Message-Id: <20250709164829.3072944-3-noltari@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250709164829.3072944-1-noltari@gmail.com> References: <20250709164829.3072944-1-noltari@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Introduce yaml schema for Microchip emc2101 pwm fan controller with temperature monitoring. Signed-off-by: =C3=81lvaro Fern=C3=A1ndez Rojas Reviewed-by: Krzysztof Kozlowski --- .../bindings/hwmon/microchip,emc2101.yaml | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/microchip,emc21= 01.yaml v3: fix errors, remove patternProperties and drop emc2101-r. v2: add missing properties. diff --git a/Documentation/devicetree/bindings/hwmon/microchip,emc2101.yaml= b/Documentation/devicetree/bindings/hwmon/microchip,emc2101.yaml new file mode 100644 index 000000000000..10167747f748 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/microchip,emc2101.yaml @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/microchip,emc2101.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip EMC2101 SMBus compliant PWM fan controller + +maintainers: + - =C3=81lvaro Fern=C3=A1ndez Rojas + +description: + Microchip EMC2101 pwm controller which supports up to 1 fan, 1 internal + temperature sensor, 1 external temperature sensor and an 8 entry look + up table to create a programmable temperature response. + +properties: + compatible: + enum: + - microchip,emc2101 + + reg: + maxItems: 1 + + fan: + $ref: fan-common.yaml# + unevaluatedProperties: false + + '#pwm-cells': + const: 2 + description: | + Number of cells in a PWM specifier. + - cell 0: The PWM frequency + - cell 1: The PWM polarity: 0 or PWM_POLARITY_INVERTED + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + fan_controller: fan-controller@4c { + compatible =3D "microchip,emc2101"; + reg =3D <0x4c>; + + #pwm-cells =3D <2>; + + fan { + pwms =3D <&fan_controller 5806 0>; + }; + }; + }; +... --=20 2.39.5 From nobody Tue Oct 7 13:14:31 2025 Received: from mail-wm1-f42.google.com (mail-wm1-f42.google.com [209.85.128.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C6D2C226D1F; Wed, 9 Jul 2025 16:48:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752079726; cv=none; b=YBm6S/L/xBjMvRaP95uHDbThm9/e6Yv0Rfn2E+A8BsTiZd1+UpXfaf3muzSSARqZous3mshWZgrqKvPCivimNUPTKg9N6jawEDy+Jl2QxNbWOTjpgB4Hk8r4JiPNhsp9AtSF05KDvZjTuMCvsO14DQmf0NKTaK76L9mnAkTagJg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752079726; c=relaxed/simple; bh=7U5vf5d4/Zr9JNXcLnZIm1RTXbjFBjrAe7/FtxJPWtY=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=n6Tlz7tSDJOdDMVpa3oXPC6SYlt1FscfiWUFkqX4fJ+I7Foej5PA5FA3wVekXY6JIn9A5XaDT0sC80wFw6gfmc/hvwV/1L/gJlw6xFesNfy6QbX127vF8B/Z7QZn5CbzOxeZ+xKFcFltd4c+60+PxJZ5WmFbhcvBmjnB8kFh5fs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=UguBYVD7; arc=none smtp.client-ip=209.85.128.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="UguBYVD7" Received: by mail-wm1-f42.google.com with SMTP id 5b1f17b1804b1-450ce671a08so368555e9.3; Wed, 09 Jul 2025 09:48:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1752079721; x=1752684521; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=k/VC6bFb6mm7Qpd0PQrSZpkcoTwbj8idx77T0g6SdXw=; b=UguBYVD7EyOldg1Q+AazPqnC5HtjUJOAoZ2iH/E8/jjePUc7Sjnzt6WzhFuPf7Ku5G i8nimquOxIV2qT1eOdGRVEKrL6SY9lBhw8sz5H8t0mW2w5JGqoXGcEJFuGzad8mGWa/G I+OvKxJt4DzafiowdtkVbQcLGriBxklm9eOFvnNdWFcRjeAp0ICKqxt6qcKbY8TEl7ig k6OrIJUcljpsoMie+bw/tyy7eICnK0s6lC8nIYIlHRy56SL54+7EPOy+W/U3nJK4xAa6 GY+9abT/2ICQOVC+r/oo0a5j0IXyDX0l6WzKm9CfaqlVwWhB5M8iK9/VojspYBWLlFrY zGjg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752079721; x=1752684521; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=k/VC6bFb6mm7Qpd0PQrSZpkcoTwbj8idx77T0g6SdXw=; b=i1wVEYTtymjQlnbnQdQ+pRHCepiSd8BsFmF/GgZBPDdu7fKIrqtyIa7sRZNDB6NGkI DDtY21XAfBypIKi/sLWN0vd2muXlI9Ck/ex8ZmWIsBcJ+r5sRG6KfT1nEEDnPdiwW+AN 5zX8qRLnCpmSCe2FaimETJZojqQTtiwtRhQ9lyOjVGEwLX+MTSrO/PZ4D+5GL+DOjy/d 5wwVGMxvxe1hfnj23iDdsDcXcrJ/pBSvK0CG4NyUPHetiNCfDO1pjpFJIXQe6Utzf9pc 3NRyePqWenjhskq3SfExuHJ7+HyKS2PperwxK3xV1Gl1SZphHRpwA+Z5D7F10nJE5y9K aONw== X-Forwarded-Encrypted: i=1; AJvYcCVYLYL+6YMeD4vF4kakAy9w1CR8CMNF03ufZkjyQLB0ljQsTvcFiGYcyLjJrOg4PM90t2T/Jy/tTFx4@vger.kernel.org, AJvYcCW3IKzBbuWHMMvXZ2l+LDWV+p4Yrj/iJDeo3xQPNN/qa2hLiLgfWuv4PLcH7m6UoqlKHy6znKHaiP3o@vger.kernel.org, AJvYcCWj7zLRN7ALwMdCiFL9E1Fo5aTUEP1KdaSODQmvrN/tQsFf52lENDjDV2Ca8q/FfjglWjOLMOffLk1ySPU=@vger.kernel.org, AJvYcCWnz7cAuqhOphuLI9NSwZZ0U6iCN2nLx7XBZfFHes/u/K+337wbTIBS6ia0e1r4Mi9nVOLD8eckV6H8HLb1@vger.kernel.org X-Gm-Message-State: AOJu0Yzo18lEjX1k3Jwdqhh/2H5uo18l6Ngo442QXJ6gkcF4cha+HJ8q dXK35lBOi9xphJM1NUk5DDF2JhiPzxjj+8nluumXGRj/pwNDYb18oIfp X-Gm-Gg: ASbGnct7PlddYfq5Izm8iaeIcKNDeN1fO4T1IdhDqXiREmTf+a3aF/v9+eM3FoIQ5OI tSvmjdza+0ruoiHK9jjye9JPnfFkiD4dugrnpSPmHTc/YJnrn4yuSf03CdPChluNnqHK97J7H3d zRpmSESrM3TQC0o5FsVQB8NDC9rneHk8YJXEV4Ura4zD6vHiqeCdKgV9ZioDWUAlwBcE8z8EcWp Xg/8dju4MB63vIyke5W4BLAGg/loL5Or33ALAH4ng0KQ9Rk2kXOT687DIf0Qbq9OWPxwsiU48AN p1MllAcFQFPLKSz+Sw+rNAPMLiAYxD71vnI+Q3akL2cMC5JqnqLyhPN7OzlP7Wv3Ml7snqb+Ian T0nqH5ZUsQT7P/IASXZeab028RXKXHGmOyzGsWvf3jaitSftJJzeHgVqofypt1d0= X-Google-Smtp-Source: AGHT+IFLFvu2ef9LGGAhow4TyfUlm6ATN7ht6lr23+B+Zua5wNDrXL4qod40eHU+BfDGYhUbA9VG3w== X-Received: by 2002:a05:600c:8b62:b0:450:d568:909b with SMTP id 5b1f17b1804b1-454d5351577mr34916585e9.14.1752079720284; Wed, 09 Jul 2025 09:48:40 -0700 (PDT) Received: from skynet.lan (2a02-9142-4580-2e00-0000-0000-0000-0008.red-2a02-914.customerbaf.ipv6.rima-tde.net. [2a02:9142:4580:2e00::8]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-454d50df125sm30634915e9.19.2025.07.09.09.48.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Jul 2025 09:48:39 -0700 (PDT) From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= To: jdelvare@suse.com, linux@roeck-us.net, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, corbet@lwn.net, linux-hwmon@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org Cc: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Subject: [PATCH v3 3/3] drivers: hwmon: add EMC2101 driver Date: Wed, 9 Jul 2025 18:48:29 +0200 Message-Id: <20250709164829.3072944-4-noltari@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250709164829.3072944-1-noltari@gmail.com> References: <20250709164829.3072944-1-noltari@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable The Microchip EMC2101 is a SMBus 2.0 fan controller with temperature monitoring. It supports up to 1 fan, 1 internal temperature sensor, 1 external temperature sensor and an 8 entry look up table to create a programmable temperature response. Signed-off-by: =C3=81lvaro Fern=C3=A1ndez Rojas --- drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/emc2101.c | 2176 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 2188 insertions(+) create mode 100644 drivers/hwmon/emc2101.c v3: multiple improvements: - Switch to regmap-i2c. - Use regmap fields. - Add Power Management support. - Demote dev_info() to dev_dbg(). - Remove "emc2101-r" i2c_device_id. - Remove "microchip,emc2101-r" of_device_id. - Properly implement standby mode. - Use milliseconds instead of millihertz for update_interval. - Drop mutex except for FAN_LUT_DISABLE and TEMP_EXT_CRIT_UNLOCK. - Fix u16 fraction temperature conversions. - Other code cleanups and refactors. v2: multiple improvements: - Remove FAN_RPM_MIN definition. - Rename FAN_FALSE_READ to FAN_MIN_READ. - pwm_auto_point_temp_hyst_store(): simplify function. - emc2101_fan_min_read(): add missing FAN_MIN_READ condition. - emc2101_fan_min_write(): fix tach_count calculation. - emc2101_init(): fix REG_TACH_MIN value. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 079620dd4286..16bcd560276e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2002,6 +2002,17 @@ config SENSORS_EMC1403 Threshold values can be configured using sysfs. Data from the different diodes are accessible via sysfs. =20 +config SENSORS_EMC2101 + tristate "SMSC EMC2101" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the SMSC EMC2101 + fan controller chips. + + This driver can also be built as a module. If so, the module + will be called emc2101. + config SENSORS_EMC2103 tristate "SMSC EMC2103" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 48e5866c0c9a..70e95096c6f2 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -73,6 +73,7 @@ obj-$(CONFIG_SENSORS_DRIVETEMP) +=3D drivetemp.o obj-$(CONFIG_SENSORS_DS620) +=3D ds620.o obj-$(CONFIG_SENSORS_DS1621) +=3D ds1621.o obj-$(CONFIG_SENSORS_EMC1403) +=3D emc1403.o +obj-$(CONFIG_SENSORS_EMC2101) +=3D emc2101.o obj-$(CONFIG_SENSORS_EMC2103) +=3D emc2103.o obj-$(CONFIG_SENSORS_EMC2305) +=3D emc2305.o obj-$(CONFIG_SENSORS_EMC6W201) +=3D emc6w201.o diff --git a/drivers/hwmon/emc2101.c b/drivers/hwmon/emc2101.c new file mode 100644 index 000000000000..13a7860ef161 --- /dev/null +++ b/drivers/hwmon/emc2101.c @@ -0,0 +1,2176 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Microchip EMC2101 fan controller. + * + * Copyright 2025 =C3=81lvaro Fern=C3=A1ndez Rojas + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REG_TEMP_INT 0x00 +#define REG_TEMP_EXT_HI 0x01 +#define REG_STATUS 0x02 +#define ADC_BUSY BIT(7) +#define TEMP_INT_HIGH BIT(6) +#define EEPROM_ERROR BIT(5) +#define TEMP_EXT_HIGH BIT(4) +#define TEMP_EXT_LOW BIT(3) +#define TEMP_EXT_FAULT BIT(2) +#define TEMP_EXT_CRIT BIT(1) +#define TACH_LOW BIT(0) +#define REG_CONFIG 0x03 +#define ALERT_IRQ_ACK BIT(7) +#define FAN_STANDBY_ENABLE BIT(6) +#define FAN_STANDBY_MODE BIT(5) +#define FAN_MODE_DAC BIT(4) +#define SMBUS_TOUT_DISABLE BIT(3) +#define PIN_FUNC_TACH BIT(2) +#define TEMP_EXT_CRIT_UNLOCK BIT(1) +#define PIN_ASSERT_3_EXC BIT(0) +#define REG_CONV_RATE 0x04 +#define CONV_RATE_SHIFT 0 +#define CONV_RATE_16000 0 +#define CONV_RATE_8000 1 +#define CONV_RATE_4000 2 +#define CONV_RATE_2000 3 +#define CONV_RATE_1000 4 +#define CONV_RATE_500 5 +#define CONV_RATE_250 6 +#define CONV_RATE_125 7 +#define CONV_RATE_62 8 +#define CONV_RATE_31 9 +#define CONV_RATE_MASK 0xf +#define REG_TEMP_INT_MAX 0x05 +#define REG_TEMP_EXT_MAX_HI 0x07 +#define REG_TEMP_EXT_MIN_HI 0x08 +#define REG_TEMP_EXT_FORCE 0x0c +#define REG_ONE_SHOT 0x0f +#define REG_TEMP_EXT_LO 0x10 +#define REG_SCRATCHPAD_1 0x11 +#define REG_SCRATCHPAD_2 0x12 +#define REG_TEMP_EXT_MAX_LO 0x13 +#define REG_TEMP_EXT_MIN_LO 0x14 +#define REG_ALERT_MASK 0x16 +#define IRQ_TEMP_INT_MAX_DISABLE BIT(6) +#define IRQ_TEMP_EXT_MAX_DISABLE BIT(4) +#define IRQ_TEMP_EXT_MIN_DISABLE BIT(3) +#define IRQ_TEMP_EXT_CRIT_DISABLE BIT(1) +#define IRQ_TACH_MIN_DISABLE BIT(0) +#define REG_EXT_IDEALITY 0x17 +#define EXT_IDEALITY_SHIFT 0 +#define EXT_IDEALITY_START 9846 +#define EXT_IDEALITY_STEP 13 +#define EXT_IDEALITY_VAL(x) (EXT_IDEALITY_START + \ + ((x) * EXT_IDEALITY_STEP)) +#define EXT_IDEALITY_MASK 0x3f +#define REG_BETA_COMP 0x18 +#define BETA_COMP_AUTO BIT(3) +#define BETA_COMP_SHIFT 0 +#define BETA_COMP_DISABLE 7 +#define BETA_COMP_2_33 6 +#define BETA_COMP_1_00 5 +#define BETA_COMP_0_43 4 +#define BETA_COMP_0_33 3 +#define BETA_COMP_0_25 2 +#define BETA_COMP_0_18 1 +#define BETA_COMP_0_11 0 +#define BETA_COMP_MASK 0x7 +#define REG_TEMP_EXT_CRIT 0x19 +/* Can only be written once */ +#define REG_TEMP_EXT_CRIT_HYST 0x21 +#define REG_TACH_LO 0x46 +#define REG_TACH_HI 0x47 +#define REG_TACH_MIN_LO 0x48 +#define REG_TACH_MIN_HI 0x49 +#define REG_FAN_CONFIG 0x4a +#define FAN_EXT_TEMP_FORCE BIT(6) +#define FAN_LUT_DISABLE BIT(5) +#define FAN_POL_INV BIT(4) +#define FAN_CLK_SEL BIT(3) +#define FAN_CLK_OVR BIT(2) +#define TACH_FALSE_READ_SHIFT 0 +#define TACH_FALSE_READ_ENABLE 0 +#define TACH_FALSE_READ_DISABLE 3 +#define TACH_FALSE_READ_MASK 0x3 +#define REG_FAN_SPIN 0x4b +#define FAN_SPIN_UP_ABORT BIT(5) +#define FAN_SPIN_UP_POWER_SHIFT 3 +#define FAN_SPIN_UP_POWER_100 3 +#define FAN_SPIN_UP_POWER_75 2 +#define FAN_SPIN_UP_POWER_50 1 +#define FAN_SPIN_UP_POWER_0 0 +#define FAN_SPIN_UP_POWER_MASK 0x3 +#define FAN_SPIN_UP_TIME_SHIFT 0 +#define FAN_SPIN_UP_TIME_3200 7 +#define FAN_SPIN_UP_TIME_1600 6 +#define FAN_SPIN_UP_TIME_800 5 +#define FAN_SPIN_UP_TIME_400 4 +#define FAN_SPIN_UP_TIME_200 3 +#define FAN_SPIN_UP_TIME_100 2 +#define FAN_SPIN_UP_TIME_50 1 +#define FAN_SPIN_UP_TIME_0 0 +#define FAN_SPIN_UP_TIME_MASK 0x7 +#define REG_FAN_SET 0x4c +#define FAN_SET_SHIFT 0 +#define FAN_SET_MASK 0x3f +#define REG_PWM_FREQ 0x4d +#define PWM_FREQ_SHIFT 0 +#define PWM_FREQ_MASK 0x1f +#define REG_PWM_FREQ_DIV 0x4e +#define REG_FAN_LUT_HYST 0x4f +#define FAN_LUT_HYST_SHIFT 0 +#define FAN_LUT_HYST_MASK 0x1f +#define REG_FAN_LUT_TEMP(x) (0x50 + (0x2 * (x))) +/* Write only with FAN_LUT_DISABLE */ +#define FAN_LUT_TEMP_SHIFT 0 +#define FAN_LUT_TEMP_MASK 0x7f +#define REG_FAN_LUT_SPEED(x) (0x51 + (0x2 * (x))) +/* Write only with FAN_LUT_DISABLE */ +#define FAN_LUT_SPEED_SHIFT 0 +#define FAN_LUT_SPEED_MASK 0x3f +#define REG_AVG_FILTER 0xbf +#define FILTER_SHIFT 1 +#define FILTER_L2 3 +#define FILTER_L1 1 +#define FILTER_NONE 0 +#define FILTER_MASK 0x3 +#define ALERT_PIN_TEMP_COMP BIT(0) +#define REG_PRODUCT_ID 0xfd +#define REG_MANUFACTURER_ID 0xfe +#define REG_REVISION 0xff + +#define CLK_FREQ_ALT 1400 +#define CLK_FREQ_BASE 360000 + +#define FAN_LUT_COUNT 8 +#define FAN_LUT_HYST_MIN 0 +#define FAN_LUT_HYST_MAX 31 +#define FAN_MIN_READ 0xffff +#define FAN_RPM_FACTOR 5400000 + +#define MANUFACTURER_ID 0x5d + +#define PWM_MASK 0x3f + +#define TEMP_FAULT_OPEN 0x7f00 +#define TEMP_FAULT_SHORT 0x7fe0 +#define TEMP_LO_FRAC 125 +#define TEMP_LO_SHIFT 5 +#define TEMP_LO_MASK 0x7 + +#define TEMP_MIN -64 +#define TEMP_MAX 127 +#define TEMP_MAX_FRAC 750 + +enum emc2101_auto_channels_temp { + EMC2101_ACT_EXT =3D 2, + EMC2101_ACT_FORCE =3D 3 +}; + +enum emc2101_mode { + EMC2101_MODE_PWM =3D 0, + EMC2101_MODE_DAC =3D 1 +}; + +enum ecm2101_product_id { + EMC2101 =3D 0x16, + EMC2101_R =3D 0x28 +}; + +enum emc2101_pwm { + EMC2101_PWM_MANUAL =3D 1, + EMC2101_PWM_LUT =3D 2 +}; + +enum emc2101_temp_channels { + EMC2101_TC_INT =3D 0, + EMC2101_TC_EXT, + EMC2101_TC_FORCE, + EMC2101_TC_NUM +}; + +enum emc2101_temp_diode { + EMC2101_TD_CPU =3D 1, + EMC2101_TD_2N3904 =3D 2 +}; + +enum emc2101_fields { + /* BETA_COMP */ + F_BETA_COMP, + F_BETA_COMP_AUTO, + + /* CONFIG */ + F_TEMP_EXT_CRIT_UNLOCK, + F_PIN_FUNC_TACH, + F_SMBUS_TOUT_DISABLE, + F_FAN_MODE_DAC, + F_FAN_STBY, + F_STBY_MODE, + + /* CONV_RATE */ + F_CONV_RATE, + + /* EXT_IDEALITY */ + F_EXT_IDEALITY, + + /* FAN_CONFIG */ + F_TACH_FALSE_READ, + F_FAN_CLK_OVR, + F_FAN_CLK_SEL, + F_FAN_POL_INV, + F_FAN_LUT_DISABLE, + F_FAN_EXT_TEMP_FORCE, + + /* FAN_LUT */ + F_FAN_LUT_HYST, + F_FAN_LUT_SPEED_1, + F_FAN_LUT_SPEED_2, + F_FAN_LUT_SPEED_3, + F_FAN_LUT_SPEED_4, + F_FAN_LUT_SPEED_5, + F_FAN_LUT_SPEED_6, + F_FAN_LUT_SPEED_7, + F_FAN_LUT_SPEED_8, + F_FAN_LUT_TEMP_1, + F_FAN_LUT_TEMP_2, + F_FAN_LUT_TEMP_3, + F_FAN_LUT_TEMP_4, + F_FAN_LUT_TEMP_5, + F_FAN_LUT_TEMP_6, + F_FAN_LUT_TEMP_7, + F_FAN_LUT_TEMP_8, + + /* FAN_SET */ + F_FAN_SET, + + /* FAN_SPIN */ + F_SPIN_UP_TIME, + F_SPIN_UP_POWER, + F_SPIN_UP_ABORT, + + /* PWM_FREQ */ + F_PWM_FREQ, + F_PWM_FREQ_DIV, + + /* STATUS */ + F_TACH_LOW_ALARM, + F_TEMP_EXT_CRIT_ALARM, + F_TEMP_EXT_FAULT, + F_TEMP_EXT_LOW_ALARM, + F_TEMP_EXT_HIGH_ALARM, + F_TEMP_INT_HIGH_ALARM, + + /* TEMP_INT */ + F_TEMP_INT, + F_TEMP_INT_MAX, + + /* TEMP_EXT */ + F_TEMP_EXT_CRIT, + F_TEMP_EXT_CRIT_HYST, + + /* TEMP_EXT_FORCE */ + F_TEMP_EXT_FORCE, + + /* sentinel */ + F_MAX_FIELDS +}; + +#define F_FAN_LUT_SPEED(x) (F_FAN_LUT_SPEED_1 + (x)) +#define F_FAN_LUT_TEMP(x) (F_FAN_LUT_TEMP_1 + (x)) + +static const struct reg_field emc2101_reg_fields[] =3D { + /* BETA_COMP */ + [F_BETA_COMP] =3D REG_FIELD(REG_BETA_COMP, 0, 2), + [F_BETA_COMP_AUTO] =3D REG_FIELD(REG_BETA_COMP, 3, 3), + + /* CONFIG */ + [F_TEMP_EXT_CRIT_UNLOCK] =3D REG_FIELD(REG_CONFIG, 1, 1), + [F_PIN_FUNC_TACH] =3D REG_FIELD(REG_CONFIG, 2, 2), + [F_SMBUS_TOUT_DISABLE] =3D REG_FIELD(REG_CONFIG, 3, 3), + [F_FAN_MODE_DAC] =3D REG_FIELD(REG_CONFIG, 4, 4), + [F_FAN_STBY] =3D REG_FIELD(REG_CONFIG, 5, 5), + [F_STBY_MODE] =3D REG_FIELD(REG_CONFIG, 6, 6), + + /* CONV_RATE */ + [F_CONV_RATE] =3D REG_FIELD(REG_CONV_RATE, 0, 3), + + /* EXT_IDEALITY */ + [F_EXT_IDEALITY] =3D REG_FIELD(REG_EXT_IDEALITY, 0, 5), + + /* FAN_CONFIG */ + [F_TACH_FALSE_READ] =3D REG_FIELD(REG_FAN_CONFIG, 0, 1), + [F_FAN_CLK_OVR] =3D REG_FIELD(REG_FAN_CONFIG, 2, 2), + [F_FAN_CLK_SEL] =3D REG_FIELD(REG_FAN_CONFIG, 3, 3), + [F_FAN_POL_INV] =3D REG_FIELD(REG_FAN_CONFIG, 4, 4), + [F_FAN_LUT_DISABLE] =3D REG_FIELD(REG_FAN_CONFIG, 5, 5), + [F_FAN_EXT_TEMP_FORCE] =3D REG_FIELD(REG_FAN_CONFIG, 6, 6), + + /* FAN_LUT */ + [F_FAN_LUT_HYST] =3D REG_FIELD(REG_FAN_LUT_HYST, 0, 4), + [F_FAN_LUT_SPEED_1] =3D REG_FIELD(REG_FAN_LUT_SPEED(0), 0, 5), + [F_FAN_LUT_SPEED_2] =3D REG_FIELD(REG_FAN_LUT_SPEED(1), 0, 5), + [F_FAN_LUT_SPEED_3] =3D REG_FIELD(REG_FAN_LUT_SPEED(2), 0, 5), + [F_FAN_LUT_SPEED_4] =3D REG_FIELD(REG_FAN_LUT_SPEED(3), 0, 5), + [F_FAN_LUT_SPEED_5] =3D REG_FIELD(REG_FAN_LUT_SPEED(4), 0, 5), + [F_FAN_LUT_SPEED_6] =3D REG_FIELD(REG_FAN_LUT_SPEED(5), 0, 5), + [F_FAN_LUT_SPEED_7] =3D REG_FIELD(REG_FAN_LUT_SPEED(6), 0, 5), + [F_FAN_LUT_SPEED_8] =3D REG_FIELD(REG_FAN_LUT_SPEED(7), 0, 5), + [F_FAN_LUT_TEMP_1] =3D REG_FIELD(REG_FAN_LUT_TEMP(0), 0, 6), + [F_FAN_LUT_TEMP_2] =3D REG_FIELD(REG_FAN_LUT_TEMP(1), 0, 6), + [F_FAN_LUT_TEMP_3] =3D REG_FIELD(REG_FAN_LUT_TEMP(2), 0, 6), + [F_FAN_LUT_TEMP_4] =3D REG_FIELD(REG_FAN_LUT_TEMP(3), 0, 6), + [F_FAN_LUT_TEMP_5] =3D REG_FIELD(REG_FAN_LUT_TEMP(4), 0, 6), + [F_FAN_LUT_TEMP_6] =3D REG_FIELD(REG_FAN_LUT_TEMP(5), 0, 6), + [F_FAN_LUT_TEMP_7] =3D REG_FIELD(REG_FAN_LUT_TEMP(6), 0, 6), + [F_FAN_LUT_TEMP_8] =3D REG_FIELD(REG_FAN_LUT_TEMP(7), 0, 6), + + /* FAN_SET */ + [F_FAN_SET] =3D REG_FIELD(REG_FAN_SET, 0, 5), + + /* FAN_SPIN */ + [F_SPIN_UP_TIME] =3D REG_FIELD(REG_FAN_SPIN, 0, 2), + [F_SPIN_UP_POWER] =3D REG_FIELD(REG_FAN_SPIN, 3, 4), + [F_SPIN_UP_ABORT] =3D REG_FIELD(REG_FAN_SPIN, 5, 5), + + /* PWM_FREQ */ + [F_PWM_FREQ] =3D REG_FIELD(REG_PWM_FREQ, 0, 4), + [F_PWM_FREQ_DIV] =3D REG_FIELD(REG_PWM_FREQ_DIV, 0, 7), + + /* STATUS */ + [F_TACH_LOW_ALARM] =3D REG_FIELD(REG_STATUS, 0, 0), + [F_TEMP_EXT_CRIT_ALARM] =3D REG_FIELD(REG_STATUS, 1, 1), + [F_TEMP_EXT_FAULT] =3D REG_FIELD(REG_STATUS, 2, 2), + [F_TEMP_EXT_LOW_ALARM] =3D REG_FIELD(REG_STATUS, 3, 3), + [F_TEMP_EXT_HIGH_ALARM] =3D REG_FIELD(REG_STATUS, 4, 4), + [F_TEMP_INT_HIGH_ALARM] =3D REG_FIELD(REG_STATUS, 6, 6), + + /* TEMP_INT */ + [F_TEMP_INT] =3D REG_FIELD(REG_TEMP_INT, 0, 7), + [F_TEMP_INT_MAX] =3D REG_FIELD(REG_TEMP_INT_MAX, 0, 6), + + /* TEMP_EXT */ + [F_TEMP_EXT_CRIT] =3D REG_FIELD(REG_TEMP_EXT_CRIT, 0, 6), + [F_TEMP_EXT_CRIT_HYST] =3D REG_FIELD(REG_TEMP_EXT_CRIT_HYST, 0, 6), + + /* TEMP_EXT_FORCE */ + [F_TEMP_EXT_FORCE] =3D REG_FIELD(REG_TEMP_EXT_FORCE, 0, 7), +}; + +struct emc2101_data { + struct regmap *regmap; + struct regmap_field *fields[F_MAX_FIELDS]; + struct device *dev; + struct mutex mutex; /* serializes FAN_LUT_DISABLE and TEMP_EXT_CRIT_UNLOC= K */ +}; + +static const u16 emc2101_conv_time[] =3D { + 16000, 8000, 4000, 2000, 1000, 500, 250, 125, 62, 31 +}; + +static const u8 emc2101_fan_spin_up_power[] =3D { + 0, 50, 75, 100 +}; + +static const u16 emc2101_fan_spin_up_time[] =3D { + 0, 50, 100, 200, 400, 800, 1600, 3200 +}; + +static const unsigned int regs_tach[2] =3D {REG_TACH_HI, REG_TACH_LO}; +static const unsigned int regs_tach_min[2] =3D {REG_TACH_MIN_HI, REG_TACH_= MIN_LO}; +static const unsigned int regs_temp_ext[2] =3D {REG_TEMP_EXT_HI, REG_TEMP_= EXT_LO}; +static const unsigned int regs_temp_ext_max[2] =3D {REG_TEMP_EXT_MAX_HI, R= EG_TEMP_EXT_MAX_LO}; +static const unsigned int regs_temp_ext_min[2] =3D {REG_TEMP_EXT_MIN_HI, R= EG_TEMP_EXT_MIN_LO}; + +static inline int emc2101_read_u16(struct emc2101_data *data, const unsign= ed int *regs, u16 *val) +{ + u8 read_seq[2]; + int ret; + + ret =3D regmap_multi_reg_read(data->regmap, regs, read_seq, 2); + if (!ret) { + *val =3D (read_seq[0] & 0xff) << 8; + *val |=3D read_seq[1] & 0xff; + } + + return ret; +} + +static inline int emc2101_write_u16(struct emc2101_data *data, const unsig= ned int *regs, u16 val) +{ + const struct reg_sequence write_seq[2] =3D { + { regs[0], (val >> 8) & 0xff }, + { regs[1], val & 0xff }, + }; + + return regmap_multi_reg_write(data->regmap, write_seq, 2); +} + +static inline u16 emc2101_rpm_to_u16(long rpm) +{ + u16 val; + + if (rpm > 0) + val =3D clamp_val(FAN_RPM_FACTOR / rpm, 1, FAN_MIN_READ); + else + val =3D FAN_MIN_READ; + + return val; +} + +static inline long emc2101_u16_to_rpm(u16 val) +{ + long rpm; + + val =3D clamp_val(val, 1, FAN_MIN_READ); + if (val < FAN_MIN_READ) + rpm =3D FAN_RPM_FACTOR / val; + else + rpm =3D 0; + + return rpm; +} + +static inline u16 emc2101_temp_to_u16(long temp) +{ + s8 val_hi =3D clamp_val(temp / 1000, TEMP_MIN, TEMP_MAX); + long rem =3D temp % 1000; + u8 val_lo; + + if (val_hi =3D=3D TEMP_MIN) + rem =3D 0; + else if (val_hi =3D=3D TEMP_MAX) + rem =3D TEMP_MAX_FRAC; + + if (rem < 0) { + val_hi -=3D 1; + rem =3D (1000 + rem); + } + + rem /=3D TEMP_LO_FRAC; + val_lo =3D (rem & TEMP_LO_MASK) << TEMP_LO_SHIFT; + + return (val_hi << 8) | val_lo; +} + +static inline long emc2101_u16_to_temp(u16 val) +{ + const s8 val_hi =3D (val >> 8) & 0xff; + const u8 val_lo =3D (val >> TEMP_LO_SHIFT) & TEMP_LO_MASK; + long temp; + + temp =3D val_hi * 1000; + temp +=3D val_lo * TEMP_LO_FRAC; + + return temp; +} + +static inline bool emc2101_lut_edit(struct emc2101_data *data, bool *disab= led) +{ + unsigned int lut_disabled; + int ret; + + ret =3D regmap_field_read(data->fields[F_FAN_LUT_DISABLE], &lut_disabled); + if (ret) + return ret; + + *disabled =3D lut_disabled; + + return regmap_field_write(data->fields[F_FAN_LUT_DISABLE], 1); +} + +static inline bool emc2101_lut_restore(struct emc2101_data *data, bool dis= abled) +{ + if (!disabled) + return regmap_field_write(data->fields[F_FAN_LUT_DISABLE], 0); + + return 0; +} + +static int emc2101_lut_hyst_write(struct regmap_field *field, long temp) +{ + const unsigned int val =3D clamp_val(temp / 1000, FAN_LUT_HYST_MIN, FAN_L= UT_HYST_MAX); + + return regmap_field_write(field, val); +} + +static int emc2101_temp_neg_read(struct regmap_field *field, long *temp) +{ + unsigned int val; + int ret; + + ret =3D regmap_field_read(field, &val); + if (!ret) + *temp =3D ((s8) val) * 1000; + + return ret; +} + +static int emc2101_temp_neg_write(struct regmap_field *field, long temp) +{ + const s8 val =3D clamp_val(temp / 1000, TEMP_MIN, TEMP_MAX); + + return regmap_field_write(field, val); +} + +static int emc2101_temp_pos_read(struct regmap_field *field, long *temp) +{ + unsigned int val; + int ret; + + ret =3D regmap_field_read(field, &val); + if (!ret) + *temp =3D val * 1000; + + return ret; +} + +static int emc2101_temp_pos_write(struct regmap_field *field, long temp) +{ + const u8 val =3D clamp_val(temp / 1000, 0, TEMP_MAX); + + return regmap_field_write(field, val); +} + +static int emc2101_temp_frac_read(struct emc2101_data *data, const unsigne= d int *regs, long *temp) +{ + u16 temp_frac; + int ret; + + ret =3D emc2101_read_u16(data, regs, &temp_frac); + if (ret) + return ret; + + switch (temp_frac) { + case TEMP_FAULT_OPEN: + dev_warn(data->dev, "[%02x, %02x]: diode fault (open)", regs[0], regs[1]= ); + return -ENODATA; + case TEMP_FAULT_SHORT: + dev_warn(data->dev, "[%02x, %02x]: diode fault (short)", regs[0], regs[1= ]); + return -ENODATA; + default: + break; + } + + *temp =3D emc2101_u16_to_temp(temp_frac); + + return ret; +} + +static int emc2101_temp_frac_write(struct emc2101_data *data, const unsign= ed int *regs, long temp) +{ + long temp_frac =3D emc2101_temp_to_u16(temp); + + return emc2101_write_u16(data, regs, temp_frac); +} + +static int emc2101_pwm_write(struct regmap_field *field, long pwm) +{ + const unsigned int val =3D clamp_val(pwm, 0, PWM_MASK); + + return regmap_field_write(field, val); +} + +static ssize_t fan_spin_up_abort_show(struct device *dev, struct device_at= tribute *devattr, + char *buf) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int fan_spin_abort; + int ret; + + ret =3D regmap_field_read(data->fields[F_SPIN_UP_ABORT], &fan_spin_abort); + if (ret) + return ret; + + return sprintf(buf, "%u\n", fan_spin_abort); +} + +static ssize_t fan_spin_up_abort_store(struct device *dev, struct device_a= ttribute *devattr, + const char *buf, size_t count) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int fan_spin_abort; + int ret; + + ret =3D kstrtouint(buf, 10, &fan_spin_abort); + if (ret) + return ret; + + switch (fan_spin_abort) { + case 0: + case 1: + ret =3D regmap_field_write(data->fields[F_SPIN_UP_ABORT], fan_spin_abort= ); + break; + default: + ret =3D -EOPNOTSUPP; + break; + } + + return !ret ? count : ret; +} + +static ssize_t fan_spin_up_time_show(struct device *dev, struct device_att= ribute *devattr, + char *buf) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int fan_spin_time; + int ret; + + ret =3D regmap_field_read(data->fields[F_SPIN_UP_TIME], &fan_spin_time); + if (ret) + return ret; + + return sprintf(buf, "%u\n", emc2101_fan_spin_up_time[fan_spin_time]); +} + +static ssize_t fan_spin_up_time_store(struct device *dev, struct device_at= tribute *devattr, + const char *buf, size_t count) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int fan_spin_time, val; + int ret; + + ret =3D kstrtouint(buf, 10, &fan_spin_time); + if (ret) + return ret; + + val =3D find_closest(fan_spin_time, emc2101_fan_spin_up_time, + ARRAY_SIZE(emc2101_fan_spin_up_time)); + + ret =3D regmap_field_write(data->fields[F_SPIN_UP_TIME], val); + + return !ret ? count : ret; +} + +static ssize_t fan_spin_up_power_show(struct device *dev, struct device_at= tribute *devattr, + char *buf) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int fan_spin_power; + int ret; + + ret =3D regmap_field_read(data->fields[F_SPIN_UP_POWER], &fan_spin_power); + if (ret) + return ret; + + return sprintf(buf, "%u\n", emc2101_fan_spin_up_power[fan_spin_power]); +} + +static ssize_t fan_spin_up_power_store(struct device *dev, struct device_a= ttribute *devattr, + const char *buf, size_t count) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int fan_spin_power, val; + int ret; + + ret =3D kstrtouint(buf, 10, &fan_spin_power); + if (ret) + return ret; + + val =3D find_closest(fan_spin_power, emc2101_fan_spin_up_power, + ARRAY_SIZE(emc2101_fan_spin_up_power)); + + ret =3D regmap_field_write(data->fields[F_SPIN_UP_POWER], val); + + return !ret ? count : ret; +} + +static ssize_t fan_standby_show(struct device *dev, struct device_attribut= e *devattr, + char *buf) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int fan_standby; + int ret; + + ret =3D regmap_field_read(data->fields[F_FAN_STBY], &fan_standby); + if (ret) + return ret; + + return sprintf(buf, "%u\n", fan_standby); +} + +static ssize_t fan_standby_store(struct device *dev, struct device_attribu= te *devattr, + const char *buf, size_t count) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int fan_standby; + int ret; + + ret =3D kstrtouint(buf, 10, &fan_standby); + if (ret) + return ret; + + switch (fan_standby) { + case 0: + case 1: + ret =3D regmap_field_write(data->fields[F_FAN_STBY], fan_standby); + break; + default: + ret =3D -EOPNOTSUPP; + break; + } + + return !ret ? count : ret; +} + +static ssize_t pwm_auto_point_pwm_show(struct device *dev, struct device_a= ttribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr =3D to_sensor_dev_attr(devattr); + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int lut_pwm; + int ret; + + ret =3D regmap_field_read(data->fields[F_FAN_LUT_SPEED(attr->index)], &lu= t_pwm); + if (ret) + return ret; + + return sprintf(buf, "%u\n", lut_pwm); +} + +static ssize_t __pwm_auto_point_pwm_store(struct emc2101_data *data, + struct device_attribute *devattr, unsigned int lut_pwm) +{ + struct sensor_device_attribute *attr =3D to_sensor_dev_attr(devattr); + bool lut_disable; + int ret; + + ret =3D emc2101_lut_edit(data, &lut_disable); + if (ret) + return ret; + + ret =3D emc2101_pwm_write(data->fields[F_FAN_LUT_SPEED(attr->index)], lut= _pwm); + if (ret) + return ret; + + return emc2101_lut_restore(data, lut_disable); +} + +static ssize_t pwm_auto_point_pwm_store(struct device *dev, struct device_= attribute *devattr, + const char *buf, size_t count) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int lut_pwm; + int ret; + + ret =3D kstrtouint(buf, 10, &lut_pwm); + if (ret) + return ret; + + mutex_lock(&data->mutex); + ret =3D __pwm_auto_point_pwm_store(data, devattr, lut_pwm); + mutex_unlock(&data->mutex); + + return !ret ? count : ret; +} + +static ssize_t pwm_auto_point_temp_show(struct device *dev, struct device_= attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr =3D to_sensor_dev_attr(devattr); + struct emc2101_data *data =3D dev_get_drvdata(dev); + long lut_temp; + int ret; + + ret =3D emc2101_temp_pos_read(data->fields[F_FAN_LUT_TEMP(attr->index)], = &lut_temp); + if (ret) + return ret; + + return sprintf(buf, "%lu\n", lut_temp); +} + +static ssize_t __pwm_auto_point_temp_store(struct emc2101_data *data, + struct device_attribute *devattr, unsigned int lut_temp) +{ + struct sensor_device_attribute *attr =3D to_sensor_dev_attr(devattr); + bool lut_disable; + unsigned int i; + long cur_temp; + int ret; + + ret =3D emc2101_lut_edit(data, &lut_disable); + if (ret) + return ret; + + for (i =3D 0; i < FAN_LUT_COUNT; i++) { + struct regmap_field *field =3D data->fields[F_FAN_LUT_TEMP(i)]; + + ret =3D emc2101_temp_pos_read(field, &cur_temp); + if (ret) + return ret; + + if (i < attr->index) { + if (cur_temp > lut_temp) + ret =3D emc2101_temp_pos_write(field, lut_temp); + } else if (i > attr->index) { + if (cur_temp < lut_temp) + ret =3D emc2101_temp_pos_write(field, lut_temp); + } else { + ret =3D emc2101_temp_pos_write(field, lut_temp); + } + + if (ret) + return ret; + } + + return emc2101_lut_restore(data, lut_disable); +} + +static ssize_t pwm_auto_point_temp_store(struct device *dev, struct device= _attribute *devattr, + const char *buf, size_t count) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int lut_temp; + int ret; + + ret =3D kstrtouint(buf, 10, &lut_temp); + if (ret) + return ret; + + mutex_lock(&data->mutex); + ret =3D __pwm_auto_point_temp_store(data, devattr, lut_temp); + mutex_unlock(&data->mutex); + + return !ret ? count : ret; +} + +static ssize_t pwm_auto_point_temp_hyst_show(struct device *dev, struct de= vice_attribute *devattr, + char *buf) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + long lut_hyst; + int ret; + + ret =3D emc2101_temp_pos_read(data->fields[F_FAN_LUT_HYST], &lut_hyst); + if (ret) + return ret; + + return sprintf(buf, "%lu\n", lut_hyst); +} + +static ssize_t pwm_auto_point_temp_hyst_store(struct device *dev, struct d= evice_attribute *devattr, + const char *buf, size_t count) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int lut_hyst; + int ret; + + ret =3D kstrtouint(buf, 10, &lut_hyst); + if (ret) + return ret; + + ret =3D emc2101_lut_hyst_write(data->fields[F_FAN_LUT_HYST], lut_hyst); + + return !ret ? count : ret; +} + +static ssize_t pwm_polarity_invert_show(struct device *dev, struct device_= attribute *devattr, + char *buf) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int polarity_inverted; + int ret; + + ret =3D regmap_field_read(data->fields[F_FAN_POL_INV], &polarity_inverted= ); + if (ret) + return ret; + + return sprintf(buf, "%u\n", polarity_inverted); +} + +static ssize_t pwm_polarity_invert_store(struct device *dev, struct device= _attribute *devattr, + const char *buf, size_t count) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int polarity_inverted; + int ret; + + ret =3D kstrtouint(buf, 10, &polarity_inverted); + if (ret) + return ret; + + switch (polarity_inverted) { + case 0: + case 1: + ret =3D regmap_field_write(data->fields[F_FAN_POL_INV], polarity_inverte= d); + break; + default: + ret =3D -EOPNOTSUPP; + break; + } + + return !ret ? count : ret; +} + +static ssize_t temp_external_force_show(struct device *dev, struct device_= attribute *devattr, + char *buf) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + long temp_force; + int ret; + + ret =3D emc2101_temp_neg_read(data->fields[F_TEMP_EXT_FORCE], &temp_force= ); + if (ret) + return ret; + + return sprintf(buf, "%ld\n", temp_force); +} + +static ssize_t temp_external_force_store(struct device *dev, struct device= _attribute *devattr, + const char *buf, size_t count) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + long temp_force; + int ret; + + ret =3D kstrtol(buf, 10, &temp_force); + if (ret) + return ret; + + ret =3D emc2101_temp_neg_write(data->fields[F_TEMP_EXT_FORCE], temp_force= ); + + return !ret ? count : ret; +} + +static ssize_t temp_external_ideality_show(struct device *dev, struct devi= ce_attribute *devattr, + char *buf) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int ext_ideality; + int ret; + + ret =3D regmap_field_read(data->fields[F_EXT_IDEALITY], &ext_ideality); + if (ret) + return ret; + + return sprintf(buf, "%u\n", EXT_IDEALITY_VAL(ext_ideality)); +} + +static ssize_t temp_external_ideality_store(struct device *dev, struct dev= ice_attribute *devattr, + const char *buf, size_t count) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int ext_ideality_factor, val; + int ret; + + ret =3D kstrtouint(buf, 10, &ext_ideality_factor); + if (ret) + return ret; + + ext_ideality_factor =3D clamp_val(ext_ideality_factor, EXT_IDEALITY_START, + EXT_IDEALITY_VAL(EXT_IDEALITY_MASK)); + val =3D (ext_ideality_factor - EXT_IDEALITY_START) / EXT_IDEALITY_STEP; + + ret =3D regmap_field_write(data->fields[F_EXT_IDEALITY], val); + + return !ret ? count : ret; +} + +static SENSOR_DEVICE_ATTR_RW(fan1_spin_up_abort, fan_spin_up_abort, 0); +static SENSOR_DEVICE_ATTR_RW(fan1_spin_up_power, fan_spin_up_power, 0); +static SENSOR_DEVICE_ATTR_RW(fan1_spin_up_time, fan_spin_up_time, 0); +static SENSOR_DEVICE_ATTR_RW(fan1_standby, fan_standby, 0); + +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_pwm, pwm_auto_point_pwm, 0); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, pwm_auto_point_temp, 0= ); + +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_pwm, pwm_auto_point_pwm, 1); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, pwm_auto_point_temp, 1= ); + +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_pwm, pwm_auto_point_pwm, 2); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_auto_point_temp, 2= ); + +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_pwm, pwm_auto_point_pwm, 3); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_auto_point_temp, 3= ); + +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_pwm, pwm_auto_point_pwm, 4); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_auto_point_temp, 4= ); + +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point6_pwm, pwm_auto_point_pwm, 5); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point6_temp, pwm_auto_point_temp, 5= ); + +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point7_pwm, pwm_auto_point_pwm, 6); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point7_temp, pwm_auto_point_temp, 6= ); + +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point8_pwm, pwm_auto_point_pwm, 7); +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point8_temp, pwm_auto_point_temp, 7= ); + +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point_temp_hyst, pwm_auto_point_tem= p_hyst, 0); + +static SENSOR_DEVICE_ATTR_RW(pwm1_polarity_invert, pwm_polarity_invert, 0); + +static SENSOR_DEVICE_ATTR_RW(temp2_external_ideality, temp_external_ideali= ty, 0); + +static SENSOR_DEVICE_ATTR_RW(temp3, temp_external_force, 0); + +static struct attribute *emc2101_hwmon_attributes[] =3D { + &sensor_dev_attr_fan1_spin_up_abort.dev_attr.attr, + &sensor_dev_attr_fan1_spin_up_power.dev_attr.attr, + &sensor_dev_attr_fan1_spin_up_time.dev_attr.attr, + &sensor_dev_attr_fan1_standby.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point7_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point7_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point8_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point8_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_polarity_invert.dev_attr.attr, + &sensor_dev_attr_temp2_external_ideality.dev_attr.attr, + &sensor_dev_attr_temp3.dev_attr.attr, + NULL +}; + +static const struct attribute_group emc2101_hwmon_group =3D { + .attrs =3D emc2101_hwmon_attributes, +}; +__ATTRIBUTE_GROUPS(emc2101_hwmon); + +static int emc2101_chip_update_interval_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int conv_rate; + int ret; + + ret =3D regmap_field_read(data->fields[F_CONV_RATE], &conv_rate); + if (!ret) { + if (conv_rate < ARRAY_SIZE(emc2101_conv_time)) + *val =3D emc2101_conv_time[conv_rate]; + else + *val =3D emc2101_conv_time[CONV_RATE_31]; + } + + return ret; +} + +static int emc2101_chip_update_interval_write(struct device *dev, long val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int conv_rate; + + conv_rate =3D find_closest_descending(val, emc2101_conv_time, ARRAY_SIZE(= emc2101_conv_time)); + + return regmap_field_write(data->fields[F_CONV_RATE], conv_rate); +} + +static int emc2101_fan_div_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int pwm_freq_div; + int ret; + + ret =3D regmap_field_read(data->fields[F_PWM_FREQ_DIV], &pwm_freq_div); + if (!ret) + *val =3D pwm_freq_div; + + return ret; +} + +static int emc2101_fan_div_write(struct device *dev, long val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + struct regmap_field *field =3D data->fields[F_PWM_FREQ_DIV]; + unsigned int pwm_freq_div; + + pwm_freq_div =3D clamp_val(val, 1, 0xff); + + return regmap_field_write(field, pwm_freq_div); +} + +static int emc2101_fan_input_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + u16 tach_count; + int ret; + + ret =3D emc2101_read_u16(data, regs_tach, &tach_count); + if (ret) + return ret; + + *val =3D emc2101_u16_to_rpm(tach_count); + + return 0; +} + +static int emc2101_fan_min_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + u16 tach_count; + int ret; + + ret =3D emc2101_read_u16(data, regs_tach_min, &tach_count); + if (ret) + return ret; + + *val =3D emc2101_u16_to_rpm(tach_count); + + return 0; +} + +static int emc2101_fan_min_write(struct device *dev, long val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + u16 tach_count =3D emc2101_rpm_to_u16(val); + + return emc2101_write_u16(data, regs_tach_min, tach_count); +} + +static int emc2101_fan_min_alarm_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int tach_low; + int ret; + + ret =3D regmap_field_read(data->fields[F_TACH_LOW_ALARM], &tach_low); + if (ret) + return ret; + + *val =3D tach_low; + + return 0; +} + +static int emc2101_pwm_auto_channels_temp_read(struct device *dev, long *v= al) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int temp_ext_force; + int ret; + + ret =3D regmap_field_read(data->fields[F_FAN_EXT_TEMP_FORCE], &temp_ext_f= orce); + if (ret) + return ret; + + *val =3D temp_ext_force ? EMC2101_ACT_FORCE : EMC2101_ACT_EXT; + + return 0; +} + +static int emc2101_pwm_auto_channels_temp_write(struct device *dev, long v= al) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + int ret; + + switch (val) { + case EMC2101_ACT_EXT: + ret =3D regmap_field_write(data->fields[F_FAN_EXT_TEMP_FORCE], 0); + break; + case EMC2101_ACT_FORCE: + ret =3D regmap_field_write(data->fields[F_FAN_EXT_TEMP_FORCE], 1); + break; + default: + ret =3D -EOPNOTSUPP; + break; + } + + return ret; +} + +static int emc2101_pwm_enable_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int lut_disable; + int ret; + + ret =3D regmap_field_read(data->fields[F_FAN_LUT_DISABLE], &lut_disable); + if (ret) + return ret; + + *val =3D lut_disable ? EMC2101_PWM_MANUAL : EMC2101_PWM_LUT; + + return 0; +} + +static int emc2101_pwm_enable_write(struct device *dev, long val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int lut_disable; + int ret; + + switch (val) { + case EMC2101_PWM_MANUAL: + lut_disable =3D 1; + break; + case EMC2101_PWM_LUT: + lut_disable =3D 0; + break; + default: + return -EOPNOTSUPP; + } + + mutex_lock(&data->mutex); + ret =3D regmap_field_write(data->fields[F_FAN_LUT_DISABLE], lut_disable); + mutex_unlock(&data->mutex); + + return ret; +} + +static int emc2101_pwm_freq_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int fan_clk_ovr, fan_clk_sel; + unsigned int pwm_freq, pwm_freq_div; + unsigned int base_clk, div; + int ret; + + ret =3D regmap_field_read(data->fields[F_FAN_CLK_OVR], &fan_clk_ovr); + if (ret) + return ret; + + ret =3D regmap_field_read(data->fields[F_FAN_CLK_SEL], &fan_clk_sel); + if (ret) + return ret; + + ret =3D regmap_field_read(data->fields[F_PWM_FREQ], &pwm_freq); + if (ret) + return ret; + + if (fan_clk_ovr) { + ret =3D regmap_field_read(data->fields[F_PWM_FREQ_DIV], &pwm_freq_div); + if (ret) + return ret; + } else { + pwm_freq_div =3D 1; + } + + if (fan_clk_sel) + base_clk =3D CLK_FREQ_ALT; + else + base_clk =3D CLK_FREQ_BASE; + + div =3D 2 * pwm_freq * pwm_freq_div; + if (div) + *val =3D base_clk / div; + else + *val =3D 0; + + return ret; +} + +static int emc2101_pwm_freq_write(struct device *dev, long val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int fan_clk_ovr, fan_clk_sel; + unsigned int pwm_freq, pwm_freq_div; + unsigned int base_clk; + int ret; + + ret =3D regmap_field_read(data->fields[F_FAN_CLK_OVR], &fan_clk_ovr); + if (ret) + return ret; + + ret =3D regmap_field_read(data->fields[F_FAN_CLK_SEL], &fan_clk_sel); + if (ret) + return ret; + + if (fan_clk_ovr) { + ret =3D regmap_field_read(data->fields[F_PWM_FREQ_DIV], &pwm_freq_div); + if (ret) + return ret; + } else { + pwm_freq_div =3D 1; + } + + if (fan_clk_sel) + base_clk =3D CLK_FREQ_ALT; + else + base_clk =3D CLK_FREQ_BASE; + + pwm_freq =3D base_clk / (2 * pwm_freq_div * val); + + return emc2101_pwm_write(data->fields[F_PWM_FREQ], pwm_freq); +} + +static int emc2101_pwm_input_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int fan_set; + int ret; + + ret =3D regmap_field_read(data->fields[F_FAN_SET], &fan_set); + if (!ret) + *val =3D fan_set; + + return ret; +} + +static int emc2101_pwm_input_write(struct device *dev, long val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + + return emc2101_pwm_write(data->fields[F_FAN_SET], val); +} + +static int emc2101_pwm_mode_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int fan_mode_dac; + int ret; + + ret =3D regmap_field_read(data->fields[F_FAN_MODE_DAC], &fan_mode_dac); + if (ret) + return ret; + + *val =3D fan_mode_dac ? EMC2101_MODE_DAC : EMC2101_MODE_PWM; + + return ret; +} + +static int emc2101_pwm_mode_write(struct device *dev, long val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + int ret; + + switch (val) { + case EMC2101_MODE_DAC: + case EMC2101_MODE_PWM: + ret =3D regmap_field_write(data->fields[F_FAN_MODE_DAC], val); + break; + default: + ret =3D -EOPNOTSUPP; + break; + } + + return ret; +} + +static int emc2101_temp_ext_crit_alarm_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int temp_ext_crit; + int ret; + + ret =3D regmap_field_read(data->fields[F_TEMP_EXT_CRIT_ALARM], &temp_ext_= crit); + if (ret) + return ret; + + *val =3D temp_ext_crit; + + return 0; +} + +static int emc2101_temp_ext_crit_hyst_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + + return emc2101_temp_pos_read(data->fields[F_TEMP_EXT_CRIT_HYST], val); +} + +static int emc2101_temp_ext_crit_hyst_write(struct device *dev, long val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + + return emc2101_temp_pos_write(data->fields[F_TEMP_EXT_CRIT_HYST], val); +} + +static int emc2101_temp_ext_crit_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + + return emc2101_temp_pos_read(data->fields[F_TEMP_EXT_CRIT], val); +} + +static int __emc2101_temp_ext_crit_write(struct emc2101_data *data, long v= al) +{ + unsigned int temp_ext_crit_unlock; + int ret; + + ret =3D regmap_field_read(data->fields[F_TEMP_EXT_CRIT_UNLOCK], &temp_ext= _crit_unlock); + if (ret) + return ret; + + if (temp_ext_crit_unlock) { + dev_err(data->dev, "critical temperature can only be updated once"); + return -EIO; + } + + ret =3D regmap_field_write(data->fields[F_TEMP_EXT_CRIT_UNLOCK], 1); + if (ret) + return ret; + + return emc2101_temp_pos_write(data->fields[F_TEMP_EXT_CRIT], val); +} + +static int emc2101_temp_ext_crit_write(struct device *dev, long val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + int ret; + + mutex_lock(&data->mutex); + ret =3D __emc2101_temp_ext_crit_write(data, val); + mutex_unlock(&data->mutex); + + return ret; +} + +static int emc2101_temp_ext_fault_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int temp_ext_fault; + int ret; + + ret =3D regmap_field_read(data->fields[F_TEMP_EXT_FAULT], &temp_ext_fault= ); + if (ret) + return ret; + + *val =3D temp_ext_fault; + + return 0; +} + +static int emc2101_temp_ext_max_alarm_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int temp_ext_high; + int ret; + + ret =3D regmap_field_read(data->fields[F_TEMP_EXT_HIGH_ALARM], &temp_ext_= high); + if (ret) + return ret; + + *val =3D temp_ext_high; + + return 0; +} + +static int emc2101_temp_ext_max_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + + return emc2101_temp_frac_read(data, regs_temp_ext_max, val); +} + +static int emc2101_temp_ext_max_write(struct device *dev, long val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + + return emc2101_temp_frac_write(data, regs_temp_ext_max, val); +} + +static int emc2101_temp_ext_min_alarm_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int temp_ext_low; + int ret; + + ret =3D regmap_field_read(data->fields[F_TEMP_EXT_LOW_ALARM], &temp_ext_l= ow); + if (ret) + return ret; + + *val =3D temp_ext_low; + + return 0; +} + +static int emc2101_temp_ext_min_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + + return emc2101_temp_frac_read(data, regs_temp_ext_min, val); +} + +static int emc2101_temp_ext_min_write(struct device *dev, long val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + + return emc2101_temp_frac_write(data, regs_temp_ext_min, val); +} + +static int emc2101_temp_ext_type_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int beta_comp, beta_comp_auto; + int ret; + + ret =3D regmap_field_read(data->fields[F_BETA_COMP], &beta_comp); + if (ret) + return ret; + + ret =3D regmap_field_read(data->fields[F_BETA_COMP], &beta_comp_auto); + if (ret) + return ret; + + if (beta_comp =3D=3D BETA_COMP_DISABLE && !beta_comp_auto) + *val =3D EMC2101_TD_2N3904; + else + *val =3D EMC2101_TD_CPU; + + return 0; +} + +static int emc2101_temp_ext_type_write(struct device *dev, long val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int beta_comp, beta_comp_auto; + int ret; + + switch (val) { + case EMC2101_TD_CPU: + beta_comp =3D 0; + beta_comp_auto =3D 1; + break; + case EMC2101_TD_2N3904: + beta_comp =3D BETA_COMP_DISABLE; + beta_comp_auto =3D 0; + break; + default: + return -EOPNOTSUPP; + } + + ret =3D regmap_field_write(data->fields[F_BETA_COMP_AUTO], beta_comp_auto= ); + if (ret) + return ret; + + return regmap_field_write(data->fields[F_BETA_COMP], beta_comp); +} + +static int emc2101_temp_ext_input_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + + return emc2101_temp_frac_read(data, regs_temp_ext, val); +} + +static int emc2101_temp_int_max_alarm_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + unsigned int temp_int_high; + int ret; + + ret =3D regmap_field_read(data->fields[F_TEMP_INT_HIGH_ALARM], &temp_int_= high); + if (ret) + return ret; + + *val =3D temp_int_high; + + return 0; +} + +static int emc2101_temp_int_max_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + + return emc2101_temp_pos_read(data->fields[F_TEMP_INT_MAX], val); +} + +static int emc2101_temp_int_max_write(struct device *dev, long val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + + return emc2101_temp_pos_write(data->fields[F_TEMP_INT_MAX], val); +} + +static int emc2101_temp_int_input_read(struct device *dev, long *val) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + + return emc2101_temp_neg_read(data->fields[F_TEMP_INT], val); +} + +static umode_t emc2101_is_visible(const void *data, enum hwmon_sensor_type= s type, u32 attr, + int channel) +{ + int max_channels; + + if (type =3D=3D hwmon_temp) + max_channels =3D EMC2101_TC_NUM; + else + max_channels =3D 1; + + if (channel >=3D max_channels) + return 0; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return 0644; + default: + break; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_min_alarm: + return 0444; + case hwmon_fan_div: + case hwmon_fan_min: + return 0644; + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_auto_channels_temp: + case hwmon_pwm_enable: + case hwmon_pwm_freq: + case hwmon_pwm_input: + case hwmon_pwm_mode: + return 0644; + default: + break; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_crit_alarm: + case hwmon_temp_fault: + case hwmon_temp_input: + case hwmon_temp_label: + case hwmon_temp_max_alarm: + case hwmon_temp_min_alarm: + return 0444; + case hwmon_temp_crit: + case hwmon_temp_crit_hyst: + case hwmon_temp_max: + case hwmon_temp_min: + case hwmon_temp_type: + return 0644; + default: + break; + } + break; + default: + break; + } + + return 0; +}; + +static int emc2101_read(struct device *dev, enum hwmon_sensor_types type, = u32 attr, int channel, + long *val) +{ + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return emc2101_chip_update_interval_read(dev, val); + default: + break; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_div: + return emc2101_fan_div_read(dev, val); + case hwmon_fan_input: + return emc2101_fan_input_read(dev, val); + case hwmon_fan_min: + return emc2101_fan_min_read(dev, val); + case hwmon_fan_min_alarm: + return emc2101_fan_min_alarm_read(dev, val); + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_auto_channels_temp: + return emc2101_pwm_auto_channels_temp_read(dev, val); + case hwmon_pwm_enable: + return emc2101_pwm_enable_read(dev, val); + case hwmon_pwm_freq: + return emc2101_pwm_freq_read(dev, val); + case hwmon_pwm_input: + return emc2101_pwm_input_read(dev, val); + case hwmon_pwm_mode: + return emc2101_pwm_mode_read(dev, val); + default: + break; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_crit: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_crit_read(dev, val); + default: + break; + } + break; + case hwmon_temp_crit_alarm: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_crit_alarm_read(dev, val); + default: + break; + } + break; + case hwmon_temp_crit_hyst: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_crit_hyst_read(dev, val); + default: + break; + } + break; + case hwmon_temp_fault: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_fault_read(dev, val); + default: + break; + } + break; + case hwmon_temp_input: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_input_read(dev, val); + case EMC2101_TC_INT: + return emc2101_temp_int_input_read(dev, val); + default: + break; + } + break; + case hwmon_temp_max: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_max_read(dev, val); + case EMC2101_TC_INT: + return emc2101_temp_int_max_read(dev, val); + default: + break; + } + break; + case hwmon_temp_max_alarm: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_max_alarm_read(dev, val); + case EMC2101_TC_INT: + return emc2101_temp_int_max_alarm_read(dev, val); + default: + break; + } + break; + case hwmon_temp_min: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_min_read(dev, val); + default: + break; + } + break; + case hwmon_temp_min_alarm: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_min_alarm_read(dev, val); + default: + break; + } + break; + case hwmon_temp_type: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_type_read(dev, val); + default: + break; + } + break; + default: + break; + } + break; + default: + break; + } + + return -EOPNOTSUPP; +}; + +static int emc2101_read_string(struct device *dev, enum hwmon_sensor_types= type, u32 attr, + int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_label: + switch (channel) { + case EMC2101_TC_EXT: + *str =3D "external"; + return 0; + case EMC2101_TC_FORCE: + *str =3D "force"; + return 0; + case EMC2101_TC_INT: + *str =3D "internal"; + return 0; + default: + break; + } + break; + default: + break; + } + break; + default: + break; + } + + return -EOPNOTSUPP; +}; + +static int emc2101_write(struct device *dev, enum hwmon_sensor_types type,= u32 attr, int channel, + long val) +{ + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return emc2101_chip_update_interval_write(dev, val); + default: + break; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_div: + return emc2101_fan_div_write(dev, val); + case hwmon_fan_min: + return emc2101_fan_min_write(dev, val); + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_auto_channels_temp: + return emc2101_pwm_auto_channels_temp_write(dev, val); + case hwmon_pwm_enable: + return emc2101_pwm_enable_write(dev, val); + case hwmon_pwm_freq: + return emc2101_pwm_freq_write(dev, val); + case hwmon_pwm_input: + return emc2101_pwm_input_write(dev, val); + case hwmon_pwm_mode: + return emc2101_pwm_mode_write(dev, val); + default: + break; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_crit: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_crit_write(dev, val); + default: + break; + } + break; + case hwmon_temp_crit_hyst: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_crit_hyst_write(dev, val); + default: + break; + } + break; + case hwmon_temp_max: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_max_write(dev, val); + case EMC2101_TC_INT: + return emc2101_temp_int_max_write(dev, val); + default: + break; + } + break; + case hwmon_temp_min: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_min_write(dev, val); + default: + break; + } + break; + case hwmon_temp_type: + switch (channel) { + case EMC2101_TC_EXT: + return emc2101_temp_ext_type_write(dev, val); + default: + break; + } + break; + default: + break; + } + break; + default: + break; + } + + return -EOPNOTSUPP; +} + +#define EMC2101_CHIP_CFG HWMON_C_UPDATE_INTERVAL +#define EMC2101_FAN_CFG (HWMON_F_DIV |\ + HWMON_F_INPUT |\ + HWMON_F_MIN |\ + HWMON_F_MIN_ALARM) +#define EMC2101_PWM_CFG (HWMON_PWM_AUTO_CHANNELS_TEMP |\ + HWMON_PWM_ENABLE |\ + HWMON_PWM_FREQ |\ + HWMON_PWM_INPUT |\ + HWMON_PWM_MODE) +#define EMC2101_TEMP_INT_CFG (HWMON_T_INPUT |\ + HWMON_T_LABEL |\ + HWMON_T_MAX |\ + HWMON_T_MAX_ALARM) +#define EMC2101_TEMP_EXT_CFG (HWMON_T_CRIT |\ + HWMON_T_CRIT_ALARM |\ + HWMON_T_CRIT_HYST |\ + HWMON_T_FAULT |\ + HWMON_T_INPUT |\ + HWMON_T_LABEL |\ + HWMON_T_MAX |\ + HWMON_T_MAX_ALARM |\ + HWMON_T_MIN |\ + HWMON_T_MIN_ALARM |\ + HWMON_T_TYPE) +#define EMC2101_TEMP_FORCE_CFG HWMON_T_LABEL + +static const struct hwmon_channel_info * const emc2101_info[] =3D { + HWMON_CHANNEL_INFO(chip, EMC2101_CHIP_CFG), + HWMON_CHANNEL_INFO(fan, EMC2101_FAN_CFG), + HWMON_CHANNEL_INFO(pwm, EMC2101_PWM_CFG), + HWMON_CHANNEL_INFO(temp, EMC2101_TEMP_INT_CFG, + EMC2101_TEMP_EXT_CFG, + EMC2101_TEMP_FORCE_CFG), + NULL +}; + +static const struct hwmon_ops emc2101_ops =3D { + .is_visible =3D emc2101_is_visible, + .read =3D emc2101_read, + .read_string =3D emc2101_read_string, + .write =3D emc2101_write, +}; + +static const struct hwmon_chip_info emc2101_chip_info =3D { + .info =3D emc2101_info, + .ops =3D &emc2101_ops, +}; + +static int emc2101_resume(struct device *dev) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + + return regmap_field_write(data->fields[F_STBY_MODE], 0); +} + +static int emc2101_suspend(struct device *dev) +{ + struct emc2101_data *data =3D dev_get_drvdata(dev); + + return regmap_field_write(data->fields[F_STBY_MODE], 1); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(emc2101_pm, emc2101_suspend, emc2101_resum= e); + +static int emc2101_init(struct emc2101_data *data) +{ + static const u8 lut_t[FAN_LUT_COUNT] =3D { 35, 40, 45, 50, 55, = 60, 70, 75}; + static const u8 lut_s[FAN_LUT_COUNT] =3D {0x12, 0x19, 0x1f, 0x25, 0x2c, 0= x32, 0x38, 0x3f}; + unsigned int i; + u16 tach_count; + int ret; + + /* CONFIG */ + ret =3D regmap_field_write(data->fields[F_PIN_FUNC_TACH], 1); + if (ret) + return ret; + ret =3D regmap_field_write(data->fields[F_SMBUS_TOUT_DISABLE], 1); + if (ret) + return ret; + + /* FAN_CONFIG */ + ret =3D regmap_field_write(data->fields[F_TACH_FALSE_READ], TACH_FALSE_RE= AD_DISABLE); + if (ret) + return ret; + ret =3D regmap_field_write(data->fields[F_FAN_CLK_OVR], 1); + if (ret) + return ret; + ret =3D regmap_field_write(data->fields[F_FAN_CLK_SEL], 0); + if (ret) + return ret; + + /* FAN_LUT */ + ret =3D regmap_field_write(data->fields[F_FAN_LUT_DISABLE], 1); + if (ret) + return ret; + for (i =3D 0; i < FAN_LUT_COUNT; i++) { + ret =3D regmap_field_write(data->fields[F_FAN_LUT_TEMP(i)], lut_t[i]); + if (ret) + return ret; + ret =3D regmap_field_write(data->fields[F_FAN_LUT_SPEED(i)], lut_s[i]); + if (ret) + return ret; + } + ret =3D regmap_field_write(data->fields[F_FAN_LUT_DISABLE], 0); + if (ret) + return ret; + + /* PWM_FREQ */ + ret =3D regmap_field_write(data->fields[F_PWM_FREQ], PWM_FREQ_MASK); + if (ret) + return ret; + ret =3D regmap_field_write(data->fields[F_PWM_FREQ_DIV], 1); + if (ret) + return ret; + + /* TACH gives invalid value on first reading */ + return emc2101_read_u16(data, regs_tach, &tach_count); +} + +static bool emc2101_regmap_is_volatile(struct device *dev, unsigned int re= g) +{ + switch (reg) { + case REG_TEMP_INT: /* internal diode */ + case REG_TEMP_EXT_HI: /* external diode high byte */ + case REG_STATUS: /* status */ + case REG_TEMP_EXT_LO: /* external diode low byte */ + case REG_TACH_LO: /* tach input low byte */ + case REG_TACH_HI: /* tach input high byte */ + case REG_FAN_SET: /* fan pwm */ + return true; + default: + return false; + } +} + +static const struct regmap_config emc2101_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 8, + .cache_type =3D REGCACHE_MAPLE, + .volatile_reg =3D emc2101_regmap_is_volatile, +}; + +static int emc2101_probe(struct i2c_client *client) +{ + struct i2c_adapter *adapter =3D client->adapter; + struct device *dev =3D &client->dev; + struct emc2101_data *data; + struct device *hwmon_dev; + unsigned int i; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + data =3D devm_kzalloc(dev, sizeof(struct emc2101_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->regmap =3D devm_regmap_init_i2c(client, &emc2101_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + for (i =3D 0; i < F_MAX_FIELDS; i++) { + data->fields[i] =3D devm_regmap_field_alloc(dev, data->regmap, + emc2101_reg_fields[i]); + if (IS_ERR(data->fields[i])) { + dev_err(dev, "Unable to allocate regmap fields\n"); + return PTR_ERR(data->fields[i]); + } + } + + data->dev =3D dev; + mutex_init(&data->mutex); + + hwmon_dev =3D devm_hwmon_device_register_with_info(dev, client->name, dat= a, + &emc2101_chip_info, + emc2101_hwmon_groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + dev_dbg(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name); + + return emc2101_init(data); +} + +static int emc2101_detect(struct i2c_client *client, struct i2c_board_info= *info) +{ + struct i2c_adapter *adapter =3D client->adapter; + s32 manufacturer, product, revision; + struct device *dev =3D &adapter->dev; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + manufacturer =3D i2c_smbus_read_byte_data(client, REG_MANUFACTURER_ID); + if (manufacturer !=3D MANUFACTURER_ID) + return -ENODEV; + + product =3D i2c_smbus_read_byte_data(client, REG_PRODUCT_ID); + switch (product) { + case EMC2101: + strscpy(info->type, "emc2101", I2C_NAME_SIZE); + break; + case EMC2101_R: + strscpy(info->type, "emc2101-r", I2C_NAME_SIZE); + break; + default: + return -ENODEV; + } + + revision =3D i2c_smbus_read_byte_data(client, REG_REVISION); + + dev_dbg(dev, "Found %s at 0x%02x (rev 0x%02x).\n", info->type, client->ad= dr, revision); + + return 0; +} + +static const struct i2c_device_id emc2101_ids[] =3D { + { "emc2101" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, emc2101_ids); + +static const struct of_device_id emc2101_of_match_table[] =3D { + { .compatible =3D "microchip,emc2101", }, + { }, +}; +MODULE_DEVICE_TABLE(of, emc2101_of_match_table); + +static const unsigned short emc2101_address_list[] =3D { + 0x4c, I2C_CLIENT_END +}; + +static struct i2c_driver emc2101_driver =3D { + .address_list =3D emc2101_address_list, + .class =3D I2C_CLASS_HWMON, + .detect =3D emc2101_detect, + .driver =3D { + .name =3D "emc2101", + .of_match_table =3D emc2101_of_match_table, + .pm =3D pm_sleep_ptr(&emc2101_pm), + }, + .id_table =3D emc2101_ids, + .probe =3D emc2101_probe, +}; +module_i2c_driver(emc2101_driver); + +MODULE_AUTHOR("=C3=81lvaro Fern=C3=A1ndez Rojas "); +MODULE_DESCRIPTION("Microchip EMC2101 hwmon driver"); +MODULE_LICENSE("GPL"); --=20 2.39.5