Received: by 2002:ab2:6816:0:b0:1f9:5764:f03e with SMTP id t22csp2677953lqo; Mon, 20 May 2024 13:00:54 -0700 (PDT) X-Forwarded-Encrypted: i=3; AJvYcCWFjbMoHXSf3kOV1IDovpc59cF3v3btvfb0AcGfQCjQKM0rhwGQbcKY5EID7Hn06AInr3q+5JexZilXbHyAkR5rCFk7mtOk3JOTVprl2Q== X-Google-Smtp-Source: AGHT+IFVMtU4mV8l5BTDN+7fzYWwfTPGWxKb/dBIRmqIM0NUrgZzWGn8zT4+eT7ffn1m2Heqlm50 X-Received: by 2002:ad4:5766:0:b0:6a0:bc4c:d8e2 with SMTP id 6a1803df08f44-6a841a4f0e9mr139485956d6.5.1716235253923; Mon, 20 May 2024 13:00:53 -0700 (PDT) ARC-Seal: i=2; a=rsa-sha256; t=1716235253; cv=pass; d=google.com; s=arc-20160816; b=xnb3wsKKA2ygOXZRdtgdKT2pdmCV7qeFZrlI55fWk4KOU1u1vxNnX39fO8obilsmuZ 02WjIw634jXDg0vcIhQtEdctE3MVhpaGZy+VoSx92SMVBKIbZZ3bDeoGtK74AYBSkT/z 2BFdJtbTzz0GULXFap8zmqH/O5Myd33PsxXSs7r2kYu2HEwHn/u4Pn131D0eoo7nqd/B hsuaSujZm8mYof2m2IhC8PpbfWRfUkqp39B66X5ZzAwphY6GkqQqS5IeoEAH3p3ZhBNU ScB9+BHLJZw3mtsxqsjTDZvPAmMpvBPuKLmbOQ2woD9j8TucA0TT9URMQXrRtDPDCjX+ DzTQ== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=content-transfer-encoding:mime-version:list-unsubscribe :list-subscribe:list-id:precedence:references:in-reply-to:message-id :date:subject:cc:to:from:dkim-signature; bh=smFRWty8H8eYfo6Wznf/L+BqoYIjUADdw6rZ+Z7IQy0=; fh=WU8T+AyjLY90ClFWxFrYcA1s2xBcmxguCxa+zJpjgQI=; b=o4pQElbGBanNI80twgxe0u7MzBKlEj7l2KfDOwIKiDCKiSX5C8V7zENz6Rs4W2SFwC fo/CInU7OScfhnhfNE2IhfqStitlLPTKhAUMvqey2/0QYrYtLF1kKnhYNPEV3q0R56JW KUSlTiN0PMMwq3Tv25c4xmehC6Zoe7TN4gg5pb87zVF1eA4wN17Ex/RQxd4i3e7TTYca ycN8vis1FUROtOmzVmlrfguYpd/PpuzZaBlEeDYphkIWHCxW7xE0K1r5mAutiB8+en7g yRnSe5YjWTjoBFCaWfc8ab8/PCprxn7uv0NwMEDycevekeVtABSn1DXH145Zl9oNB+G4 xf8A==; dara=google.com ARC-Authentication-Results: i=2; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=n6aDhvhl; arc=pass (i=1 spf=pass spfdomain=ideasonboard.com dkim=pass dkdomain=ideasonboard.com); spf=pass (google.com: domain of linux-kernel+bounces-184139-linux.lists.archive=gmail.com@vger.kernel.org designates 2604:1380:45d1:ec00::1 as permitted sender) smtp.mailfrom="linux-kernel+bounces-184139-linux.lists.archive=gmail.com@vger.kernel.org" Return-Path: Received: from ny.mirrors.kernel.org (ny.mirrors.kernel.org. [2604:1380:45d1:ec00::1]) by mx.google.com with ESMTPS id 6a1803df08f44-6a15f2b65d3si264717626d6.362.2024.05.20.13.00.53 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 20 May 2024 13:00:53 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel+bounces-184139-linux.lists.archive=gmail.com@vger.kernel.org designates 2604:1380:45d1:ec00::1 as permitted sender) client-ip=2604:1380:45d1:ec00::1; Authentication-Results: mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=n6aDhvhl; arc=pass (i=1 spf=pass spfdomain=ideasonboard.com dkim=pass dkdomain=ideasonboard.com); spf=pass (google.com: domain of linux-kernel+bounces-184139-linux.lists.archive=gmail.com@vger.kernel.org designates 2604:1380:45d1:ec00::1 as permitted sender) smtp.mailfrom="linux-kernel+bounces-184139-linux.lists.archive=gmail.com@vger.kernel.org" Received: from smtp.subspace.kernel.org (wormhole.subspace.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ny.mirrors.kernel.org (Postfix) with ESMTPS id 7B0471C20E67 for ; Mon, 20 May 2024 20:00:53 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id BB151137C5A; Mon, 20 May 2024 20:00:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="n6aDhvhl" Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C6643139D1C; Mon, 20 May 2024 19:59:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.167.242.64 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1716235201; cv=none; b=HZ2GubaxFdCD3x08mRecILCHL/UwsgGBPY5YjnseECONFfcWmjABeUdjaHkjOM0t7FiJULIxqVleFIrycBxcBVWBb6TWsvKzN3LSZAOC6yOjzCojVAa0iKpiUJfSntz8ui5d1zKsu8R22sLDbNMBZhk2HusmbWCZM6HHLerkjrU= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1716235201; c=relaxed/simple; bh=edOZnoLpGGwMfNSHAt6rfmjOeaPRifmFW19KtEa3l2s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SgqjLKDg3Oh33ME2sT8iGjToOvSONf4yQEBkr8nYtoHEbUmhT+iq67dfF4qLO1Kq1AHwWSQpkWaEizZXI721cQ7ZQrICA/exJw0lRi2l+uPW+V+C/bVFp9xKDmG4euB3GPzl9AtCZq65rD5kGXkodD86cWeymx0hOZAqcXPuA44= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com; spf=pass smtp.mailfrom=ideasonboard.com; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b=n6aDhvhl; arc=none smtp.client-ip=213.167.242.64 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ideasonboard.com Received: from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi [81.175.209.231]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E817DEBB; Mon, 20 May 2024 21:59:45 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1716235186; bh=edOZnoLpGGwMfNSHAt6rfmjOeaPRifmFW19KtEa3l2s=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=n6aDhvhlmt7JYn0a9FIj6vD5A7zWGpFD4SWGYLOgRZj9a+Ov8L9ApCHogSNmFfiMq ONF/X+4aUUOcUcx7h56g3kxcrJQX1CoopXWPub5wtHufsDGLZSmeF8caHKs1NbF0zM 0r5XAmfqfKlHd67EC8jL2ds0R4Yq93V5VkJWh44k= From: Laurent Pinchart To: linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-pwm@vger.kernel.org Cc: Alexandru Ardelean , Bartosz Golaszewski , Conor Dooley , Krzysztof Kozlowski , Lee Jones , Linus Walleij , Rob Herring , =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= , Clark Wang Subject: [PATCH 5/5] pwm: adp5585: Add Analog Devices ADP5585 support Date: Mon, 20 May 2024 22:59:41 +0300 Message-ID: <20240520195942.11582-6-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.44.1 In-Reply-To: <20240520195942.11582-1-laurent.pinchart@ideasonboard.com> References: <20240520195942.11582-1-laurent.pinchart@ideasonboard.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Clark Wang The ADP5585 is a 10/11 input/output port expander with a built in keypad matrix decoder, programmable logic, reset generator, and PWM generator. This driver supports the PWM function using the platform device registered by the core MFD driver. The driver is derived from an initial implementation from NXP, available in commit 113113742208 ("MLK-25922-1 pwm: adp5585: add adp5585 PWM support") in their BSP kernel tree. It has been extensively rewritten. Signed-off-by: Clark Wang Signed-off-by: Laurent Pinchart --- Changes compared to the NXP original version - Add MAINTAINERS entry - Drop pwm_ops.owner - Fix compilation - Add prefix to compatible string - Switch to regmap - Use devm_pwmchip_add() - Cleanup header includes - White space fixes - Drop ADP5585_REG_MASK - Fix register field names - Use mutex scope guards - Clear OSC_EN when freeing PWM - Reorder functions - Clear PWM_IN_AND and PWM_MODE bits - Support inverted polarity - Clean up on/off computations - Fix duty cycle computation in .get_state() - Destroy mutex on remove - Update copyright - Update license to GPL-2.0-only --- MAINTAINERS | 1 + drivers/pwm/Kconfig | 7 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-adp5585.c | 230 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 239 insertions(+) create mode 100644 drivers/pwm/pwm-adp5585.c diff --git a/MAINTAINERS b/MAINTAINERS index 5689fec270ef..280f97129598 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -505,6 +505,7 @@ S: Maintained F: Documentation/devicetree/bindings/*/adi,adp5585*.yaml F: drivers/gpio/gpio-adp5585.c F: drivers/mfd/adp5585.c +F: drivers/pwm/pwm-adp5585.c F: include/linux/mfd/adp5585.h ADP5588 QWERTY KEYPAD AND IO EXPANDER DRIVER (ADP5588/ADP5587) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 4b956d661755..2393a50b3781 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -51,6 +51,13 @@ config PWM_AB8500 To compile this driver as a module, choose M here: the module will be called pwm-ab8500. +config PWM_ADP5585 + tristate "ADP5585 PWM support" + depends on MFD_ADP5585 + help + This option enables support for the PWM function found in the Analog + Devices ADP5585. + config PWM_APPLE tristate "Apple SoC PWM support" depends on ARCH_APPLE || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index c5ec9e168ee7..100ac66b5f40 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_SYSFS) += sysfs.o obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o +obj-$(CONFIG_PWM_ADP5585) += pwm-adp5585.o obj-$(CONFIG_PWM_APPLE) += pwm-apple.o obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o diff --git a/drivers/pwm/pwm-adp5585.c b/drivers/pwm/pwm-adp5585.c new file mode 100644 index 000000000000..709713d8f47a --- /dev/null +++ b/drivers/pwm/pwm-adp5585.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices ADP5585 PWM driver + * + * Copyright 2022 NXP + * Copyright 2024 Ideas on Board Oy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADP5585_PWM_CHAN_NUM 1 + +#define ADP5585_PWM_OSC_FREQ_HZ 1000000U +#define ADP5585_PWM_MIN_PERIOD_NS (2ULL * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) +#define ADP5585_PWM_MAX_PERIOD_NS (2ULL * 0xffff * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) + +struct adp5585_pwm_chip { + struct pwm_chip chip; + struct regmap *regmap; + struct mutex lock; + u8 pin_config_val; +}; + +static inline struct adp5585_pwm_chip * +to_adp5585_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct adp5585_pwm_chip, chip); +} + +static int pwm_adp5585_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct adp5585_pwm_chip *adp5585_pwm = to_adp5585_pwm_chip(chip); + unsigned int val; + int ret; + + guard(mutex)(&adp5585_pwm->lock); + + ret = regmap_read(adp5585_pwm->regmap, ADP5585_PIN_CONFIG_C, &val); + if (ret) + return ret; + + adp5585_pwm->pin_config_val = val; + + ret = regmap_update_bits(adp5585_pwm->regmap, ADP5585_PIN_CONFIG_C, + ADP5585_R3_EXTEND_CFG_MASK, + ADP5585_R3_EXTEND_CFG_PWM_OUT); + if (ret) + return ret; + + ret = regmap_update_bits(adp5585_pwm->regmap, ADP5585_GENERAL_CFG, + ADP5585_OSC_EN, ADP5585_OSC_EN); + if (ret) + return ret; + + return 0; +} + +static void pwm_adp5585_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct adp5585_pwm_chip *adp5585_pwm = to_adp5585_pwm_chip(chip); + + guard(mutex)(&adp5585_pwm->lock); + + regmap_update_bits(adp5585_pwm->regmap, ADP5585_PIN_CONFIG_C, + ADP5585_R3_EXTEND_CFG_MASK, + adp5585_pwm->pin_config_val); + regmap_update_bits(adp5585_pwm->regmap, ADP5585_GENERAL_CFG, + ADP5585_OSC_EN, 0); +} + +static int pwm_adp5585_apply(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct adp5585_pwm_chip *adp5585_pwm = to_adp5585_pwm_chip(chip); + u32 on, off; + int ret; + + if (!state->enabled) { + guard(mutex)(&adp5585_pwm->lock); + + return regmap_update_bits(adp5585_pwm->regmap, ADP5585_PWM_CFG, + ADP5585_PWM_EN, 0); + } + + if (state->period < ADP5585_PWM_MIN_PERIOD_NS || + state->period > ADP5585_PWM_MAX_PERIOD_NS) + return -EINVAL; + + /* + * Compute the on and off time. As the internal oscillator frequency is + * 1MHz, the calculation can be simplified without loss of precision. + */ + on = DIV_ROUND_CLOSEST_ULL(state->duty_cycle, + NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ); + off = DIV_ROUND_CLOSEST_ULL(state->period - state->duty_cycle, + NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ); + + if (state->polarity == PWM_POLARITY_INVERSED) + swap(on, off); + + guard(mutex)(&adp5585_pwm->lock); + + ret = regmap_write(adp5585_pwm->regmap, ADP5585_PWM_OFFT_LOW, + off & 0xff); + if (ret) + return ret; + ret = regmap_write(adp5585_pwm->regmap, ADP5585_PWM_OFFT_HIGH, + (off >> 8) & 0xff); + if (ret) + return ret; + ret = regmap_write(adp5585_pwm->regmap, ADP5585_PWM_ONT_LOW, + on & 0xff); + if (ret) + return ret; + ret = regmap_write(adp5585_pwm->regmap, ADP5585_PWM_ONT_HIGH, + (on >> 8) & 0xff); + if (ret) + return ret; + + /* Enable PWM in continuous mode and no external AND'ing. */ + ret = regmap_update_bits(adp5585_pwm->regmap, ADP5585_PWM_CFG, + ADP5585_PWM_IN_AND | ADP5585_PWM_MODE | + ADP5585_PWM_EN, ADP5585_PWM_EN); + if (ret) + return ret; + + return 0; +} + +static int pwm_adp5585_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct adp5585_pwm_chip *adp5585_pwm = to_adp5585_pwm_chip(chip); + unsigned int on, off; + unsigned int val; + + regmap_read(adp5585_pwm->regmap, ADP5585_PWM_OFFT_LOW, &off); + regmap_read(adp5585_pwm->regmap, ADP5585_PWM_OFFT_HIGH, &val); + off |= val << 8; + + regmap_read(adp5585_pwm->regmap, ADP5585_PWM_ONT_LOW, &on); + regmap_read(adp5585_pwm->regmap, ADP5585_PWM_ONT_HIGH, &val); + on |= val << 8; + + state->duty_cycle = on * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ); + state->period = (on + off) * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ); + + state->polarity = PWM_POLARITY_NORMAL; + + regmap_read(adp5585_pwm->regmap, ADP5585_PWM_CFG, &val); + state->enabled = !!(val & ADP5585_PWM_EN); + + return 0; +} + +static const struct pwm_ops adp5585_pwm_ops = { + .request = pwm_adp5585_request, + .free = pwm_adp5585_free, + .apply = pwm_adp5585_apply, + .get_state = pwm_adp5585_get_state, +}; + +static int adp5585_pwm_probe(struct platform_device *pdev) +{ + struct adp5585_dev *adp5585 = dev_get_drvdata(pdev->dev.parent); + struct adp5585_pwm_chip *adp5585_pwm; + int ret; + + adp5585_pwm = devm_kzalloc(&pdev->dev, sizeof(*adp5585_pwm), GFP_KERNEL); + if (!adp5585_pwm) + return -ENOMEM; + + platform_set_drvdata(pdev, adp5585_pwm); + + adp5585_pwm->regmap = adp5585->regmap; + + mutex_init(&adp5585_pwm->lock); + + adp5585_pwm->chip.dev = &pdev->dev; + adp5585_pwm->chip.ops = &adp5585_pwm_ops; + adp5585_pwm->chip.npwm = ADP5585_PWM_CHAN_NUM; + + ret = devm_pwmchip_add(&pdev->dev, &adp5585_pwm->chip); + if (ret) { + mutex_destroy(&adp5585_pwm->lock); + return dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n"); + } + + return 0; +} + +static void adp5585_pwm_remove(struct platform_device *pdev) +{ + struct adp5585_pwm_chip *adp5585_pwm = platform_get_drvdata(pdev); + + mutex_destroy(&adp5585_pwm->lock); +} + +static const struct of_device_id adp5585_pwm_of_match[] = { + { .compatible = "adi,adp5585-pwm" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, adp5585_pwm_of_match); + +static struct platform_driver adp5585_pwm_driver = { + .driver = { + .name = "adp5585-pwm", + .of_match_table = adp5585_pwm_of_match, + }, + .probe = adp5585_pwm_probe, + .remove_new = adp5585_pwm_remove, +}; +module_platform_driver(adp5585_pwm_driver); + +MODULE_AUTHOR("Xiaoning Wang "); +MODULE_DESCRIPTION("ADP5585 PWM Driver"); +MODULE_LICENSE("GPL"); -- Regards, Laurent Pinchart