Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions Documentation/devicetree/bindings/gpio/apple,smc-pcio-gpio.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# SPDX-License-Identifier: GPL-2.0-only
%YAML 1.2
---
$id: http://devicetree.org/schemas/gpio/apple,smc-pcio-gpio.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#

title: Apple SMC pcIO output GPIOs

maintainers:
- Dj <hey@djdev.me>

description: |
The writable eight-byte pcIO SMC key controls outputs through board
command templates. Each template has its low 16 bits clear; the driver
inserts a boolean output level and sends the little-endian command.
Input reads and mode changes are not supported by this interface.

properties:
compatible:
const: apple,smc-pcio-gpio

gpio-controller: true

'#gpio-cells':
const: 2

apple,output-commands:
$ref: /schemas/types.yaml#/definitions/uint64-array
minItems: 1
maxItems: 64
uniqueItems: true
description: Unique command templates with low 16 bits clear, in GPIO order

required:
- compatible
- gpio-controller
- '#gpio-cells'
- apple,output-commands

additionalProperties: false
7 changes: 5 additions & 2 deletions Documentation/devicetree/bindings/mfd/apple,smc.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) AND GPL-2.0-only
%YAML 1.2
---
$id: http://devicetree.org/schemas/mfd/apple,smc.yaml#
Expand All @@ -16,6 +16,7 @@ description:
properties:
compatible:
oneOf:
- const: apple,t6050-smc
- items:
- enum:
- apple,t6020-smc
Expand Down Expand Up @@ -47,7 +48,9 @@ properties:
maxItems: 1

gpio:
$ref: /schemas/gpio/apple,smc-gpio.yaml
oneOf:
- $ref: /schemas/gpio/apple,smc-gpio.yaml
- $ref: /schemas/gpio/apple,smc-pcio-gpio.yaml

reboot:
$ref: /schemas/power/reset/apple,smc-reboot.yaml
Expand Down
73 changes: 72 additions & 1 deletion drivers/gpio/gpio-macsmc.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
// SPDX-License-Identifier: (GPL-2.0-only OR MIT) AND GPL-2.0-only
/*
* Apple SMC GPIO driver
* Copyright The Asahi Linux Contributors
Expand All @@ -15,6 +15,7 @@
#include <linux/mfd/macsmc.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/unaligned.h>

#define MAX_GPIO 64

Expand Down Expand Up @@ -101,6 +102,7 @@ struct macsmc_gpio {
struct apple_smc *smc;
struct gpio_chip gc;

u64 *output_commands;
int first_index;
smc_key base_key;

Expand Down Expand Up @@ -342,6 +344,72 @@ static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc,
return 0;
}

/* Each DT template identifies one output; its low 16 bits carry the value. */
static int macsmc_pcio_set(struct gpio_chip *gc, unsigned int offset, int value)
{
struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
u8 command[8];
int ret;

put_unaligned_le64(smcgp->output_commands[offset] | !!value, command);
ret = apple_smc_write(smcgp->smc, SMC_KEY(pcIO), command, sizeof(command));
/* SMC writes return status, not a byte count. */
return ret < 0 ? ret : 0;
}

static int macsmc_pcio_get(struct gpio_chip *gc, unsigned int offset)
{
/* The native output command has no established input/readback operation. */
return -EOPNOTSUPP;
}

static int macsmc_pcio_get_direction(struct gpio_chip *gc, unsigned int offset)
{
return GPIO_LINE_DIRECTION_OUT;
}

static int macsmc_pcio_probe(struct macsmc_gpio *smcgp)
{
struct device *dev = smcgp->dev;
struct apple_smc_key_info info;
int count, ret;

ret = apple_smc_get_key_info(smcgp->smc, SMC_KEY(pcIO), &info);
if (ret < 0)
return ret;
if (info.size != 8 || !(info.flags & APPLE_SMC_WRITABLE))
return dev_err_probe(dev, -EINVAL, "pcIO is not an eight-byte output key\n");
count = of_property_count_u64_elems(dev->of_node, "apple,output-commands");
if (count <= 0 || count > MAX_GPIO)
return -EINVAL;
smcgp->output_commands = devm_kcalloc(dev, count, sizeof(u64), GFP_KERNEL);
if (!smcgp->output_commands)
return -ENOMEM;
ret = of_property_read_u64_array(dev->of_node, "apple,output-commands",
smcgp->output_commands, count);
if (ret)
return ret;
for (int i = 0; i < count; i++) {
if (smcgp->output_commands[i] & 0xffff)
return -EINVAL;
for (int j = 0; j < i; j++)
if (smcgp->output_commands[i] == smcgp->output_commands[j])
return -EINVAL;
}

smcgp->gc.label = "macsmc-pcio-output";
smcgp->gc.owner = THIS_MODULE;
smcgp->gc.get = macsmc_pcio_get;
smcgp->gc.set = macsmc_pcio_set;
smcgp->gc.direction_output = macsmc_pcio_set;
smcgp->gc.get_direction = macsmc_pcio_get_direction;
smcgp->gc.can_sleep = true;
smcgp->gc.ngpio = count;
smcgp->gc.base = -1;
smcgp->gc.parent = dev;
return devm_gpiochip_add_data(dev, &smcgp->gc, smcgp);
}

struct macsmc_gpio_of_match_data {
smc_key base_key;
};
Expand All @@ -360,6 +428,8 @@ static int macsmc_gpio_probe(struct platform_device *pdev)

smcgp->dev = &pdev->dev;
smcgp->smc = smc;
if (device_is_compatible(&pdev->dev, "apple,smc-pcio-gpio"))
return macsmc_pcio_probe(smcgp);
smcgp->base_key = data ? data->base_key : _SMC_KEY("gP\0\0");
smcgp->j700_pcio = of_machine_is_compatible("apple,j700") &&
device_is_compatible(&pdev->dev, "apple,smc-gpio");
Expand Down Expand Up @@ -405,6 +475,7 @@ static const struct macsmc_gpio_of_match_data macsmc_gpio_low_data = {
};

static const struct of_device_id macsmc_gpio_of_table[] = {
{ .compatible = "apple,smc-pcio-gpio" },
{
.compatible = "apple,smc-gpio",
.data = &macsmc_gpio_up_data,
Expand Down
35 changes: 30 additions & 5 deletions drivers/mfd/macsmc.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
// SPDX-License-Identifier: (GPL-2.0-only OR MIT) AND GPL-2.0-only
/*
* Apple SMC (System Management Controller) MFD driver
*
Expand Down Expand Up @@ -56,6 +56,16 @@ static const struct mfd_cell apple_smc_devs[] = {
MFD_CELL_OF("macsmc-rtc", NULL, NULL, 0, 0, "apple,smc-rtc"),
};

/* This profile exposes notifications and the explicitly described outputs. */
static const struct mfd_cell apple_smc_t6050_devs[] = {
MFD_CELL_NAME("macsmc-input"),
MFD_CELL_NAME("macsmc-power"),
MFD_CELL_OF("macsmc-hwmon", NULL, NULL, 0, 0, "apple,smc-hwmon"),
};

static const struct mfd_cell apple_smc_pcio_dev =
MFD_CELL_OF("macsmc-gpio", NULL, NULL, 0, 0, "apple,smc-pcio-gpio");

/* The second GPIO bank ('gp' keys) only exists on some SMCs. */
static const struct mfd_cell apple_smc_low_gpio_dev =
MFD_CELL_OF("macsmc-low-gpio", NULL, NULL, 0, 0, "apple,smc-low-gpio");
Expand Down Expand Up @@ -242,8 +252,8 @@ int apple_smc_get_key_info(struct apple_smc *smc, smc_key key, struct apple_smc_
u8 key_info[6];
int ret;

/* Keep the shared reply buffer stable until its metadata is copied. */
guard(mutex)(&smc->mutex);

ret = apple_smc_cmd_locked(smc, SMC_MSG_GET_KEY_INFO, key, 0, 0, NULL);
if (ret >= 0 && info) {
apple_smc_read_sram(smc, key_info, sizeof(key_info));
Expand Down Expand Up @@ -529,12 +539,26 @@ static int apple_smc_probe(struct platform_device *pdev)
if (ret)
return ret;

ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE,
apple_smc_devs, ARRAY_SIZE(apple_smc_devs),
NULL, 0, NULL);
if (of_device_is_compatible(dev->of_node, "apple,t6050-smc"))
ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE,
apple_smc_t6050_devs,
ARRAY_SIZE(apple_smc_t6050_devs), NULL, 0, NULL);
else
ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE,
apple_smc_devs, ARRAY_SIZE(apple_smc_devs),
NULL, 0, NULL);
if (ret)
return dev_err_probe(smc->dev, ret, "Failed to register sub-devices");

np = of_get_compatible_child(dev->of_node, "apple,smc-pcio-gpio");
if (np) {
of_node_put(np);
ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE,
&apple_smc_pcio_dev, 1, NULL, 0, NULL);
if (ret)
return dev_err_probe(dev, ret, "Failed to register pcIO outputs\n");
}

/*
* Register the second GPIO bank only where the device tree describes
* it: the MFD core would otherwise create an unbound device and warn
Expand All @@ -554,6 +578,7 @@ static int apple_smc_probe(struct platform_device *pdev)
}

static const struct of_device_id apple_smc_of_match[] = {
{ .compatible = "apple,t6050-smc" },
{ .compatible = "apple,t8103-smc" },
{ .compatible = "apple,smc" },
{},
Expand Down
68 changes: 50 additions & 18 deletions drivers/power/supply/macsmc-power.c
Original file line number Diff line number Diff line change
Expand Up @@ -979,10 +979,34 @@ static int macsmc_power_event(struct notifier_block *nb, unsigned long event, vo
return NOTIFY_DONE;
}

static int macsmc_read_identity(struct apple_smc *smc, smc_key key,
char *out, size_t capacity)
{
struct apple_smc_key_info info;
int ret;

if (!capacity)
return -EINVAL;
out[0] = '\0';
ret = apple_smc_get_key_info(smc, key, &info);
if (ret < 0)
return ret;
if (!info.size || info.size >= capacity || !(info.flags & APPLE_SMC_READABLE))
return -EINVAL;
/* SMC read requests must describe this key, not the destination buffer. */
ret = apple_smc_read(smc, key, out, info.size);
if (ret != info.size) {
out[0] = '\0';
return ret < 0 ? ret : -EIO;
}
out[info.size] = '\0';
return 0;
}

static int macsmc_power_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
struct apple_smc *smc;
struct power_supply_config psy_cfg = {};
struct apple_smc_key_info info;
struct macsmc_power *power;
Expand All @@ -995,6 +1019,9 @@ static int macsmc_power_probe(struct platform_device *pdev)
enum power_supply_property *props;
size_t nprops;

if (!dev->parent)
return dev_err_probe(dev, -ENODEV, "Missing SMC parent\n");
smc = dev_get_drvdata(dev->parent);
if (!smc)
return -ENODEV;

Expand Down Expand Up @@ -1108,17 +1135,22 @@ static int macsmc_power_probe(struct platform_device *pdev)
of_machine_is_compatible("apple,j700");
power->status_from_current = of_machine_is_compatible("apple,j700");

/* Reset "Optimised Battery Charging" flags to default state */
if (power->has_chte)
apple_smc_write_u32(smc, SMC_KEY(CHTE), 0);
else if (power->has_ch0c)
apple_smc_write_u8(smc, SMC_KEY(CH0C), 0);

if (power->has_ch0i)
apple_smc_write_u8(smc, SMC_KEY(CH0I), 0);

apple_smc_write_u8(smc, SMC_KEY(CH0K), 0);
apple_smc_write_u8(smc, SMC_KEY(CH0B), 0);
/* T6050 telemetry discovery must preserve firmware charge policy.
* Do not reset charging controls merely to publish the battery.
*/
if (!of_device_is_compatible(dev->parent->of_node, "apple,t6050-smc")) {
/* Reset "Optimised Battery Charging" flags to default state. */
if (power->has_chte)
apple_smc_write_u32(smc, SMC_KEY(CHTE), 0);
else if (power->has_ch0c)
apple_smc_write_u8(smc, SMC_KEY(CH0C), 0);

if (power->has_ch0i)
apple_smc_write_u8(smc, SMC_KEY(CH0I), 0);

apple_smc_write_u8(smc, SMC_KEY(CH0K), 0);
apple_smc_write_u8(smc, SMC_KEY(CH0B), 0);
}

/* Detect charge limit method (CHWA vs CHLS) */
if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag) == 0)
Expand Down Expand Up @@ -1150,12 +1182,12 @@ static int macsmc_power_probe(struct platform_device *pdev)
power->batt_desc.num_properties = nprops;

/* Fetch identity strings */
apple_smc_read(smc, SMC_KEY(BMDN), power->model_name,
sizeof(power->model_name) - 1);
apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number,
sizeof(power->serial_number) - 1);
apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date,
sizeof(power->mfg_date) - 1);
macsmc_read_identity(smc, SMC_KEY(BMDN), power->model_name,
sizeof(power->model_name));
macsmc_read_identity(smc, SMC_KEY(BMSN), power->serial_number,
sizeof(power->serial_number));
macsmc_read_identity(smc, SMC_KEY(BMDT), power->mfg_date,
sizeof(power->mfg_date));

apple_smc_read_u8(power->smc, SMC_KEY(BNCB), &power->num_cells);
power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells;
Expand Down
40 changes: 40 additions & 0 deletions tools/testing/selftests/aurora-apif/smc_charge_policy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-only
"""Ensure T6050 battery discovery cannot reset charging policy."""
from pathlib import Path
import subprocess, tempfile
from common import block

ROOT=Path(__file__).resolve().parents[4]
source=(ROOT/'drivers/power/supply/macsmc-power.c').read_text()
guarded=block(source,r'^\t\tif \(!of_device_is_compatible\(dev->parent->of_node, "apple,t6050-smc"\)\)')
probe=block(source,r'^static int macsmc_power_probe\(')
assert probe.count('apple_smc_write_')==guarded.count('apple_smc_write_')==5
program=r'''
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
struct device { struct device *parent; bool *of_node; };
struct macsmc_power { bool has_chte,has_ch0c,has_ch0i; };
static int calls;
static bool of_device_is_compatible(bool *node,const char *name){assert(!strcmp(name,"apple,t6050-smc"));return *node;}
#define SMC_KEY(k) #k
static void apple_smc_write_u32(void *smc,const char *key,uint32_t value){(void)smc;assert(value==0);assert(!strcmp(key,"CHTE"));calls++;}
static void apple_smc_write_u8(void *smc,const char *key,uint8_t value){(void)smc;(void)key;assert(value==0);calls++;}
static void policy(struct device *dev, struct macsmc_power *power){void *smc=0;
''' + guarded + r'''
}
int main(void) {
bool t6050=true;struct device parent={.of_node=&t6050},dev={.parent=&parent};
struct macsmc_power p={true,true,true};policy(&dev,&p);assert(calls==0);
t6050=false;policy(&dev,&p);assert(calls==4);
calls=0;p.has_chte=false;policy(&dev,&p);assert(calls==4);
calls=0;p.has_ch0c=false;p.has_ch0i=false;policy(&dev,&p);assert(calls==2);
}
'''
with tempfile.TemporaryDirectory(prefix='smc-charge-') as directory:
d=Path(directory);(d/'test.c').write_text(program)
subprocess.run(['cc','-std=gnu11','-Wall','-Wextra','-Werror','-fsanitize=address,undefined',str(d/'test.c'),'-o',str(d/'test')],check=True)
subprocess.run([str(d/'test')],check=True)
print('T6050 probe preserves charge policy; older-platform reset behavior unchanged')
Loading