Skip to content
Closed
4 changes: 3 additions & 1 deletion Documentation/devicetree/bindings/dma/apple,sio.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ title: Apple SIO Coprocessor
description:
SIO is a coprocessor on Apple M1 and later chips (and maybe also on earlier
chips). Its role is to offload SPI, UART and DisplayPort audio transfers,
being a pretend DMA controller.
being a pretend DMA controller. The SoC-specific compatible selects the
firmware protocol and channel configuration. In particular, apple,t6030-sio
requires protocol-10 boot tables before the coprocessor can be started.

maintainers:
- Martin Povišer <povik+lin@cutebit.org>
Expand Down
1 change: 0 additions & 1 deletion arch/arm64/configs/asahi.config
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ CONFIG_USB_XHCI_PCI_ASMEDIA=y
CONFIG_TYPEC_SN201202X=m
CONFIG_RTC_DRV_APPLE_PMU=m
CONFIG_APPLE_SIO=m
CONFIG_APPLE_SIO_T6030=y
CONFIG_APPLE_DART=y
CONFIG_APPLE_DART_J713_DCP=y
CONFIG_APPLE_DOCKCHANNEL=m
Expand Down
20 changes: 3 additions & 17 deletions drivers/dma/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -111,26 +111,12 @@ config APPLE_SIO_KUNIT_TEST
default KUNIT_ALL_TESTS
help
Check which SIO device-tree nodes the probe goes on with: the
generic SIO of the M1 and M2 in every configuration, and a T6030
SIO only with APPLE_SIO_T6030 and protocol-10 boot tables. Run it
both with and without APPLE_SIO_T6030.

config APPLE_SIO_T6030
bool "Apple SIO on the M3 Pro (T6030)"
depends on APPLE_SIO
help
Configure the SIO channels of the T6030 (apple,t6030-sio) the way
its firmware expects them: the DisplayPort audio channels refill
their FIFO at the threshold the system's device tree records for
them (0x600 bytes, not 0x800), and every period descriptor is
published whole. The firmware's boot tables come from the boot
loader, as on the other SoCs; the SIO is only started when they
are protocol-10 tables, which a boot loader without T6030 support
does not build. Without this option a T6030 SIO is left stopped.
generic SIO of the M1 and M2, and a T6030 SIO only with validated
protocol-10 boot tables. The compatible selects the firmware layout.

config APPLE_SIO_T6030_KUNIT_TEST
bool "KUnit tests for the T6030 SIO boot-table check" if !KUNIT_ALL_TESTS
depends on APPLE_SIO=y && APPLE_SIO_T6030 && KUNIT=y
depends on APPLE_SIO=y && KUNIT=y
depends on OF_OVERLAY
default KUNIT_ALL_TESTS
help
Expand Down
46 changes: 38 additions & 8 deletions drivers/dma/apple-sio-probe-test.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/*
* Included by apple-sio.c: which SIO nodes the probe goes on with, with and
* without APPLE_SIO_T6030. The test devices have no register range, so a probe
* Included by apple-sio.c: the compatible selects which firmware tables a
* node needs. The test devices have no register range, so a probe
* that goes on toward the hardware stops there with -EINVAL; a refused node
* returns -ENODEV first.
*/
Expand All @@ -13,16 +13,17 @@ OF_OVERLAY_DECLARE(apple_sio_probe_test);

static void sio_probe_nodes_test(struct kunit *test)
{
const bool t6030 = IS_ENABLED(CONFIG_APPLE_SIO_T6030);
const struct {
const char *path;
int err;
} cases[] = {
/* M1/M2: the generic SIO, either way */
/* M1/M2: the generic SIO */
{ "/sio-probe-test/sio-probe-test-t8103", -EINVAL },
/* T6030: only with its support built, and with protocol-10 tables */
{ "/sio-probe-test/sio-probe-test-t6030-v10", t6030 ? -EINVAL : -ENODEV },
/* T6030: protocol-10 tables select the admitted path */
{ "/sio-probe-test/sio-probe-test-t6030-v10", -EINVAL },
{ "/sio-probe-test/sio-probe-test-t6030-earlier", -ENODEV },
{ "/sio-probe-test/sio-probe-test-t6030-missing", -ENODEV },
{ "/sio-probe-test/sio-probe-test-unknown", -ENODEV },
};
unsigned int i;

Expand All @@ -38,15 +39,44 @@ static void sio_probe_nodes_test(struct kunit *test)
KUNIT_ASSERT_NOT_NULL_MSG(test, pdev->dev.of_node, "%s", cases[i].path);
mask = pdev->dev.coherent_dma_mask;
KUNIT_EXPECT_EQ_MSG(test, sio_probe(pdev), cases[i].err, "%s", cases[i].path);
/* Without its support, a T6030 is refused before its DMA is set up */
if (!t6030 && cases[i].err == -ENODEV)
/* Bad protocol tables are refused before DMA setup. */
if (cases[i].err == -ENODEV)
KUNIT_EXPECT_EQ_MSG(test, pdev->dev.coherent_dma_mask, mask, "%s",
cases[i].path);
}
}

static void sio_match_data_test(struct kunit *test)
{
const struct of_device_id *match;
struct device_node *np;
const struct sio_hw_data *hw;

KUNIT_ASSERT_EQ(test, 0, of_overlay_apply_kunit(test, apple_sio_probe_test));
np = of_find_node_by_path("/sio-probe-test/sio-probe-test-t6030-v10");
KUNIT_ASSERT_NOT_NULL(test, np);
match = of_match_node(sio_of_match, np);
of_node_put(np);
KUNIT_ASSERT_NOT_NULL(test, match);
hw = match->data;
KUNIT_EXPECT_EQ(test, hw->fifo_threshold, 0x600U);
KUNIT_EXPECT_TRUE(test, hw->clear_period_desc);
KUNIT_EXPECT_TRUE(test, hw->check_boot_tables == sio_t6030_check_boot_tables);

np = of_find_node_by_path("/sio-probe-test/sio-probe-test-t8103");
KUNIT_ASSERT_NOT_NULL(test, np);
match = of_match_node(sio_of_match, np);
of_node_put(np);
KUNIT_ASSERT_NOT_NULL(test, match);
hw = match->data;
KUNIT_EXPECT_EQ(test, hw->fifo_threshold, 0x800U);
KUNIT_EXPECT_FALSE(test, hw->clear_period_desc);
KUNIT_EXPECT_FALSE(test, hw->check_boot_tables);
}

static struct kunit_case sio_probe_test_cases[] = {
KUNIT_CASE(sio_probe_nodes_test),
KUNIT_CASE(sio_match_data_test),
{}
};

Expand Down
10 changes: 10 additions & 0 deletions drivers/dma/apple-sio-probe-test.dtso
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@
dma-channels = <8>;
};

sio-probe-test-t6030-missing {
compatible = "apple,t6030-sio", "apple,sio";
dma-channels = <8>;
};

sio-probe-test-unknown {
compatible = "apple,sio-probe-test-unknown";
dma-channels = <8>;
};

v10_w: v10-w {
iommu-addresses = <&v10 0x0 0x30000 0x0 0x4000>, <&v10 0x0 0x34000 0x0 0x20000>,
<&v10 0x0 0x54000 0x0 0x4000>, <&v10 0x0 0x58000 0x0 0x4000>;
Expand Down
79 changes: 39 additions & 40 deletions drivers/dma/apple-sio.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,14 @@ struct sio_chan {

typedef void (*sio_ack_callback)(struct sio_chan *, void *, bool);

struct sio_hw_data {
u32 fifo_threshold;
bool clear_period_desc;
int (*check_boot_tables)(struct device *dev);
};

struct sio_data {
const struct sio_hw_data *hw;
void __iomem *base;
struct dma_device dma;
struct device *dev;
Expand All @@ -130,9 +137,6 @@ struct sio_data {
} tags;

int nchannels;
#if IS_ENABLED(CONFIG_APPLE_SIO_T6030)
bool t6030;
#endif
struct sio_chan channels[];
};

Expand All @@ -155,16 +159,6 @@ static int sio_send_siomsg_atomic(struct sio_data *sio, u64 msg,
void *cookie);
static int sio_call(struct sio_data *sio, u64 msg);

static inline bool sio_is_t6030(struct sio_data *sio)
{
#if IS_ENABLED(CONFIG_APPLE_SIO_T6030)
return sio->t6030;
#else
return false;
#endif
}

#if IS_ENABLED(CONFIG_APPLE_SIO_T6030)
/*
* Boot tables of the T6030 SIO firmware, protocol 10. The boot loader builds them from the ADT
* and passes each one as two apple,sio-firmware-params: the table's IOVA page under its key and
Expand Down Expand Up @@ -320,7 +314,23 @@ static int sio_t6030_check_boot_tables(struct device *dev)
}
return 0;
}
#endif

static const struct sio_hw_data sio_hw_generic = {
.fifo_threshold = 0x800,
};

static const struct sio_hw_data sio_hw_t6030 = {
.fifo_threshold = 0x600,
.clear_period_desc = true,
.check_boot_tables = sio_t6030_check_boot_tables,
};

static const struct of_device_id sio_of_match[] = {
{ .compatible = "apple,t6030-sio", .data = &sio_hw_t6030 },
{ .compatible = "apple,sio", .data = &sio_hw_generic },
{ }
};
MODULE_DEVICE_TABLE(of, sio_of_match);

static struct sio_chan *to_sio_chan(struct dma_chan *chan)
{
Expand Down Expand Up @@ -459,7 +469,7 @@ static struct dma_async_tx_descriptor *sio_prep_dma_cyclic(
* T6030: publish the whole descriptor, its other fields zero;
* a slot may come back from an earlier transfer.
*/
if (sio_is_t6030(siochan->host))
if (siochan->host->hw->clear_period_desc)
memset(d, 0, sizeof(*d));
d->flag = 1; /* not sure what's up with this */
d->iova = buf_addr + period_len * i;
Expand Down Expand Up @@ -839,7 +849,7 @@ static int sio_device_config(struct dma_chan *chan,
* dp-audioN dma-channels: channel, data shape, timeout, FIFO size,
* threshold). T6030's DisplayPort audio channels refill at 0x600.
*/
cfg.threshold = sio_is_t6030(sio) ? 0x600 : 0x800;
cfg.threshold = sio->hw->fifo_threshold;

/*
* Dmaengine prescribes we ought to apply the new configuration only
Expand Down Expand Up @@ -967,20 +977,23 @@ static int sio_send_dt_params(struct sio_data *sio)
static int sio_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *match;
const struct sio_hw_data *hw;
struct sio_data *sio;
struct dma_device *dma;
int nchannels;
int err, i;

/*
* Only APPLE_SIO_T6030 checks a T6030's boot tables and configures its
* channels: without it, leave the SIO stopped rather than start it as
* the generic one.
*/
if (!IS_ENABLED(CONFIG_APPLE_SIO_T6030) &&
of_device_is_compatible(np, "apple,t6030-sio"))
return dev_err_probe(&pdev->dev, -ENODEV,
"T6030 SIO support (APPLE_SIO_T6030) not built; leaving the SIO stopped\n");
match = of_match_node(sio_of_match, np);
if (!match)
return -ENODEV;
hw = match->data;
/* Refuse incompatible firmware tables before DMA or hardware setup. */
if (hw->check_boot_tables) {
err = hw->check_boot_tables(&pdev->dev);
if (err)
return err;
}

err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42));
if (err)
Expand All @@ -998,15 +1011,7 @@ static int sio_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, sio);
sio->dev = &pdev->dev;
sio->nchannels = nchannels;
#if IS_ENABLED(CONFIG_APPLE_SIO_T6030)
sio->t6030 = of_device_is_compatible(np, "apple,t6030-sio");
/* Before any register, power or firmware access */
if (sio->t6030) {
err = sio_t6030_check_boot_tables(&pdev->dev);
if (err)
return err;
}
#endif
sio->hw = hw;

sio->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(sio->base))
Expand Down Expand Up @@ -1107,12 +1112,6 @@ static void sio_remove(struct platform_device *pdev)
dma_async_device_unregister(&sio->dma);
}

static const struct of_device_id sio_of_match[] = {
{ .compatible = "apple,sio", },
{ }
};
MODULE_DEVICE_TABLE(of, sio_of_match);

static __maybe_unused int sio_suspend(struct device *dev)
{
/*
Expand Down