From nobody Thu Dec 18 19:05:17 2025 Received: from mail-wr1-f41.google.com (mail-wr1-f41.google.com [209.85.221.41]) (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 AE4492940D; Wed, 26 Feb 2025 00:26:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1740529570; cv=none; b=F9lFI9wGbb2p1dV9/RF7GJgCIuh9gMT5w1UMpxRutQLVPb6q8PWROruBcxdh4RXTC11c//ngzLHAWK7RgrMkpI6FWVXo76ZGUfI3+4/z8Q4huXWJKgHTZc+VxX66xsiaQQDm/zScqouoc+Od/fBPGmrrYtgK6i3bZuES5zpd3bc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1740529570; c=relaxed/simple; bh=mj42VpVljgGImIoh5OgkU4zIW69w34RJgB/fgFD9bgM=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=eQTpbtZppASldygXIS5oIk4o3c6OC+Jioq6PAtx1V3Ea6OA46Tbd2Dcdg3X2iySUVe/JR9W2yC6PDCzmigZFCpMrVOggehpxumbAaO/fbaxCjd5z7beAUIP5ZgTUi53G99tqP6jzcGGFikn5mRbbSdaGQu2go+/B+Yk3rdGBXy8= 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=YEL1/jwS; arc=none smtp.client-ip=209.85.221.41 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="YEL1/jwS" Received: by mail-wr1-f41.google.com with SMTP id ffacd0b85a97d-38f1e8efe82so6547836f8f.0; Tue, 25 Feb 2025 16:26:07 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1740529566; x=1741134366; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=DaSjNH3wLeyETSqWv78cRCug4f9wxUbYU8afOXA51Q0=; b=YEL1/jwSPtJ3/yD2US060jGQwSe8fUvcxdQyQzIMel4hSdvQv0eUMrx233ystTjIZe 7+Q1X0ANv4ncv8VR1Gey5NSNIJ7RFAtI214sD5WzkLS8EMxUjoWYuNt1D9r5QEstHoLB rfLc61RfIEsdTj9BNkH0Wdz5ebS+bqPrlIK1mNIh8OR6ePCQZA60kTnXwSa/SciLmXhp tLiaNpEjmvo9w4g4XleQXKS5rbs+tv+8bPXB3fzL1Gyz3Gk1PDqYhtJMjbKmlb7ntP1T hi6ez4B5XWX8DfEsdZ/h/YmBcKcyh3uHi6fQmPGns+U/StPM0JJ3g5vNFo9jFIV9pebI LkDA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1740529566; x=1741134366; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=DaSjNH3wLeyETSqWv78cRCug4f9wxUbYU8afOXA51Q0=; b=aImx5Uk3DGwFVJssLnjjmXOx1T7uIjf+RxsQO6tpoOVvuDf3XWt0xLiXGenidZGdNr r19MTb/liauKbfyFKqArj5tb5Meo2iQ08RP6kBFY59Heo2m+sI1Up5bdi4CQvPBXUxtS lKarfScKr5XMt1rOoUnOFH0Z+7SIyTK/swlzOmm5yJUYM9T6E7HTXFV0jxDAmUyRB+sL QMh3Ww/R8Hq5SWLHYEdB5T9OTAE/a7wOxYovNP/X9KNq6hU1xMmb4/mLmf6HsnBZBhus SmOk/Nw5fvDFEbgce9IlCmyPIwYGqV0A7uXAmLqwlMJMvHsIiC+DbhMxdSe+B0eoQYzk 4FkA== X-Forwarded-Encrypted: i=1; AJvYcCWWAUFFXfRtDXiQlnzSfb0Vd7MMpjHRWYbAP4dDP9ZgsXzPRa/qCPvh59KdX9hwrNyJE/g2lS+LYACHWm4=@vger.kernel.org, AJvYcCXjgo7ecvRWzhXAMJS4da1TPuxSCqZdhKxalCBv8FhVABLgmVyKzwMMLSlizy8GEyL4QpFDR0bTuKM7@vger.kernel.org X-Gm-Message-State: AOJu0Yzl4ZMmRYcAjspO/+RwDdVH87ZSPu3IwH10npxeZWQiuXR9VRT2 Pxx14mgLwWbLB+Sd1D1+gm1CwH+8BHUq3bYWKvLHFWkOTu8tRihdSBySNA== X-Gm-Gg: ASbGnctkFU1LRofX2nRaqjaktRxHMbrNVPfPyomOCdPogh2JWi/XGYA1HAB3+e1Dora dwIgBIZ8MXji9GWLkhPqQgKRcLodD5kYGnbQX4wKqEwHt2XHQeqNAUU/cBhqb/013oH2PLGRoG2 bJQ8ULGy8Pxv4HyM3PPa6FUAm9fk9flaz5N6UwKF2RmRTwpEudWQoK6waX+tip6e0C+9wIM5McO TWuvo4ytzkEeCSypMVkMsudi/lA4ShRlVYJAi7GQdrnAjGzPhxJPwaIBM5f/yj3jOcEG4bJAUdi HOFt8WP7lQn5Y1C+Jz9Xci2ml6140vnL2yipL7TXMd4iri12oGMIicg3f+vr9nD3Sv8MelE= X-Google-Smtp-Source: AGHT+IHSyvHAdolmJyQsWbRZ95LJv2b1j51ml6YTV2cGMe5ouCv+tOWL4iNMt7u2hQhnj58b8zGwCg== X-Received: by 2002:a5d:64eb:0:b0:390:d73a:4848 with SMTP id ffacd0b85a97d-390d73a48d9mr117152f8f.47.1740529565556; Tue, 25 Feb 2025 16:26:05 -0800 (PST) Received: from localhost.localdomain (93-34-90-129.ip49.fastwebnet.it. [93.34.90.129]) by smtp.googlemail.com with ESMTPSA id ffacd0b85a97d-390cd88295dsm3785410f8f.45.2025.02.25.16.26.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 25 Feb 2025 16:26:04 -0800 (PST) From: Christian Marangi To: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= , linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Cc: Benjamin Larsson , AngeloGioacchino Del Regno , Lorenzo Bianconi , Christian Marangi Subject: [RESEND PATCH v11] pwm: airoha: Add support for EN7581 SoC Date: Wed, 26 Feb 2025 01:24:42 +0100 Message-ID: <20250226002537.3752-1-ansuelsmth@gmail.com> X-Mailer: git-send-email 2.47.1 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Benjamin Larsson Introduce driver for PWM module available on EN7581 SoC. Signed-off-by: Benjamin Larsson Reviewed-by: AngeloGioacchino Del Regno Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Co-developed-by: Christian Marangi Signed-off-by: Christian Marangi --- Changes v11: - Fix wrong calculation of period and duty - Use AIROHA_PWM prefix for each define - Drop set/get special define in favour of BITS and GENMASK - Correctly use dev_err_probe - Init bucket with initial values - Rework define to make use of FIELD_PREP and FIELD_GET Changes in v10: - repost just patch 6/6 (pwm driver) since patches {1/6-5/6} have been already applied in linux-pinctrl tree - pwm: introduce AIROHA_PWM_FIELD_GET and AIROHA_PWM_FIELD_SET macros to get/set field with non-const mask - pwm: simplify airoha_pwm_get_generator() to report unused generator and remove double lookup - pwm: remove device_node pointer in airoha_pwm struct since this is write-only field - pwm: cosmetics - Link to v9: https://lore.kernel.org/r/20241023-en7581-pinctrl-v9-0-afb0cb= cab0ec@kernel.org Changes in v9: - pwm: remove unused properties - Link to v8: https://lore.kernel.org/r/20241018-en7581-pinctrl-v8-0-b676b9= 66a1d1@kernel.org Changes in v8: - pwm: add missing properties documentation - Link to v7: https://lore.kernel.org/r/20241016-en7581-pinctrl-v7-0-4ff611= f263a7@kernel.org Changes in v7: - pinctrl: cosmetics - pinctrl: fix compilation warning - Link to v6: https://lore.kernel.org/r/20241013-en7581-pinctrl-v6-0-2048e2= d099c2@kernel.org Changes in v6: - pwm: rely on regmap APIs - pwm: introduce compatible string - pinctrl: introduce compatible string - remove airoha-mfd driver - add airoha,en7581-pinctrl binding - add airoha,en7581-pwm binding - update airoha,en7581-gpio-sysctl binding - Link to v5: https://lore.kernel.org/r/20241001-en7581-pinctrl-v5-0-dc1ce5= 42b6c6@kernel.org Changes in v5: - use spin_lock in airoha_pinctrl_rmw instead of a mutex since it can run in interrupt context - remove unused includes in pinctrl driver - since the irq_chip is immutable, allocate the gpio_irq_chip struct statically in pinctrl driver - rely on regmap APIs in pinctrl driver but keep the spin_lock local to the driver - rely on guard/guard_scope APIs in pinctrl driver - improve naming convention pinctrl driver - introduce airoha_pinconf_set_pin_value utility routine - Link to v4: https://lore.kernel.org/r/20240911-en7581-pinctrl-v4-0-60ac93= d760bb@kernel.org Changes in v4: - add 'Limitation' description in pwm driver - fix comments in pwm driver - rely on mfd->base __iomem pointer in pwm driver, modify register offsets according to it and get rid of sgpio_cfg, flash_cfg and cycle_cfg pointers - simplify register utility routines in pwm driver - use 'generator' instead of 'waveform' suffix for pwm routines - fix possible overflow calculating duty cycle in pwm driver - do not modify pwm state in free callback in pwm driver - cap the maximum period in pwm driver - do not allow inverse polarity in pwm driver - do not set of_xlate callback in the pwm driver and allow the stack to do it - fix MAINTAINERS file for airoha pinctrl driver - fix undefined reference to __ffsdi2 in pinctrl driver - simplify airoha,en7581-gpio-sysctl.yam binding - Link to v3: https://lore.kernel.org/r/20240831-en7581-pinctrl-v3-0-98eebf= b4da66@kernel.org Changes in v3: - introduce airoha-mfd driver - add pwm driver to the same series - model pinctrl and pwm drivers as childs of a parent mfd driver. - access chip-scu memory region in pinctrl driver via syscon - introduce a single airoha,en7581-gpio-sysctl.yaml binding and get rid of dedicated bindings for pinctrl and pwm - add airoha,en7581-chip-scu.yaml binding do the series - Link to v2: https://lore.kernel.org/r/20240822-en7581-pinctrl-v2-0-ba1559= 173a7f@kernel.org Changes in v2: - Fix compilation errors - Collapse some register mappings for gpio and irq controllers - update dt-bindings according to new register mapping - fix some dt-bindings errors - Link to v1: https://lore.kernel.org/all/cover.1723392444.git.lorenzo@kern= el.org/ drivers/pwm/Kconfig | 11 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-airoha.c | 488 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 500 insertions(+) create mode 100644 drivers/pwm/pwm-airoha.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 63beb0010e3e..e939187784c0 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -54,6 +54,17 @@ config PWM_ADP5585 This option enables support for the PWM function found in the Analog Devices ADP5585. =20 +config PWM_AIROHA + tristate "Airoha PWM support" + depends on ARCH_AIROHA || COMPILE_TEST + depends on OF + select REGMAP_MMIO + help + Generic PWM framework driver for Airoha SoC. + + To compile this driver as a module, choose M here: the module + will be called pwm-airoha. + 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 539e0def3f82..97c1c79bbc54 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_PWM) +=3D core.o obj-$(CONFIG_PWM_AB8500) +=3D pwm-ab8500.o obj-$(CONFIG_PWM_ADP5585) +=3D pwm-adp5585.o +obj-$(CONFIG_PWM_AIROHA) +=3D pwm-airoha.o obj-$(CONFIG_PWM_APPLE) +=3D pwm-apple.o obj-$(CONFIG_PWM_ATMEL) +=3D pwm-atmel.o obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) +=3D pwm-atmel-hlcdc.o diff --git a/drivers/pwm/pwm-airoha.c b/drivers/pwm/pwm-airoha.c new file mode 100644 index 000000000000..51d02a5ff064 --- /dev/null +++ b/drivers/pwm/pwm-airoha.c @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2022 Markus Gothe + * + * Limitations: + * - Only 8 concurrent waveform generators are available for 8 combinatio= ns of + * duty_cycle and period. Waveform generators are shared between 16 GPIO + * pins and 17 SIPO GPIO pins. + * - Supports only normal polarity. + * - On configuration the currently running period is completed. + * - Minimum supported period is 4ms + * - Maximum supported period is 1s + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AIROHA_PWM_REG_SGPIO_LED_DATA 0x0024 +#define AIROHA_PWM_SGPIO_LED_DATA_SHIFT_FLAG BIT(31) +#define AIROHA_PWM_SGPIO_LED_DATA_DATA GENMASK(16, 0) + +#define AIROHA_PWM_REG_SGPIO_CLK_DIVR 0x0028 +#define AIROHA_PWM_SGPIO_CLK_DIVR GENMASK(1, 0) + +#define AIROHA_PWM_REG_SGPIO_CLK_DLY 0x002c + +#define AIROHA_PWM_REG_SIPO_FLASH_MODE_CFG 0x0030 +#define AIROHA_PWM_SERIAL_GPIO_FLASH_MODE BIT(1) +#define AIROHA_PWM_SERIAL_GPIO_MODE_74HC164 BIT(0) + +#define AIROHA_PWM_REG_GPIO_FLASH_PRD_SET(_n) (0x003c + (4 * (_n))) +#define AIROHA_PWM_REG_GPIO_FLASH_PRD_SHIFT(_n) (16 * (_n)) +#define AIROHA_PWM_GPIO_FLASH_PRD_LOW GENMASK(15, 8) +#define AIROHA_PWM_GPIO_FLASH_PRD_HIGH GENMASK(7, 0) + +#define AIROHA_PWM_REG_GPIO_FLASH_MAP(_n) (0x004c + (4 * (_n))) +#define AIROHA_PWM_REG_GPIO_FLASH_MAP_SHIFT(_n) (4 * (_n)) +#define AIROHA_PWM_GPIO_FLASH_EN BIT(3) +#define AIROHA_PWM_GPIO_FLASH_SET_ID GENMASK(2, 0) + +/* Register map is equal to GPIO flash map */ +#define AIROHA_PWM_REG_SIPO_FLASH_MAP(_n) (0x0054 + (4 * (_n))) + +#define AIROHA_PWM_REG_CYCLE_CFG_VALUE(_n) (0x0098 + (4 * (_n))) +#define AIROHA_PWM_REG_CYCLE_CFG_SHIFT(_n) (8 * (_n)) +#define AIROHA_PWM_WAVE_GEN_CYCLE GENMASK(7, 0) + +/* GPIO/SIPO flash map handles 8 pins in one register */ +#define AIROHA_PWM_PINS_PER_FLASH_MAP 8 +/* Cycle cfg handles 4 generators in one register */ +#define AIROHA_PWM_BUCKET_PER_CYCLE_CFG 4 +/* Flash producer handles 2 generators in one register */ +#define AIROHA_PWM_BUCKET_PER_FLASH_PROD 2 + +#define AIROHA_PWM_NUM_BUCKETS 8 +/* + * The first 16 GPIO pins, GPIO0-GPIO15, are mapped into 16 PWM channels, = 0-15. + * The SIPO GPIO pins are 17 pins which are mapped into 17 PWM channels, 1= 6-32. + * However, we've only got 8 concurrent waveform generators and can theref= ore + * only use up to 8 different combinations of duty cycle and period at a t= ime. + */ +#define AIROHA_PWM_NUM_GPIO 16 +#define AIROHA_PWM_NUM_SIPO 17 +#define AIROHA_PWM_MAX_CHANNELS (AIROHA_PWM_NUM_GPIO + AIROHA_PWM_NUM_SI= PO) + +struct airoha_pwm_bucket { + /* Bitmask of PWM channels using this bucket */ + u64 used; + u64 period_ns; + u64 duty_ns; +}; + +struct airoha_pwm { + struct regmap *regmap; + + u64 initialized; + + struct airoha_pwm_bucket buckets[AIROHA_PWM_NUM_BUCKETS]; + + /* Cache bucket used by each pwm channel */ + u8 channel_bucket[AIROHA_PWM_MAX_CHANNELS]; +}; + +/* The PWM hardware supports periods between 4 ms and 1 s */ +#define AIROHA_PWM_PERIOD_TICK_NS (4 * NSEC_PER_MSEC) +#define AIROHA_PWM_PERIOD_MAX_NS (1 * NSEC_PER_SEC) +/* It is represented internally as 1/250 s between 1 and 250. Unit is tick= s. */ +#define AIROHA_PWM_PERIOD_MIN 1 +#define AIROHA_PWM_PERIOD_MAX 250 +/* Duty cycle is relative with 255 corresponding to 100% */ +#define AIROHA_PWM_DUTY_FULL 255 + +static void airoha_pwm_get_flash_map_addr_and_shift(unsigned int hwpwm, + u32 *addr, u32 *shift) +{ + u64 offset =3D hwpwm; + + if (hwpwm >=3D AIROHA_PWM_NUM_GPIO) + offset -=3D AIROHA_PWM_NUM_GPIO; + + /* One FLASH_MAP register handles 8 pins */ + *shift =3D do_div(offset, AIROHA_PWM_PINS_PER_FLASH_MAP); + *shift =3D AIROHA_PWM_REG_GPIO_FLASH_MAP_SHIFT(*shift); + + if (offset < AIROHA_PWM_NUM_GPIO) + *addr =3D AIROHA_PWM_REG_GPIO_FLASH_MAP(offset); + else + *addr =3D AIROHA_PWM_REG_SIPO_FLASH_MAP(offset); +} + +static void airoha_pwm_get_ticks_from_ns(u64 period_ns, u32 *period_tick, + u64 duty_ns, u32 *duty_tick) +{ + period_ns =3D min_t(u64, period_ns, AIROHA_PWM_PERIOD_MAX_NS); + *period_tick =3D div_u64(period_ns, AIROHA_PWM_PERIOD_TICK_NS); + + *duty_tick =3D mul_u64_u64_div_u64(duty_ns, AIROHA_PWM_DUTY_FULL, + *period_tick * AIROHA_PWM_PERIOD_TICK_NS); + *duty_tick =3D min_t(u32, *duty_tick, AIROHA_PWM_DUTY_FULL); +} + +static void airoha_pwm_fill_bucket(struct airoha_pwm *pc, int bucket) +{ + u64 offset, period_ns, duty_ns; + u32 period_tick, duty_tick; + u32 shift, val; + + offset =3D bucket; + shift =3D do_div(offset, AIROHA_PWM_BUCKET_PER_CYCLE_CFG); + shift =3D AIROHA_PWM_REG_CYCLE_CFG_SHIFT(shift); + + regmap_read(pc->regmap, AIROHA_PWM_REG_CYCLE_CFG_VALUE(offset), &val); + + period_tick =3D FIELD_GET(AIROHA_PWM_WAVE_GEN_CYCLE, val >> shift); + period_ns =3D period_tick * AIROHA_PWM_PERIOD_TICK_NS; + + offset =3D bucket; + shift =3D do_div(offset, AIROHA_PWM_BUCKET_PER_FLASH_PROD); + shift =3D AIROHA_PWM_REG_GPIO_FLASH_PRD_SHIFT(shift); + + regmap_read(pc->regmap, AIROHA_PWM_REG_GPIO_FLASH_PRD_SET(offset), + &val); + + duty_tick =3D FIELD_GET(AIROHA_PWM_GPIO_FLASH_PRD_HIGH, val >> shift); + duty_ns =3D mul_u64_u64_div_u64(duty_tick, period_ns, AIROHA_PWM_DUTY_FUL= L); + + pc->buckets[bucket].period_ns =3D period_ns; + pc->buckets[bucket].duty_ns =3D duty_ns; +} + +static int airoha_pwm_get_generator(struct airoha_pwm *pc, u64 duty_ns, + u64 period_ns) +{ + int i, unused =3D -1; + + for (i =3D 0; i < ARRAY_SIZE(pc->buckets); i++) { + struct airoha_pwm_bucket *bucket =3D &pc->buckets[i]; + u32 duty_tick, duty_tick_bucket; + u32 period_tick; + + /* If found, save an unused bucket to return it later */ + if (!bucket->used && unused =3D=3D -1) { + unused =3D i; + continue; + } + + if (duty_ns =3D=3D bucket->duty_ns && + period_ns =3D=3D bucket->period_ns) + return i; + + /* + * Unlike duty cycle zero, which can be handled by + * disabling PWM, a generator is needed for full duty + * cycle but it can be reused regardless of period + */ + airoha_pwm_get_ticks_from_ns(period_ns, &period_tick, + duty_ns, &duty_tick); + airoha_pwm_get_ticks_from_ns(bucket->period_ns, &period_tick, + bucket->duty_ns, &duty_tick_bucket); + if (duty_tick =3D=3D AIROHA_PWM_DUTY_FULL && + duty_tick =3D=3D duty_tick_bucket) + return i; + } + + return unused; +} + +static void airoha_pwm_release_bucket_config(struct airoha_pwm *pc, + unsigned int hwpwm) +{ + int bucket; + + /* Nothing to clear, PWM channel never used */ + if (!(pc->initialized & BIT_ULL(hwpwm))) + return; + + bucket =3D pc->channel_bucket[hwpwm]; + pc->buckets[bucket].used &=3D ~BIT_ULL(hwpwm); +} + +static int airoha_pwm_consume_generator(struct airoha_pwm *pc, + u64 duty_ns, u64 period_ns, + unsigned int hwpwm) +{ + int bucket; + + /* + * Search for a bucket that already satisfy duty and period + * or an unused one. + * If not found, -1 is returned. + */ + bucket =3D airoha_pwm_get_generator(pc, duty_ns, period_ns); + if (bucket < 0) + return bucket; + + airoha_pwm_release_bucket_config(pc, hwpwm); + pc->buckets[bucket].used |=3D BIT_ULL(hwpwm); + pc->buckets[bucket].period_ns =3D period_ns; + pc->buckets[bucket].duty_ns =3D duty_ns; + + return bucket; +} + +static int airoha_pwm_sipo_init(struct airoha_pwm *pc) +{ + u32 val; + + if (!(pc->initialized >> AIROHA_PWM_NUM_GPIO)) + return 0; + + regmap_clear_bits(pc->regmap, AIROHA_PWM_REG_SIPO_FLASH_MODE_CFG, + AIROHA_PWM_SERIAL_GPIO_MODE_74HC164); + + /* Configure shift register timings, use 32x divisor */ + regmap_write(pc->regmap, AIROHA_PWM_REG_SGPIO_CLK_DIVR, + FIELD_PREP(AIROHA_PWM_SGPIO_CLK_DIVR, 0x3)); + + /* + * The actual delay is clock + 1. + * Notice that clock delay should not be greater + * than (divisor / 2) - 1. + * Set to 0 by default. (aka 1) + */ + regmap_write(pc->regmap, AIROHA_PWM_REG_SGPIO_CLK_DLY, 0x0); + + /* + * It it necessary to after muxing explicitly shift out all + * zeroes to initialize the shift register before enabling PWM + * mode because in PWM mode SIPO will not start shifting until + * it needs to output a non-zero value (bit 31 of led_data + * indicates shifting in progress and it must return to zero + * before led_data can be written or PWM mode can be set) + */ + if (regmap_read_poll_timeout(pc->regmap, AIROHA_PWM_REG_SGPIO_LED_DATA, v= al, + !(val & AIROHA_PWM_SGPIO_LED_DATA_SHIFT_FLAG), + 10, 200 * USEC_PER_MSEC)) + return -ETIMEDOUT; + + regmap_clear_bits(pc->regmap, AIROHA_PWM_REG_SGPIO_LED_DATA, + AIROHA_PWM_SGPIO_LED_DATA_DATA); + if (regmap_read_poll_timeout(pc->regmap, AIROHA_PWM_REG_SGPIO_LED_DATA, v= al, + !(val & AIROHA_PWM_SGPIO_LED_DATA_SHIFT_FLAG), + 10, 200 * USEC_PER_MSEC)) + return -ETIMEDOUT; + + /* Set SIPO in PWM mode */ + regmap_set_bits(pc->regmap, AIROHA_PWM_REG_SIPO_FLASH_MODE_CFG, + AIROHA_PWM_SERIAL_GPIO_FLASH_MODE); + + return 0; +} + +static void airoha_pwm_calc_bucket_config(struct airoha_pwm *pc, int bucke= t, + u64 duty_ns, u64 period_ns) +{ + u32 period_tick, duty_tick; + u32 mask, shift, val; + u64 offset; + + airoha_pwm_get_ticks_from_ns(period_ns, &period_tick, + duty_ns, &duty_tick); + + offset =3D bucket; + shift =3D do_div(offset, AIROHA_PWM_BUCKET_PER_CYCLE_CFG); + shift =3D AIROHA_PWM_REG_CYCLE_CFG_SHIFT(shift); + + /* Configure frequency divisor */ + mask =3D AIROHA_PWM_WAVE_GEN_CYCLE << shift; + val =3D FIELD_PREP(AIROHA_PWM_WAVE_GEN_CYCLE, period_tick) << shift; + regmap_update_bits(pc->regmap, AIROHA_PWM_REG_CYCLE_CFG_VALUE(offset), ma= sk, val); + + offset =3D bucket; + shift =3D do_div(offset, AIROHA_PWM_BUCKET_PER_FLASH_PROD); + shift =3D AIROHA_PWM_REG_GPIO_FLASH_PRD_SHIFT(shift); + + /* Configure duty cycle */ + mask =3D AIROHA_PWM_GPIO_FLASH_PRD_HIGH << shift; + val =3D FIELD_PREP(AIROHA_PWM_GPIO_FLASH_PRD_HIGH, duty_tick) << shift; + regmap_update_bits(pc->regmap, AIROHA_PWM_REG_GPIO_FLASH_PRD_SET(offset), + mask, val); + + mask =3D AIROHA_PWM_GPIO_FLASH_PRD_LOW << shift; + val =3D FIELD_PREP(AIROHA_PWM_GPIO_FLASH_PRD_LOW, + AIROHA_PWM_DUTY_FULL - duty_tick) << shift; + regmap_update_bits(pc->regmap, AIROHA_PWM_REG_GPIO_FLASH_PRD_SET(offset), + mask, val); +} + +static void airoha_pwm_config_flash_map(struct airoha_pwm *pc, + unsigned int hwpwm, int index) +{ + u32 addr, shift; + + airoha_pwm_get_flash_map_addr_and_shift(hwpwm, &addr, &shift); + + /* index -1 means disable PWM channel */ + if (index < 0) { + /* + * Change of waveform takes effect immediately but + * disabling has some delay so to prevent glitching + * only the enable bit is touched when disabling. + * Duty cycle can't be set to 0 as it might be shared with + * others channels with same duty cycle. + */ + regmap_clear_bits(pc->regmap, addr, + AIROHA_PWM_GPIO_FLASH_EN << shift); + return; + } + + regmap_update_bits(pc->regmap, addr, + AIROHA_PWM_GPIO_FLASH_SET_ID << shift, + FIELD_PREP(AIROHA_PWM_GPIO_FLASH_SET_ID, index) << shift); + regmap_set_bits(pc->regmap, addr, AIROHA_PWM_GPIO_FLASH_EN << shift); +} + +static int airoha_pwm_config(struct airoha_pwm *pc, struct pwm_device *pwm, + u64 duty_ns, u64 period_ns) +{ + int bucket, hwpwm =3D pwm->hwpwm; + + bucket =3D airoha_pwm_consume_generator(pc, duty_ns, period_ns, + hwpwm); + if (bucket < 0) + return -EBUSY; + + /* + * For SIPO reinit is always needed to trigger the shift register chip + * and apply the new flash configuration. + */ + if (!(pc->initialized & BIT_ULL(hwpwm)) && hwpwm >=3D AIROHA_PWM_NUM_GPIO) + airoha_pwm_sipo_init(pc); + + airoha_pwm_calc_bucket_config(pc, bucket, duty_ns, period_ns); + airoha_pwm_config_flash_map(pc, hwpwm, bucket); + + pc->initialized |=3D BIT_ULL(hwpwm); + pc->channel_bucket[hwpwm] =3D bucket; + + return 0; +} + +static void airoha_pwm_disable(struct pwm_chip *chip, struct pwm_device *p= wm) +{ + struct airoha_pwm *pc =3D pwmchip_get_drvdata(chip); + + /* Disable PWM and release the bucket */ + airoha_pwm_config_flash_map(pc, pwm->hwpwm, -1); + airoha_pwm_release_bucket_config(pc, pwm->hwpwm); + + pc->initialized &=3D ~BIT_ULL(pwm->hwpwm); + if (!(pc->initialized >> AIROHA_PWM_NUM_GPIO)) + regmap_clear_bits(pc->regmap, AIROHA_PWM_REG_SIPO_FLASH_MODE_CFG, + AIROHA_PWM_SERIAL_GPIO_FLASH_MODE); +} + +static int airoha_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct airoha_pwm *pc =3D pwmchip_get_drvdata(chip); + u64 period_ns =3D state->period; + + /* Only normal polarity is supported */ + if (state->polarity =3D=3D PWM_POLARITY_INVERSED) + return -EINVAL; + + if (!state->enabled) { + airoha_pwm_disable(chip, pwm); + return 0; + } + + if (period_ns < AIROHA_PWM_PERIOD_TICK_NS) + return -EINVAL; + + return airoha_pwm_config(pc, pwm, state->duty_cycle, period_ns); +} + +static int airoha_pwm_get_state(struct pwm_chip *chip, struct pwm_device *= pwm, + struct pwm_state *state) +{ + struct airoha_pwm *pc =3D pwmchip_get_drvdata(chip); + int ret, hwpwm =3D pwm->hwpwm; + u32 addr, shift, val; + u8 bucket; + + airoha_pwm_get_flash_map_addr_and_shift(hwpwm, &addr, &shift); + + ret =3D regmap_read(pc->regmap, addr, &val); + if (ret) + return ret; + + state->enabled =3D FIELD_GET(AIROHA_PWM_GPIO_FLASH_EN, val >> shift); + if (!state->enabled) + return 0; + + state->polarity =3D PWM_POLARITY_NORMAL; + + bucket =3D FIELD_GET(AIROHA_PWM_GPIO_FLASH_SET_ID, val >> shift); + state->period =3D pc->buckets[bucket].period_ns; + state->duty_cycle =3D pc->buckets[bucket].duty_ns; + + return 0; +} + +static const struct pwm_ops airoha_pwm_ops =3D { + .apply =3D airoha_pwm_apply, + .get_state =3D airoha_pwm_get_state, +}; + +static int airoha_pwm_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct airoha_pwm *pc; + struct pwm_chip *chip; + int i, ret; + + chip =3D devm_pwmchip_alloc(dev, AIROHA_PWM_MAX_CHANNELS, sizeof(*pc)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + chip->ops =3D &airoha_pwm_ops; + pc =3D pwmchip_get_drvdata(chip); + + pc->regmap =3D device_node_to_regmap(dev->parent->of_node); + if (IS_ERR(pc->regmap)) + return dev_err_probe(dev, PTR_ERR(pc->regmap), "Failed to get PWM regmap= \n"); + + /* Fill buckets with initial values */ + for (i =3D 0; i < AIROHA_PWM_NUM_BUCKETS; i++) + airoha_pwm_fill_bucket(pc, i); + + ret =3D devm_pwmchip_add(&pdev->dev, chip); + if (ret) + return dev_err_probe(dev, ret, "Failed to add PWM chip\n"); + + return 0; +} + +static const struct of_device_id airoha_pwm_of_match[] =3D { + { .compatible =3D "airoha,en7581-pwm" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, airoha_pwm_of_match); + +static struct platform_driver airoha_pwm_driver =3D { + .driver =3D { + .name =3D "pwm-airoha", + .of_match_table =3D airoha_pwm_of_match, + }, + .probe =3D airoha_pwm_probe, +}; +module_platform_driver(airoha_pwm_driver); + +MODULE_AUTHOR("Lorenzo Bianconi "); +MODULE_AUTHOR("Markus Gothe "); +MODULE_AUTHOR("Benjamin Larsson "); +MODULE_DESCRIPTION("Airoha EN7581 PWM driver"); +MODULE_LICENSE("GPL"); --=20 2.47.1