From nobody Mon Jun 8 06:35:54 2026 Received: from outbound7.mail.transip.nl (outbound7.mail.transip.nl [136.144.136.7]) (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 C4FCC286D7D; Wed, 3 Jun 2026 07:15:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=136.144.136.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780470929; cv=none; b=Xwq9+5saE5HnMrHsH0BtbfjxHzb/iC/JlMnWggXWglYNUHPwnBlbMbY516gaYWAcIH44ZbonNR/iXfaTSY5WAw/YApCksrBEuLBBPnIw0wRHEf/9adljQdQaa5OpWGWEXUTAXScEs14ofyg3bIn3FYKSLjgR80UzlMose+MM7aQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780470929; c=relaxed/simple; bh=CBox3QeLB8bD5zr5Zk/ou2hsAHr1V3tPZbRM4BFKHsM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=cGP9QSW3CVVYo7yR72oHCofejITeFBMyKifOhBsMo4OaIC0k5L4nC4siXzN7ubeSNAbDy3Gvr4jyBa2CRr9/o4ECeHk2HeI5niOMLosvbgjdmdplKOj9TFHj813T4UmjGM9VWSPOyTnmmTDnvI/uqISubqD6du6VLnK5pssDV1s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=herrie.org; spf=pass smtp.mailfrom=herrie.org; dkim=pass (2048-bit key) header.d=herrie.org header.i=@herrie.org header.b=vweiJbfx; arc=none smtp.client-ip=136.144.136.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=herrie.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=herrie.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=herrie.org header.i=@herrie.org header.b="vweiJbfx" Received: from submission9.mail.transip.nl (unknown [10.103.8.160]) by outbound7.mail.transip.nl (Postfix) with ESMTP id 4gVf9P3pr7zQvtGC; Wed, 3 Jun 2026 09:15:21 +0200 (CEST) Received: from herrie-desktop.. (180-93-184-31.ftth.glasoperator.nl [31.184.93.180]) by submission9.mail.transip.nl (Postfix) with ESMTPA id 4gVf9N71GPz3NZKgV; Wed, 3 Jun 2026 09:15:20 +0200 (CEST) From: Herman van Hazendonk To: sre@kernel.org Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Herman van Hazendonk Subject: [PATCH 1/2] dt-bindings: power: supply: maxim,max8903: add DC and USB input current-limit controls Date: Wed, 3 Jun 2026 09:15:18 +0200 Message-ID: <20260603071519.807604-2-github.com@herrie.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260603071519.807604-1-github.com@herrie.org> References: <20260603071519.807604-1-github.com@herrie.org> 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 X-Scanned-By: ClueGetter at submission9.mail.transip.nl DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=transip-a; d=herrie.org; t=1780470921; h=from:subject:to:cc: references:in-reply-to:date:mime-version; bh=etAnj7fgonFdKVbkoiHP4F3EeQb64hWRqIT01hc+eX4=; b=vweiJbfx0EdZUupXRNc053qs50nOGApHgUkaQzWHesG2QhLBI/TTfBsbDGR/u+SdI45jEG iIdlE7LTS5JfHkKq0cClDKAa8OTkfY8B42TZg9yCD3ZXculZ96nRFz3ipw17Y0zPx8GrO3 4iPiAdSHZt+/V15TMzUvYmxsUucXcbP//4HYDiO2iVWoZX8aUljEJIWC400pntntNXDTWX Zc/738zbX5Nn/EdyQ1ekZNB8LjceSQcpBFr4U0+khC47dLugRsDopnW2jv4qydz6zaK1p9 Jtf3LaRy9g3n0Uq4pl3El411Q9PfztpliP3pVfR227ja2sAYlH8BWrfPnv3zIA== X-Report-Abuse-To: abuse@transip.nl Content-Type: text/plain; charset="utf-8" Add four optional properties to the MAX8903 charger binding to describe board-level GPIO control of the DC and USB input current limits: DC input (TA / DOK pin): - dc-current-limit-gpios (1..4 GPIOs): mux control lines feeding the MAX8903 IDC resistor mux; - dc-current-limit-mapping (uint32-matrix of {microamps, gpio_bit_pattern} pairs): the available current levels and the GPIO bit pattern that selects each level. USB input (USB / UOK pin): - usb-current-limit-gpios: a single GPIO driving the IUSB tri-state pin (low / high); - usb-current-limit-values: the two microamp values that the IUSB pin selects. These let userspace clamp the input draw via the standard POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT power_supply attribute. The HP TouchPad uses both: two TLMM lines select between 0.5 A, 1.0 A, 1.5 A and 2.0 A DC input current limits behind the MAX8903B charger, and a third TLMM line picks the IUSB 100 mA / 500 mA limit. These are purely additive; existing platforms remain unaffected. Signed-off-by: Herman van Hazendonk --- .../bindings/power/supply/maxim,max8903.yaml | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max8903.y= aml b/Documentation/devicetree/bindings/power/supply/maxim,max8903.yaml index 86af38378999..5e970ebc08df 100644 --- a/Documentation/devicetree/bindings/power/supply/maxim,max8903.yaml +++ b/Documentation/devicetree/bindings/power/supply/maxim,max8903.yaml @@ -44,6 +44,41 @@ properties: maxItems: 1 description: USB suspend pin (active high, output) =20 + dc-current-limit-gpios: + minItems: 1 + maxItems: 4 + description: + GPIOs controlling DC input current limit via resistor mux. + Used with dc-current-limit-mapping to select charging current. + + dc-current-limit-mapping: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + minItems: 2 + maxItems: 16 + description: | + Array of (current_microamps, gpio_bit_pattern) pairs defining availa= ble + DC current limits. The gpio_bit_pattern is applied to dc-current-lim= it-gpios + to select that current level. + items: + items: + - description: Current limit in microamps + - description: GPIO bit pattern value + + usb-current-limit-gpios: + maxItems: 1 + description: + GPIO controlling USB input current limit. + Low =3D usb-current-limit-values[0], High =3D usb-current-limit-valu= es[1]. + + usb-current-limit-values: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 2 + maxItems: 2 + default: [100000, 500000] + description: + USB current limits in microamps for GPIO low and high states. + Default is 100mA (low) and 500mA (high) per USB specification. + required: - compatible =20 @@ -65,3 +100,26 @@ examples: chg-gpios =3D <&gpio3 15 GPIO_ACTIVE_LOW>; cen-gpios =3D <&gpio2 5 GPIO_ACTIVE_LOW>; }; + - | + /* Example with DC and USB current limit control */ + #include + charger-with-current-limit { + compatible =3D "maxim,max8903"; + dok-gpios =3D <&gpio2 3 GPIO_ACTIVE_LOW>; + flt-gpios =3D <&gpio2 2 GPIO_ACTIVE_LOW>; + chg-gpios =3D <&gpio3 15 GPIO_ACTIVE_LOW>; + cen-gpios =3D <&gpio2 5 GPIO_ACTIVE_LOW>; + dcm-gpios =3D <&gpio2 6 GPIO_ACTIVE_HIGH>; + + /* DC input current limit via IDC resistor mux */ + dc-current-limit-gpios =3D <&gpio1 0 GPIO_ACTIVE_HIGH>, + <&gpio1 1 GPIO_ACTIVE_HIGH>; + dc-current-limit-mapping =3D <750000 0>, /* GPIO[1:0]=3D0b00 */ + <900000 1>, /* GPIO[1:0]=3D0b01 */ + <1400000 3>, /* GPIO[1:0]=3D0b11 */ + <2000000 2>; /* GPIO[1:0]=3D0b10 */ + + /* USB current control */ + usb-current-limit-gpios =3D <&gpio1 2 GPIO_ACTIVE_HIGH>; + usb-current-limit-values =3D <100000 500000>; /* 100mA / 500mA */ + }; --=20 2.43.0 From nobody Mon Jun 8 06:35:54 2026 Received: from outbound11.mail.transip.nl (outbound11.mail.transip.nl [136.144.136.18]) (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 6267C399CE6; Wed, 3 Jun 2026 07:15:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=136.144.136.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780470937; cv=none; b=iS871DIV2xA/hDEciU90+ubXhlGsQ+GUzhusibuzyqnYzaHlzwsrJzKJFZRuhAxR7hCeoYlq4RRc0n9W84OCm4OsvQW0nD0rYwJhj63q9sralIjUcxVfW+tX23RPQ8vtCpnB0GTVShOgGSjT0HLU6jxjAWVoq/YcifqU90iAcpA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780470937; c=relaxed/simple; bh=zxHoyjs6y/jmxNfBUNRe8Y4jk7x+5/HosJWesooToGY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=rpVZMxdwjuwcFEM8GsdpdfGk2UG9B7oUr/4y5fY12pmbkLpB3urDddA7IYV1bM18qY/OT1n04lXyA6Zg+6hzHEIXlA0RRSgBV0il7fD42pcQQ/fe1VDs+Lh3Eqe3qvCIEy2FNDgqNdiPDzoCQjvdZ0snvVxhR2loUp9IDppxA+Q= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=herrie.org; spf=pass smtp.mailfrom=herrie.org; dkim=pass (2048-bit key) header.d=herrie.org header.i=@herrie.org header.b=FGiAGq78; arc=none smtp.client-ip=136.144.136.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=herrie.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=herrie.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=herrie.org header.i=@herrie.org header.b="FGiAGq78" Received: from submission9.mail.transip.nl (unknown [10.103.8.160]) by outbound11.mail.transip.nl (Postfix) with ESMTP id 4gVf9R1HYvzkQMbd; Wed, 3 Jun 2026 09:15:23 +0200 (CEST) Received: from herrie-desktop.. (180-93-184-31.ftth.glasoperator.nl [31.184.93.180]) by submission9.mail.transip.nl (Postfix) with ESMTPA id 4gVf9P4YL7z3NZKgV; Wed, 3 Jun 2026 09:15:21 +0200 (CEST) From: Herman van Hazendonk To: sre@kernel.org Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Herman van Hazendonk Subject: [PATCH 2/2] power: supply: max8903: add DC and USB input current-limit GPIO controls Date: Wed, 3 Jun 2026 09:15:19 +0200 Message-ID: <20260603071519.807604-3-github.com@herrie.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260603071519.807604-1-github.com@herrie.org> References: <20260603071519.807604-1-github.com@herrie.org> 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 X-Scanned-By: ClueGetter at submission9.mail.transip.nl DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=transip-a; d=herrie.org; t=1780470922; h=from:subject:to:cc: references:in-reply-to:date:mime-version:content-type; bh=IkvPeEvcPZJF24rL45B2rO35dUlyNTzo5GHUZFfz18Q=; b=FGiAGq78jyyEklyqbnHAX1weKTpm+5yvRzq8b3bIYxJ4ObfHm3kJVuH34FinGqyD+npWmp vOiylZja2cdFGDr7BJf9VP2S60g/dcy+vJQorqFccgKsaNDqY+cxTPvEQdG+lYnsplJgAH 05AcUi0aKJdUNStZXzZ6uUt1ftHKYgTTbx+PEK+2nfOc0KQSsOkxygUMXffV2+BF3prg2P 8fpNfffbi6HrYz++ld28oNO9G3O8wF2SKLIqneH5bC1PtIbO32KauCmAkGfNfii4Cp+HKz CZcWYQWkWIn7Pk6FJSAMpwkFDxnLm2tH8+zh0783SPkYRQcO8GCbex2HyuTttQ== X-Report-Abuse-To: abuse@transip.nl Add two optional current-limit knobs surfaced through POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, selected by which input source is currently online: - DC (DOK / TA-IN): a "dc-current-limit-gpios" array drives the GPIOs of an external resistor mux connected to the MAX8903 IDC pin (DC Current-Limit Set Input, pin 11). The IDC pin programs the step-down DC input current limit from 0.5 A to 2 A via R_IDC when the DCM mode pin is logic-high. The DT property "dc-current-limit-mapping" describes the (current_ua, gpio_value) pairs the board can program; the driver picks the largest entry whose limit is <=3D the requested limit. A 0 uA entry, used to stop drawing DC current, is selectable by issuing a 0 uA request (the selection uses a -1 "not found" sentinel rather than tracking best_limit > 0, so the all-zero entry can win). - USB (UOK / USB-IN): a single "usb-current-limit-gpio" drives the MAX8903 IUSB pin (USB Current-Limit Set Input, pin 7). Logic-low selects 100 mA; logic-high selects 500 mA per the MAX8903 spec. The DT property "usb-current-limit-values" overrides those limits for boards with non-standard IUSB thresholds. The requested limit selects the highest of the two configured values that does not exceed the cap: requests at or above the high value pick high, requests at or above the low value pick low, and a request below the low value is rejected with -EINVAL rather than silently programming a higher current that would violate the system power budget. The dispatch in max8903_set_property() to the DC vs USB path needs to match the active source flag set by the corresponding *_ok GPIO IRQ handler; both update sites take a new struct mutex source_lock so the check and the resulting hardware write cannot be torn by a concurrent IRQ flipping the source-online flag mid-decision. The IRQs are requested with IRQF_ONESHOT (threaded), so a sleepable lock is the right primitive in both contexts. max8903_get_property() also takes source_lock briefly to snapshot the source flags and current-limit values so userspace never observes a torn pair of (source-online flag, current-limit ua). dc-current-limit-mapping gpio_value entries are validated at parse against the GPIO array width so a malformed DT value is rejected instead of being silently truncated by gpiod_set_array_value() and selecting the wrong mux level. The mapping is additionally required to contain a gpio_value=3D0 entry: devm_gpiod_get_array_optional() asks for GPIOD_OUT_LOW, so the hardware mux starts at gpio_value 0, and the driver seeds dc_current_limit_ua from the matching map entry. A DT lacking the all-zero entry is rejected with -EINVAL because otherwise the reported INPUT_CURRENT_LIMIT could disagree with the mux state until a set_property write picks a real value. Signed-off-by: Herman van Hazendonk --- drivers/power/supply/max8903_charger.c | 369 ++++++++++++++++++++++++- 1 file changed, 358 insertions(+), 11 deletions(-) diff --git a/drivers/power/supply/max8903_charger.c b/drivers/power/supply/= max8903_charger.c index 45fbaad6c647..19c2b348a045 100644 --- a/drivers/power/supply/max8903_charger.c +++ b/drivers/power/supply/max8903_charger.c @@ -9,11 +9,18 @@ #include #include #include +#include #include +#include #include #include #include =20 +struct max8903_current_limit_mapping { + u32 limit_ua; /* Current limit in microamps */ + u32 gpio_value; /* GPIO bit pattern */ +}; + struct max8903_data { struct device *dev; struct power_supply *psy; @@ -31,6 +38,27 @@ struct max8903_data { struct gpio_desc *flt; /* Fault output */ struct gpio_desc *dcm; /* Current-Limit Mode input (1: DC, 2: USB) */ struct gpio_desc *usus; /* USB Suspend Input (1: suspended) */ + + /* DC current limit control (ISET pins) */ + struct gpio_descs *dc_current_limit_gpios; + struct max8903_current_limit_mapping *dc_current_limit_map; + u32 dc_current_limit_map_size; + u32 dc_current_limit_ua; /* Current setting in uA */ + + /* USB current limit control (IUSB pin) */ + struct gpio_desc *usb_current_limit_gpio; + u32 usb_current_limit_low_ua; /* Current when GPIO low */ + u32 usb_current_limit_high_ua; /* Current when GPIO high */ + u32 usb_current_limit_ua; /* Current setting in uA */ + + /* + * Serialises ta_in / usb_in updates against + * max8903_set_property() which steers the current-limit write to + * the DC or USB path based on which source is currently online. + * The IRQ handlers are requested with IRQF_ONESHOT (threaded), so + * a sleepable mutex is the right primitive in both contexts. + */ + struct mutex source_lock; bool fault; bool usb_in; bool ta_in; @@ -40,6 +68,7 @@ static enum power_supply_property max8903_charger_props[]= =3D { POWER_SUPPLY_PROP_STATUS, /* Charger status output */ POWER_SUPPLY_PROP_ONLINE, /* External power source */ POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */ + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, /* Input current limit */ }; =20 static int max8903_get_property(struct power_supply *psy, @@ -47,6 +76,24 @@ static int max8903_get_property(struct power_supply *psy, union power_supply_propval *val) { struct max8903_data *data =3D power_supply_get_drvdata(psy); + bool ta_in, usb_in; + u32 dc_limit, usb_limit; + + /* + * Snapshot the source flags and current-limit settings under the + * source_lock that the IRQs (max8903_dcin / max8903_usbin) and + * max8903_set_property() take when updating them, so we never + * observe a torn pair of (source-online flag, current-limit ua). + * The gpiod_get_value() reads further down deliberately stay + * outside the lock =E2=80=94 they hit the GPIO controller, not driver + * state, and the IRQs do not touch them under the lock either. + */ + mutex_lock(&data->source_lock); + ta_in =3D data->ta_in; + usb_in =3D data->usb_in; + dc_limit =3D data->dc_current_limit_ua; + usb_limit =3D data->usb_current_limit_ua; + mutex_unlock(&data->source_lock); =20 switch (psp) { case POWER_SUPPLY_PROP_STATUS: @@ -55,21 +102,31 @@ static int max8903_get_property(struct power_supply *p= sy, if (gpiod_get_value(data->chg)) /* CHG asserted */ val->intval =3D POWER_SUPPLY_STATUS_CHARGING; - else if (data->usb_in || data->ta_in) + else if (usb_in || ta_in) val->intval =3D POWER_SUPPLY_STATUS_NOT_CHARGING; else val->intval =3D POWER_SUPPLY_STATUS_DISCHARGING; } break; case POWER_SUPPLY_PROP_ONLINE: - val->intval =3D 0; - if (data->usb_in || data->ta_in) - val->intval =3D 1; + val->intval =3D (ta_in || usb_in) ? 1 : 0; break; case POWER_SUPPLY_PROP_HEALTH: - val->intval =3D POWER_SUPPLY_HEALTH_GOOD; - if (data->fault) - val->intval =3D POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + /* + * data->fault is a single bool toggled from one IRQ + * handler, so a torn read is not possible; no need to + * extend source_lock coverage here. + */ + val->intval =3D data->fault ? POWER_SUPPLY_HEALTH_UNSPEC_FAILURE + : POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + if (ta_in && data->dc_current_limit_gpios) + val->intval =3D dc_limit; + else if (usb_in && data->usb_current_limit_gpio) + val->intval =3D usb_limit; + else + return -ENODATA; break; default: return -EINVAL; @@ -78,6 +135,130 @@ static int max8903_get_property(struct power_supply *p= sy, return 0; } =20 +static int max8903_set_dc_current_limit(struct max8903_data *data, u32 lim= it_ua) +{ + int i, best_idx =3D -1; + u32 best_gpio_value; + /* + * gpio_value is a u32 in the DT mapping and is parse-time + * validated to fit in BIT(ndescs); size the bitmap to the full + * width of the source u32 so a DT with up to 32 dc-current-limit + * GPIOs cannot overflow this stack buffer. + */ + DECLARE_BITMAP(values, BITS_PER_TYPE(u32)); + + if (!data->dc_current_limit_gpios) + return -EOPNOTSUPP; + + /* + * Find the highest supported current <=3D requested. Use a -1 + * "not found" sentinel rather than tracking best_limit > 0 so + * that a 0 uA entry (used to disable charging) can be selected + * by a 0 uA request. + */ + for (i =3D 0; i < data->dc_current_limit_map_size; i++) { + if (data->dc_current_limit_map[i].limit_ua > limit_ua) + continue; + if (best_idx < 0 || + data->dc_current_limit_map[i].limit_ua > + data->dc_current_limit_map[best_idx].limit_ua) + best_idx =3D i; + } + + if (best_idx < 0) + return -EINVAL; + + best_gpio_value =3D data->dc_current_limit_map[best_idx].gpio_value; + bitmap_from_arr32(values, &best_gpio_value, BITS_PER_TYPE(u32)); + gpiod_set_array_value_cansleep(data->dc_current_limit_gpios->ndescs, + data->dc_current_limit_gpios->desc, + data->dc_current_limit_gpios->info, + values); + + data->dc_current_limit_ua =3D data->dc_current_limit_map[best_idx].limit_= ua; + dev_dbg(data->dev, "DC current limit set to %u uA\n", + data->dc_current_limit_ua); + + return 0; +} + +static int max8903_set_usb_current_limit(struct max8903_data *data, u32 li= mit_ua) +{ + u32 selected; + int gpio_val; + + if (!data->usb_current_limit_gpio) + return -EOPNOTSUPP; + + /* + * Pick the highest of the two configured limits that does not + * exceed the requested cap. Mirror the DC path's policy: if + * neither value fits (the request is below even the low limit), + * refuse the request rather than silently program a higher + * current that violates the system power budget. + */ + if (limit_ua >=3D data->usb_current_limit_high_ua) { + selected =3D data->usb_current_limit_high_ua; + gpio_val =3D 1; + } else if (limit_ua >=3D data->usb_current_limit_low_ua) { + selected =3D data->usb_current_limit_low_ua; + gpio_val =3D 0; + } else { + return -EINVAL; + } + + gpiod_set_value_cansleep(data->usb_current_limit_gpio, gpio_val); + data->usb_current_limit_ua =3D selected; + + dev_dbg(data->dev, "USB current limit set to %u uA\n", + data->usb_current_limit_ua); + + return 0; +} + +static int max8903_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max8903_data *data =3D power_supply_get_drvdata(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + /* + * Hold source_lock across the source check and the + * resulting hardware write so the IRQ handler cannot + * flip ta_in/usb_in between them and have us program the + * limit for a source that has just gone offline. + */ + mutex_lock(&data->source_lock); + if (data->ta_in && data->dc_current_limit_gpios) + ret =3D max8903_set_dc_current_limit(data, val->intval); + else if (data->usb_in && data->usb_current_limit_gpio) + ret =3D max8903_set_usb_current_limit(data, val->intval); + else + ret =3D -EINVAL; + mutex_unlock(&data->source_lock); + return ret; + default: + return -EINVAL; + } +} + +static int max8903_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + struct max8903_data *data =3D power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return data->dc_current_limit_gpios || + data->usb_current_limit_gpio; + default: + return 0; + } +} + static irqreturn_t max8903_dcin(int irq, void *_data) { struct max8903_data *data =3D _data; @@ -91,12 +272,21 @@ static irqreturn_t max8903_dcin(int irq, void *_data) * library as the line should be flagged GPIO_ACTIVE_LOW in the device * tree. */ + /* + * Sample the line under source_lock so a concurrent + * max8903_set_property() observes either the old or the new + * state consistently, never a torn read where the lock is held + * but the cached flag is about to be updated. + */ + mutex_lock(&data->source_lock); ta_in =3D gpiod_get_value(data->dok); - - if (ta_in =3D=3D data->ta_in) + if (ta_in =3D=3D data->ta_in) { + mutex_unlock(&data->source_lock); return IRQ_HANDLED; + } =20 data->ta_in =3D ta_in; + mutex_unlock(&data->source_lock); =20 /* Set Current-Limit-Mode 1:DC 0:USB */ if (data->dcm) @@ -150,12 +340,16 @@ static irqreturn_t max8903_usbin(int irq, void *_data) * library as the line should be flagged GPIO_ACTIVE_LOW in the device * tree. */ + /* See ta_in handler: sample the line under the lock. */ + mutex_lock(&data->source_lock); usb_in =3D gpiod_get_value(data->uok); - - if (usb_in =3D=3D data->usb_in) + if (usb_in =3D=3D data->usb_in) { + mutex_unlock(&data->source_lock); return IRQ_HANDLED; + } =20 data->usb_in =3D usb_in; + mutex_unlock(&data->source_lock); =20 /* Do not touch Current-Limit-Mode */ =20 @@ -221,6 +415,148 @@ static irqreturn_t max8903_fault(int irq, void *_data) return IRQ_HANDLED; } =20 +static int max8903_parse_dc_current_limit(struct platform_device *pdev, + struct max8903_data *data) +{ + struct device *dev =3D &pdev->dev; + int ret, i, map_size; + u32 *map; + + data->dc_current_limit_gpios =3D devm_gpiod_get_array_optional(dev, + "dc-current-limit", GPIOD_OUT_LOW); + if (IS_ERR(data->dc_current_limit_gpios)) + return dev_err_probe(dev, PTR_ERR(data->dc_current_limit_gpios), + "failed to get DC current limit GPIOs"); + + if (!data->dc_current_limit_gpios) + return 0; /* Optional feature not present */ + + /* Parse mapping: pairs of (current_ua, gpio_value) */ + map_size =3D device_property_count_u32(dev, "dc-current-limit-mapping"); + if (map_size <=3D 0 || map_size % 2) { + dev_err(dev, "invalid dc-current-limit-mapping\n"); + return -EINVAL; + } + + map =3D devm_kcalloc(dev, map_size, sizeof(*map), GFP_KERNEL); + if (!map) + return -ENOMEM; + + ret =3D device_property_read_u32_array(dev, "dc-current-limit-mapping", + map, map_size); + if (ret) { + dev_err(dev, "failed to read dc-current-limit-mapping\n"); + return ret; + } + + data->dc_current_limit_map_size =3D map_size / 2; + data->dc_current_limit_map =3D devm_kcalloc(dev, + data->dc_current_limit_map_size, + sizeof(*data->dc_current_limit_map), + GFP_KERNEL); + if (!data->dc_current_limit_map) + return -ENOMEM; + + for (i =3D 0; i < data->dc_current_limit_map_size; i++) { + u32 gpio_value =3D map[i * 2 + 1]; + + /* + * gpio_value is the bitmap programmed across the + * dc-current-limit GPIOs, so it cannot represent more + * bits than the GPIO array width. A larger value would + * be silently truncated by gpiod_set_array_value() and + * select the wrong limit; reject it at parse time so + * the bogus DT is visible to the integrator. + */ + if (gpio_value >=3D BIT(data->dc_current_limit_gpios->ndescs)) { + dev_err(dev, + "dc-current-limit-mapping entry %d: gpio_value 0x%x exceeds %u-GPIO ra= nge\n", + i, gpio_value, + data->dc_current_limit_gpios->ndescs); + return -EINVAL; + } + data->dc_current_limit_map[i].limit_ua =3D map[i * 2]; + data->dc_current_limit_map[i].gpio_value =3D gpio_value; + } + + /* + * devm_gpiod_get_array_optional() above asked for GPIOD_OUT_LOW, + * so the hardware mux starts at gpio_value 0. Require the DT + * mapping to include a gpio_value=3D0 entry so the software + * current-limit state has a definite initial value matching the + * hardware. Without this entry we would have to guess and the + * reported INPUT_CURRENT_LIMIT could disagree with what the + * mux is actually wired to until a set_property write picks a + * real value. + */ + for (i =3D 0; i < data->dc_current_limit_map_size; i++) + if (data->dc_current_limit_map[i].gpio_value =3D=3D 0) + break; + if (i =3D=3D data->dc_current_limit_map_size) { + dev_err(dev, + "dc-current-limit-mapping must include a gpio_value=3D0 entry to descri= be the boot-time mux state\n"); + return -EINVAL; + } + data->dc_current_limit_ua =3D data->dc_current_limit_map[i].limit_ua; + + dev_dbg(dev, "DC current limit control: %d levels available, initial %u u= A\n", + data->dc_current_limit_map_size, data->dc_current_limit_ua); + + return 0; +} + +static int max8903_parse_usb_current_limit(struct platform_device *pdev, + struct max8903_data *data) +{ + struct device *dev =3D &pdev->dev; + u32 limits[2]; + int ret; + + data->usb_current_limit_gpio =3D devm_gpiod_get_optional(dev, + "usb-current-limit", GPIOD_OUT_LOW); + if (IS_ERR(data->usb_current_limit_gpio)) + return dev_err_probe(dev, PTR_ERR(data->usb_current_limit_gpio), + "failed to get USB current limit GPIO"); + + if (!data->usb_current_limit_gpio) + return 0; /* Optional feature not present */ + + /* Parse [low_ua, high_ua] values, default to USB spec values */ + ret =3D device_property_read_u32_array(dev, "usb-current-limit-values", + limits, 2); + if (ret) { + /* Default to USB spec values */ + data->usb_current_limit_low_ua =3D 100000; /* 100mA */ + data->usb_current_limit_high_ua =3D 500000; /* 500mA */ + } else { + data->usb_current_limit_low_ua =3D limits[0]; + data->usb_current_limit_high_ua =3D limits[1]; + } + + /* + * max8903_set_usb_current_limit() picks the highest cap that + * doesn't exceed the request by checking >=3Dhigh first then + * >=3Dlow; that policy only works when high > low. Reject DTs + * that hand the property in the wrong order rather than + * silently program a sub-optimal current limit. + */ + if (data->usb_current_limit_high_ua <=3D data->usb_current_limit_low_ua) { + dev_err(dev, + "usb-current-limit-values must be [low, high] with high > low (got low= =3D%u uA, high=3D%u uA)\n", + data->usb_current_limit_low_ua, + data->usb_current_limit_high_ua); + return -EINVAL; + } + + /* Start at low current for safety */ + data->usb_current_limit_ua =3D data->usb_current_limit_low_ua; + + dev_dbg(dev, "USB current limit control: %u/%u uA\n", + data->usb_current_limit_low_ua, data->usb_current_limit_high_ua); + + return 0; +} + static int max8903_setup_gpios(struct platform_device *pdev) { struct max8903_data *data =3D platform_get_drvdata(pdev); @@ -335,17 +671,28 @@ static int max8903_probe(struct platform_device *pdev) return -ENOMEM; =20 data->dev =3D dev; + mutex_init(&data->source_lock); platform_set_drvdata(pdev, data); =20 ret =3D max8903_setup_gpios(pdev); if (ret) return ret; =20 + ret =3D max8903_parse_dc_current_limit(pdev, data); + if (ret) + return ret; + + ret =3D max8903_parse_usb_current_limit(pdev, data); + if (ret) + return ret; + data->psy_desc.name =3D "max8903_charger"; data->psy_desc.type =3D (data->ta_in) ? POWER_SUPPLY_TYPE_MAINS : ((data->usb_in) ? POWER_SUPPLY_TYPE_USB : POWER_SUPPLY_TYPE_BATTERY); data->psy_desc.get_property =3D max8903_get_property; + data->psy_desc.set_property =3D max8903_set_property; + data->psy_desc.property_is_writeable =3D max8903_property_is_writeable; data->psy_desc.properties =3D max8903_charger_props; data->psy_desc.num_properties =3D ARRAY_SIZE(max8903_charger_props); =20 --=20 2.43.0